From 67f62d06926b32e2e6df47ce707c743c1554196c Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Sun, 21 Mar 2021 23:27:26 -0500 Subject: [PATCH 001/111] 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 | 976 ++++++++++------ .../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 | 1026 ++++++++++------- .../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, 1453 insertions(+), 1025 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 1701fed8a..16551aa4f 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 ac0e14d77..1049e0e9a 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 11619b357..bd7d05c8d 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 de5149db2..551d70e4d 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 a89324a75..de441dff1 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 81aef23e6..74310a86e 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 755a3f3b7..ae4d5296d 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'; - - when(mockDio.post( - '/messages/messageid/reaction', - data: { - 'reaction': { - 'type': reactionType, + const reactionType = 'test'; + + 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 de8ddaf88..1e46fee00 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 8c7b57e5f..ad81aeffb 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 e60706dcb..7f5bf4b1e 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 d9486477f..9dcbd4e37 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 21b9d09ba..6ae8ee3ed 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', () { + setUpAll(() { + registerFallbackValue(FakeRequestOptions()); + registerFallbackValue>(const Stream.empty()); + registerFallbackValue>(Future.value()); + }); + group('constructor', () { - final List log = []; + final log = []; - overridePrint(testFn()) => () { + 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)); + 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( + () => 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)); + when(() => mockDio.get('/devices')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); 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)); + 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); + 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)); + 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', + 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)); + 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', + 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)); + 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', + 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)); + 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', + 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)); + 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', + 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)); + 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', + 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)); + 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', + 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)); + 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', + 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()); - 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(), + ), ); - 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()); - - final client = StreamChatClient( - 'api-key', - httpClient: mockDio, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final messageId = 'test'; + final client = StreamChatClient('api-key', httpClient: mockDio); + const messageId = 'test'; - when(mockDio.delete('/messages/$messageId')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + 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); + 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, - ); + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); - final messageId = 'test'; + final client = StreamChatClient('api-key', httpClient: mockDio); + const messageId = 'test'; - when(mockDio.get('/messages/$messageId')) - .thenAnswer((_) async => Response(data: '{}', statusCode: 200)); + when(() => mockDio.get('/messages/$messageId')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); 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)); + when(() => mockDio.post('/channels/read')).thenAnswer( + (_) async => Response( + data: '{}', + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); 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}; - final Map queryParams = { - 'test': 1, - }; - - when(mockDio.get('/test', queryParameters: queryParams)) - .thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); + 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)) + 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,39 +881,47 @@ 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 data = { - 'test': 1, - }; + final client = StreamChatClient('api-key', httpClient: mockDio); + final data = {'test': 1}; - when(mockDio.post('/test', data: data)).thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); + 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); + verify(() => mockDio.post('/test', data: data)).called(1); }); test('should catch the error', () async { - final dioHttp = Dio(); - final mockHttpClientAdapter = MockHttpClientAdapter(); - dioHttp.httpClientAdapter = mockHttpClientAdapter; + final mockDio = MockDio(); + + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); final client = StreamChatClient( 'api-key', - httpClient: dioHttp, + httpClient: mockDio, ); - when(mockHttpClientAdapter.fetch(any, any, any)).thenAnswer( - (_) async => ResponseBody.fromString('test error', 400)); + 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))); }); @@ -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}; - final Map data = { - 'test': 1, - }; - - when(mockDio.put('/test', data: data)).thenAnswer((_) async { - return Response(data: '{}', statusCode: 200); - }); + 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); + 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 4859fcbee..5d1429537 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 b15458ad5..3124ddd29 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 0a14cb76b..35dda66e7 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 69b181974..9f6aa5b71 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 dc44f7363..8fafd192e 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 3f8de16ec..27a94982b 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 db99b01ad..a7c669dff 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 237f0030a..25e1d78e6 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 3541b2c02..0898e902d 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 8bc92081e..2a1d54317 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 b15ce66a5..9fd68b6a5 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 6100b218e..0a307126e 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 537bcf1abb38d9e69f7d842a97f60ec3f67c5135 Mon Sep 17 00:00:00 2001 From: Felix Angelov Date: Wed, 24 Mar 2021 00:09:00 -0500 Subject: [PATCH 002/111] 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 1d526a16e..919434a62 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 2d0a529fa..d13dda4c4 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 2a8b7eb28..2d010ab67 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 bd7d05c8d..6a4a77f47 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 35dda66e7..c2d69b2b9 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 003/111] 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 c2333b05a..1160433df 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 5a5fc7da1..b0a726911 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 004/111] 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 faa4fcd47..2586bea65 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 12d117b10..3242efc30 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 005/111] 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 b17073d8c..f0ed42a5d 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 006/111] 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 77537e59c..5347b78a8 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 007/111] 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 1b27c763d..7c000e8ee 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 429745209..f6c5f596c 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 008/111] 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 7c000e8ee..3b7f8c2f2 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 f6c5f596c..4d56ba49c 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 009/111] 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 c95861d36..bdf7ae260 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 b0a726911..6e5149240 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 1dc11a9c7..4e81ad519 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 3ec389bd718006c8f8ca816bf68e29d7a9b358ac Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Fri, 2 Apr 2021 19:19:20 +0530 Subject: [PATCH 010/111] 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 e14903e26..5993f0bfc 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 cdf17889e..000000000 --- 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 6504dcd01..000000000 --- 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 8a5270df7..af8595d56 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 d3e003ddb..4d8066f23 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 b00711761..91cf4a011 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 4387a9180..ef8cb394d 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 2586bea65..7e3a1549e 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 4e81ad519..9a9026f14 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 011/111] 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 98190f2ca..edc23a775 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 c80fc0a6b4f0a2569f9edd7f106984a8826b3466 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 8 Apr 2021 15:03:16 +0530 Subject: [PATCH 012/111] 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 aeacaf7dc..3b6848f69 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 51151fca8..5e3c07418 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 431a57e79..a26fc626b 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 a4d1e7bd8..2eaf6ab52 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 1049e0e9a..a63a7d3ac 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 9ddd83e50..ab821ca95 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 ed37ba7ff..8402ca0ce 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 7e2e47bdc..e2efaee6b 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 0f78b1bfc..ba4d42711 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 7d9e8c402..42fab498f 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 f46ba178b..5ea3030ac 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 d15772df3..6036c649e 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 60dd6bdff..714bb3861 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 1a3376ad2..af57a35a4 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 c9f5934fb..8ed914818 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 49949412d..e87eea7ea 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 62d0f104f..b91f1e130 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 551d70e4d..ae1697663 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 bae7810f1..46208e28a 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 3541573e9..1500aaf27 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 62ea18535..827f1246b 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 3a1f3178d..64c88af7d 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 a5ababd2a..c8df72fdb 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 150e6759c..284812827 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 28cf1b38e..ab97d86f4 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 e99d8d345..bd2bed8a8 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 de441dff1..0df5a7caf 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 e3d5e1a03..65300d4ad 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 18ee2abf6..041c4e950 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 6792bf3f0..42c238417 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 fc8ef1bc1..87b3dc890 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 18cdd545d..48726d5e7 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 3b9f9addd..97411a3e2 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 74310a86e..defc8db53 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 ae4d5296d..058a80440 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 5993f0bfc..0f3bd5d61 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 3124ddd29..899aec737 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 c2d69b2b9..9aa2b2d65 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 9f6aa5b71..4a221a8a0 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 2a1d54317..80d2964b8 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 9fd68b6a5..7575d35fb 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 013/111] 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 f020c1ea4..fd9a5a351 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 1f290d6e6..141534a5a 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 3c567843a..64a5195b2 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 c45771c28..a992096f3 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 46208e28a..8dac46f6a 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 4eea1f13e..a6c5e0c6e 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 c6716fba4..8dc70ac3e 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 e8152c80d..a11048889 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 4f535f46e..9b1a6aeb9 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 a66899a43..2eaa02d2e 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 f32e8e8aa..b6c08c322 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 bac60856e..ff4acc89b 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 aaef8181e..71566caf5 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 3ac8778e0..523916994 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 0df5a7caf..f559ad1ee 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 80df15893..72d2ea1fc 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 9d0b9318c..c8fe54445 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 887e6b28e..ce2653082 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 a270af018..644c643a5 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 d04ae1466..83c822e5a 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 b27935a7e..06989395d 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 014/111] 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 3b6848f69..7606bc9a9 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 f559ad1ee..6c793f633 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 d5efe48aa491dfbc252645bf3d37a8bfb6b03790 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 9 Apr 2021 11:09:46 +0200 Subject: [PATCH 015/111] 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 c3265ea19..f221fc397 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 016/111] 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 160c0579e..51ec7aa53 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 711802ca4..522a72037 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 zcmeAS@N?(olHy`uVBq!ia0vp^DImGrEN@Q7lQ+uOCZ*uuM z$1B#RE^Q-5DQ%ksmg+A}P?lT>zcne9`_G@C8DmwD2)*IWOI+A{4I zJi_C6Mt}~)p3(^i6Baxyi1^a8{FheM0cE~+=ZnJg-mUdN@xMbMjU%ZIPCQ$C-}%?y zIoA19DsoThgPm^q#q1ZufV&?8$(sCX92Xm=@@O7Zp{Pj(IbGHK%cCkyTQ zIGP;JrUe(u*k_+VzFXTv=6KZFuvtE8la_q`>C>aW`*+KmGV7(8TAoKL8WlVz-9EqG z{L1Untx>w6TB1|ERG(I^(>Xom`A=>0x+(H|H9b2u4}V$&OhF8uu6{1-oD!Mz~k6Vt$CSBwqZdmqrS`>sBak~*jaxBJ68{^{%p zE^I*bjJe>uc`7Q5sj2ut!3K`evQ5e6c4RG2A%}lZxJQ+AGT%y`wtG*f$~ACth1F_X zWDdpOtzZy|H*#S^XwP2kAM~m^Bb3jz-GZvqWsGjXWZCcY) z;U3ADTw9gjT=ScIV!kGR*{tV8^@K{-Y5SJ$mEs%jkUqF;$I*Se&K=r!Mg35VwfgN8 zYpI#B7qgxKQOfjxON@6!MPF6#RpC;51~#Tc+2-YVNxP>0#$(lpHP=#Dj3NxlpEYj^Ox!Tl zgm3F+*?^ZPn%oqa7yeGXi&^B-(Dc`EHwG_Pb9PqvIc4hk5jXxB!hTH>L3p2Z3%=Gy z-_{tfu{yory)>G|7F#P@I91PPIcdUsRla^;F*HoHA5M4H#r?vwj-^crD$EWHpUd;Y zl=v#B&H4UlnN!?Y!hxfH`B3o$I25LW?D?8ClpF|n|8q)Spq~-QIS_QKXFtVClGU(0 zRtG==o_Yx~i#o4n&%UXZ)m(;vkP&)ipB~SFhiYI!5#ozqCsv=LI$VjmQ2wRpuC03J z3yGzf4@4l4l&AePHWClverx(xXIz3jJ6yedMT~lueD=Huok7J=@wmtm&L zH24u+2%$f^M^ER$btBo@M16m9n1OaZBAU2CG)3DjkX6!ER-e(u{9(}2dU}B_gvY8+ zS?rX~*?2<_&36xe?R<)v8bMxZQb_a8;gth z?XaTz{OsSO(CU~6BACqB0}BVy^;*s8p8-VxnSQHw;W^-=^K)!OIA1QcjA+vTJmXpK zG>;Jo5{o4t-^CKQcf4M`Nk`A>%|wEs{GpF&>18W&rT3grgi9e)Ux+4@&4AzuyAKyc z1|On*^d4$b@nQqotQkPnwX0MLxJoySu0! zvbO@L{*N`rGdssYhi}wd(SBH<-lni-Ju`ml*e^T^SyX?NX!g?m8Yvo{O$#pde3X?# zz@CZt=ICR@jY$a4ry+i}*j!UkmLbtJ&QZU7o;v-Aqqg=7i-+ zF18!zKfjcONTL<=7Ug*Ns#2^8U+yaE(?$k~k9q0sHT1fzDy2k;78Q@NYQ}(&gYbst z7ps9Q^MAouktH&(#mlmbT<7{IhJ6q^rw%$i&7&4bHkC89@Z+PDIE#tBfbkAB>!n#o1@)n2Wi@<}+Zg$&S5~704(SXZixE zAhkG{e#7CDazRqnAw-Uci|zOGNq#5uqG|2HIExoP)bVtpfIM#b=)H-?!x!#k&1nZ@ zCTGtm3EgDoU6y{W!jWVW$vWvX<6P^Bszh*|<(EMTyxXAM(Nl5P;e9bIh|Xlyr>@ob zNRNvd68dVH%fQHUKOaQHgL4XbxIbEZ*^t$@;*mP{ej-~D0^9XEZ|5rM`HZH;~m5A&dq$8RmiSveDM;>0}Z9lo32?T18H|bvAiF+UW0MQ zOc1bAT+-HY{IpS@Vx*W;Z-G zNgsP+P1#Ih6&6C^*~u$stNipnW1Ac*{H7 zW05{lVy!v7kUKZknz}(Ocb?Ia7*XMYh{Tm}HUP4d1*h@VwK4SU*IFcSykzlp#HS95 zYFJ&-5&;_71^tL}!6n?yk#LG&a)dGnq|%BN;f>n5_~GWOs;&Jm#sN;i0@biE0MuFW zAX^;iX&Fb5wd)vlSXRU1sCL?tN7)Ti8sEMN2MRtW?Dm3J)J4+&Rp6dzVs$ElMcdT1 zyc{)|F}|tV%mBP>qYKBTtTmAg<~osKg^-)PBoM7GM-CX*)TdRcHU$vmCkEu~bZ9JtUsrayrQ7a5Q=+Bb*#SNJ zDdY^A43p-o^@bg=G|e06G49W%gqK;YuY}avOd4bLt^}Oku4qGB3C8{F1J)~xMYfao z?)WpyQ_8oh;F*Er^C6}=I*b{H#~W!a6zT57I-zWBQ-_TWJEPUK`agc^!QEZThI1db$s&S0n4jy zmaF$k63$;+D}$0F`xw>1jp?kR`P<^TU&-h)*oHws)BH~>aN*MQm3S)TpxTgiyyss3 pjvB_TXPjfIb@_%njk_f~e-G$LjU^u!03QY^xV@`grOlN){|9_G)r9~6 literal 3339 zcmeH~i#rqEAIIksiy~18vBZQ>Qn}yqZS!@@owOzrm72|U%-l+DkxS%GOImI*GBZT9 z++`xUi!t{tY>N4(hlQks006*YbEuge zmtX!)pa6HRaH%Zdg2&$uVhX72lc94D{Qjoq4nXd}0Pn;B0Eg<#%}g9Z@>a(qeAA!i zQy9S>dPXbl3jA^6MG6LKulS*ek|)WNp}pWO0S5<_j0kJWkkPRH*w*Vmp1Gt7CC6p+ zRr4!>%i2~{?3{HeK6*>*(wzrYiJ>?n;2p;(#nm)p8KL-3h@)djUrMu)WswB?vq)_p z0dp(jbH8V}CzG}_vw#+wWrRqHHB`1?L_wNa%16Xvc*QQ9{XhO|3iPJ<#{U((Cdw=& zH1SiS&5nu*P@;Ea>HfCsRi73ZwhL9BfsW!VhpGJaRE~#4`%gwd^HbuZLZod_1t?6N0L{D^jd!6t%kOy? zlm>ty0!(HxETAAnI5NKlX4?G)*q9T<%snPbL)SFiULIK1|2p=$I7jM7bEr_{uvbL4 zy_(G&T5|z;lly`_MQ(r@mKhs8W#-nI>I-_y&7H*3b5pop<@xyY{lP6?qUV&c>;Z}D zK}olzD-0Lv2-6@3r@f?wSctFpky`;wXFI237U3OKmSL%OrRTW)XYP(dDcREB%Mp&; zZWSS!3jWg1CTN47L*?5vLdPO|9({Ytal`~q)HC(Jjk6{Kr)FP;9n_0xE6V_Ge{q~d)~k4_Z-#r?NqB1wd@d)`isj`;Y*?6ag`alPN_bceJ6-Htg1>tOKu z?G;fY#beTiTb6h{uZO3)hiB!3Te%$c{dexe>-+b_4fea)&4u&iNn&Xd%t~)G2=uGb zIwDi^A_S8@c}y3oAXW^njWZ6>DkC`JziWF&u$Ad|WTtYgB-G#*D#v5nOobB3?M8-{ zb5OyVO1LejBckY|eHztrhAP`9attO?^dRr#T(N&_#cq!w2O$Y%3{t@yjU3VyMb118}{X$K&_CZT;>06*`DgZXvR zgd0ks#FzlhH^+*<_`>&IHl3##XaY%QnS ze1G{!%5fAEs*DSFo{j8rqHR{sEr$2Z% zoSaum*ls?VWmBMMJ-ca3?iHO?aWmVU69e1Gym^f^=;$6kD!P~5Sbq)qI0pyoXw#ZF|)IzK#8gC zk;hXJFWPnv;(QMyo;6OBVL%ncM8-?kLr`vZOa8-;~;7M$UAy_qw5N{XTW=CV`{ z68+czcWpYmWFjuwlw7u-+?Ix#RoV~uRznZTaPXBGiOwlsK%OXl>mt=3x|2|X9PhxF zXBB2z)pim_<@72@=ZzyeP=KXfRaAkuA<7J7LC!Wd^*VO$SyCU7tmG#MsK%|sc5a*S zl$6PSw4d<%RzvM3s%Fh=+0WN#D{`{yg}f5e9MroF-`I4~9M*=u_Y_iUnP!juY{9w{ zN@=Gc{C4YAeA9kbirj|PPb7E4tr*PX9u!jLkCKGKmD5;e7rnRL$Eey9CQ(Hc&WbSa z^LM=RdBX{eV26&rQb^oaVG*pTH#zqcH1f@esiy1E4E{`M0Q$A2&b6t3xOn8GnyR;gpFq`E z8R9Ep?&TkoTlBGB)xE|B1IcN8-|Y|K^GdF8T0ugd67ueIhdCJulHX1l(s}6mV#RJ! zX@PW?`0Z`-j-=y;`RALKIixWY;liKAK&9$%+yb%r3R5e?IIS zB!u4NoqaBT-~(OaR()1sEyqQW?uu%^0KC&j(jU%ikgj}4RnCN@{MuVP@#}AI<`QpN z@amyGZFme(tB==4O}{h-`NOv|e_H|c_v4Az_6ziV1w4vQnH38)|qc&H=?NY|zQ!_T$O~&VxNAkK?Oz3RvYP_Ei|AA@McTnu>p$|v-E6>fA5&`4FCsKTf8`TY*2uw-z-G%@8v^%w=rFJ!aD8FGRx4=+%?^yT2 z6EFOTZ_-4iD)qJdmbaPDGL`Dw0>x!c0sVGlmq!Fxw?q`3pS23fuBlARwXo&I3yH=X zVRE~AN=}=|qE1&)oeffxzC%m4+E(c1VlN52?j2?@qQ=e zpdJ0F2)|P`XXtb+Z~E>?R)XQDc*HZ^eIbmj+!jYS=fvd)iAdqSe-3E6Rc)@)>ROxH zkJ>uL&N`%6Au*}B#VRSXA1?RzmKk%!cT{?XNPgl9`__XyJm$g$KAu`Dy4Hj_^EGX- z!xukQp7@*AfEI^3FM>378B>7o=btyYBlf#`_zo3}ZD^EN4t9j0)F|Pinr^GDJ?Ybh zJArxM3{>x}HN<#h^BZh_zTiyk$XLu58?GNb^rhX 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 zcmV-)28;QR54aDIL4RUNL_t(|obBB^ZyeVd#_{Le<_woINjnnlCI$kCDq;gEoXVFX zND4Xm5D5aLPLTjR2-`QD_ zvwD8*YKKRo|IW;rJtHar8AO+)uX+G9E)0f57z~Lp7!qMHB$KQL8IuqJOOp@*HGjme zW24$wXk(z1fl>-ezZhNSl#p{q&Jig_q!_MxNP|-t>i1SF1!EnoEdZqk+2rWuj1U^c z&;ofgem(dg!5HgconMAhxB`XuaLz$%4gG=ylyXK&32ob=scSTC)4h;0+NRowBhJdf z*uulu0s!EBiOFOFqcuW|2r(khcYorGKx+-96s$3@#vrDI)6-Mbbp-&3p+RWsD<0)) zRtm-zC5&|dfZg3aIBU_gE!r^tPtp*awJ4lJ8v#pqaHyP}fc#j8feNF1piDP6<`h3_Y%mv)D(AXk1Z_ayBUrW-)E{zJ#+D zb=$6ETpOuw8aQj=eHV+JAEn1c9L(Y(cQTovY1>|>p`ZTb$N0hbzbAg*WqtMDUvc*B z->hx>eaD}3M$@*KOeO#x?SIQL5e1`-1(ZVJ3K*?>&4%tA+{XU?EqwQ_x5V$gjP|v= zh&ykf{?^advi()Z--ZCAH44`qsmFFLBMim>0DM^@#MtWx9vmD10A|O>z21M3_SN?R z0Q&6yTDCvL_(P0v^O@6PpN@qn7^S~_$;dKUm- zj_(b={h`JmVq8wO7yEQ9M8`ulzp7VSBloy_`=cMekJ5KDtOwI6O7F41zYhTT{EIKd z+k4vScYcfPb^rk8_z>CcAnv>Y0I>i5Xyxq>HU6CQa)zWZu9=eP#+{$M1OT{o^A=7{ zPeniI-FM!`yYIaH%zxKExp#=)|KUT?HXm2r`6a4@Up({m;{FGi{Qc*>w?FjwZ%ih5 z_^*cmfco^S-s5`Sp3Ay}I|sMV-Pr&zo6UM}zkTiQxv>C%KI?x3Tle_KL=;TU-DxQ0 zEcyoe_1C{C8tU?( zFs^|2C2riffwQXOvW81iN@1%kadL8kx~>r02B96-x_=n)v^snt^Tw+2bq9=-GTxKmp68nr%~!@=1bTH||mli1Sa!(b0dP zwMOav@~Z=SL@9;RduXL_v{={MHluxlV9-V2cD@Kaa1l5Fz|I$e2QC7CW@bvKi;7RDIll<@WAkC}|{BKK*7JjkYvePWGj;R^V& zTfzyYU@o{Td^9^k2mv?0ar2p9YHn|Bp{go4=V0yiTyzvu>aOuZKwVXQknQ{}b!$|O zb-foiZ*OnI`w~@EJu#@lIW%>Rv+8V+p*}Y`cYmj#`OIVB}mJj1=1BJ^yjK8%XOU(2yQ#c=7J0RS;Z)OC#*TEx(dZW+L)y7K?HC8aKmoHKk;pe%~{o#YxR zMLvY4Vl3ClTCQOvw6So03jpBfU+UF$cj{^D)6spKyXmex7@&*BxB^;fKq;gY5!;%v yTtn9)7#fc&zlNc4laK~AlMn$*lMn$G7Wpr3?$W&q(BuXH0000C@Z12!S+6tN|w*>+!BPCL@+fZQp8Qa0b%;R~k zFm?jF{P@~q20NdZG)f}u$4|!Z*faJ_#sXkKbWX9TCqVs$!IOdqdy{|%DSsq-G3dQ} z_b@XvgLFC#+qN-1J>83kxpcO+wxE>4=H@2W*VnPSx*GMALT4eQ>sH&gjk&ow%+1Zg zw(T&6?0ZTnEH5u(d3hO1si>zDico^dX0upaTx^FD1Oa@{gVq|2AVAP)bo&r}Ar_A# z9*ZH7NMI;eW%2c=6&z#4`+s>1J76Tr^s1qqQ~%u4^2nOsQBjwOY-1 zo@adDH=hgN_l@UyrdF$&Qn6?prObirw%5<%;$m0p`jRZ2-+5(arM)t$m5M19i^i?j zqki5l4Yyu5rDD-kE0y+oT3K0%`U#IFn5Cs9qqR1^@0)VDY$}dpf`1^0`Z>CEf*>#z z$1#pl#`k@rwKhvjOHn`R7aL4An{EG#DVNKpTB$_+yj*(KO2s%zwbxrFlZpC??`RQk z^xC$KH*em+wr$k+_W=M`$HpW>e735U3M|XQ$ml4PQn+#BM&Io{i9fh&?KU^GhVT2h za`kF2<~5M6j*Y?heSb8xZhs8Xk3=s?dwY9EYi%5*I^Mv2_UxH?`SNARFZv%%|I?WH zUwSrtSMzu4_4ZrS-rip1Z!ywE^a_*7WMJDif*?Sn(Lic=SaNp&K%r2;?(QyLzkV&g z?|I}mfUQ4(r@!`O_^#)l8XiWY(LfLcux%TeOeW+R_LeA^nSYrX0D$j#NTyO9Jz$|w z0Dzu-8l2x~{`&1#JsCcf`6p8;Xx|6$ah>)HQ84Lr8UWyHjbt(@elXW{+beuxVnTfX zv#5RG#bGe%Z$lqGwD~8KNqC3BwAN|A5PkVLIXMZS`O$woCjX*4pU=Z}T>#A$?z%2^ zcXt5**REX?kAHXE5C3UK0|4pC=I^(DZ~l4aX4k`qHh;^qU`%u8bbNfg`-xp3(QdWY z8UUcmWqkGZ*P;hnTU*20+S-XfPp8wkfB(K{%uoHC2mX~m`RCd1f&c#4@$jL~zqq#t z`^zr@0Et9T4jSl*3ppO9P$-nxaf9E~72_x)P$^QXCav-6FN&ELs{*fb(r%%a9uQl0I5_8j(_7|c6PSA$wff)UBbe`Li^~tQmMpa zU<867Fr`wdeH7lp!b0TV4M~@d4sUq-_ASQ8$5G$khcO0YUwk1M;fjVDQ$4uu{sDN+q6VQYsmB>@<^p+{it? zgmNNVCX+!fmur9X+B%WV*BZtcG!DPC97lBVL;{v&Vd&V2Y^~krK{$~u^iyT$=jZ$G zEBjt?90yOIKIN&hXUSwTM^46{nwkpxAugru?d>Bc;}60s%5gmjh6We^f0L01Y?FWo zBY)=#=FXiv?F)SqoqCPGbUJOy<+3T4%jVIeN5;18$gexdCDP^e8|3r(BNxJCIG_D; z`VEv)e|ZJfRydd7FTmwumy>_^x9`3)<#PGN%kCL^D z*gH7DlP6CwJw1KmZD{cPsiI6K)4n+O{$Ym?#!<=yL2%^NrB7X5%T#I)J(XxK?O~+M zX--w$|9}zQGwdI*LbJUG2`*h#3);)fnf6j?4+w5ORlq4ot<=t5I1pnenZ(#n{Olpb zg?3x`CQhBu&r7ljqH!KMZ=A=m-5LNih%PCY^#EwNFc=bHFq5SQilovC5(6c^=l**08p=2G8@d9!@G!31)tN9;H$#2_=qWL}3V}6k2hNxYg?QDTYGM zG-2j)$mjDIu`J|r?RR|7^MCN@(IY&2_AKKOrbG9#lu9M7l+sEmeG&xP_dM-ZD!N{; z>o5#;6h-=L5k-*>!%)}jb?sIv+V?zt5(LTiDV0h+ZR-J9x_|Kc`g*c4YSpTCD-|6y zn_0hZ10!fQwOgs^TD6*Nr}g#qtY7eKf>~Kv(Ml;DMUg%}KGs#=*MD&wXZ;!toH&kk z)%UgUc{++Bt(4L$D=S&Q=wCLN`T6Z%p6B7#ty@EP^d$b^u5;MjQVLNN z;o9V6KgM+>O-@ZAihm-sluAB^7)GL9($Ue8R!V8#^SYkkK6&y)zj*N?<(>X}bAQ)* z>9@YEzNhgA&1Uk}baZr-`CE*15$$0d$AN9zh~pToRtuwJW0Hph0LtYu4i69U^5skM zV=rU#6|nbTVD&$JS$)sr9~~P*tJOjr$FOZ1j^m^}!u}EkQ-3TL0RT}L!Wtdz>Iuu` zG63}L)8Nfl?LWW&wlAwsW&GCYC{z>yc)v}Dg(#Rpp#T7gl!9eh;wKA&AlcwEGc)4H zU&Lzw&rX9We4qO2Qyaf!SqM*q>1@+sAv*auJ39-Y{n5Xflb`6`+}uPE1OVC_JO~0D z9v%V!Zrr#bUVq<*e)(5B8UQHFw!hx{qy2sHcF(I%ZTyB|KGbq;?*sdSM2FQ% zDFA?Xe2j0u`%d&k+uPgN-roM?&kKbD9zJ|1TIX56ZUX<<{PfS~e+2&h*REHe`uHnH zN3g&71^|%H_vNC2zW5^7!<5VA&u(m>w>iP)tIx**0Dm`ReV^0s@edPGFz?>IYdcfWNPjA^13$CrLw=Y9TjZr|>HlCk^8 z#ee7F4_&W5_3>-1VHgGgT!WtO?@P3;P3sEpTJ39b?5z7(|ierq9j(@`UeJm_2^ftH*h|VP}FE1xo*SW6C zYhVQ8IM%M~CRgDtFE3|)Zb-Uxb$P>^H*YXKJ&k6g0j)Kru3wj|;;VJ`@&@1car5TQ zt3Bxj+Ozu3ZghNN0B)2xTqwXWaKHa0fSUj**D zItYTSrwRijIGyL6y$F0`Vc72jr}qG;%ENjZ0$iR)_b zgWF7oagzJtlFE&2j^kisV3OBN)eyi-#(o)x_?8B22L{WsP>(|fCh5oG9VdnD~ zIsLF}Sopq=)zwwrDti&fan9Y0f9KAf3m@}mz23e~1VG$swSQAbMi502Ow&w`E`J8u z{{H^CoAIy09_9bZ^gU52rJIe0uAaHKsp|W>UaRRwqmj%3hWzHQE3*vjl{=0^?bb6qdw zbzbWb&db8sIrBElOWu}|msU2fo%*_QzoD+(CP~2@0kGfLxv(&dNQJ`H9&Vf7s@5YRl&0 zvO}sOJ`?2)^#i^^_Jv0{?HMoaI(;7MEDi4!?tzCvGk37vZ@1Omo+f z2Mvkc7)p%dKngKbk$a%YsP$3b{qB6Ydc!F8;m+E9?JG2~%+Vef98}?MH8MT5PR)G# z{G5Y*W<4oP^!O!dQ)SjcaB{NL{9+lzpK-M00VJCBUjN(MfdT)z?PXpfHI}P+1baSGWblR_T~> z4lassARN0yT-NZZ0zX}$!}#f06RsQ^-t&7)`@ERqYQNH1pmky?&XV3f7@JhUJj7|R ztWiv5ns6F2n0$UdSZKs@j1SPq)6hHn9nU+=F)o|-`4q;--Opt>yFbB)=cU3CrL6js zs5HqSDLGPD@g!pk6T;`Lw)^Tq(OAzM>3T4hSFf(*Hkl$Z@TabAgiAPQd9?;P-Ltv% zNG&WaPLuu=v=iKHcRDV4MXj;XOuED^xHlF`?C`z>-86R*5wvi$eZQ5R2pS>9_D)P_ z0%DkvX{2ua_kpw~Pm;ZLI)u1oq?_3CAiLxwU(hJmy2Ou|i4PjN_jI-7$1vKP>wS=~ z@bK<-p5$Ef#wMzy`j@U>`+jl{>0Vwo1X3`RBvJHPA-e5U7rho|0e_fN3cQy}Ns=~+E23MK^TsooDig3+eHRZ^_9OI0EQ24xzPT&APdfFk zKfyGaYt*t^Z#t_qL8O0}nzJgLR(-4vg|5p=ou{!@-kyZF;2P77c;h$~b(*drUDWUPd;S4hQo zWlfkW9|aIXrWPKQ-C%1*c)PXrEuj3x3C`M3kGCOm_!Mb2VfbyZp)olkqb7RVEUe%i_|lXC7#86%^ljK5g$-CE-x3GD_qvhnNU!!h zc*LoQN7Zj)^2bWL^i>+cLJg82>Wzg4)rU$AU!s28J`g6O9W;O{lI&eJU}xiqtH1`N 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 017/111] 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 6077c4cc6..4b3d81de4 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 018/111] 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 7606bc9a9..3782b4b99 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 141534a5a..849b6d1ab 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 2eaf6ab52..f0f2c67b7 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 a63a7d3ac..e09c367b1 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 ba4d42711..b2208a135 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 42fab498f..42df3fb22 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 5ea3030ac..5117f7da2 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 6036c649e..b3ea7c7eb 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 8ed914818..bf54fe5cb 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 8dac46f6a..fa74d8011 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 64c88af7d..c90e6298b 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 48726d5e7..d7cb0ad84 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 defc8db53..c854c3274 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 058a80440..94990a7b6 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 1e46fee00..0e33b3d90 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 ad81aeffb..f91248af6 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 6ae8ee3ed..3c0a325c5 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 55b1818a8..9bbd446fa 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 019/111] 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 b82443343..0ba666675 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 b78501d6a..1fb512799 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 020/111] 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 5117f7da2..a6ec79d70 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 021/111] 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 3782b4b99..7421daa7c 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 a6ec79d70..f6b9da797 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 af57a35a4..8b1cb51f3 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 1fb512799..1849eb81a 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 42c238417..9f7b58513 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 022/111] 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 d13dda4c4..878b5fa10 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 7421daa7c..80f34ccb5 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 f6b9da797..4b06788e4 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 b3ea7c7eb..7911a9864 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 0ba666675..61f7afdf4 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 a992096f3..207cc02f4 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 827f1246b..caffc30a5 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 9b1a6aeb9..098a22e83 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 c90e6298b..5f9f02bf7 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 2eaa02d2e..c207a2f63 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 284812827..dc613b3c9 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 ff4acc89b..343ec0e59 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 ab97d86f4..295d6d8d1 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 71566caf5..5e1e7e587 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 bd2bed8a8..735e4ff3e 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 523916994..f6716e3d6 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 1849eb81a..8f260b1b8 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 72d2ea1fc..cac1b1df9 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 041c4e950..9ebc633b7 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 ce2653082..5cc0bd530 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 9f7b58513..cea2eaef0 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 644c643a5..2f1d1eb8e 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 87b3dc890..3fccc20b6 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 83c822e5a..7b2a6f481 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 97411a3e2..1488696ac 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 06989395d..f1e4088fd 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 94990a7b6..5d7c61f06 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 0f3bd5d61..f6616beb9 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 3c0a325c5..775d68b0e 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 9aa2b2d65..79b8b0fa7 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 4a221a8a0..6d0475e0b 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 a7c669dff..4e657bdbc 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 0898e902d..72b9ba098 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 80d2964b8..e82dcc79a 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 7575d35fb..24b56cd8e 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 0a307126e..8a30ad4d1 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 023/111] 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 ddbd70dd1..d439bddb6 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 80f34ccb5..dd25976c3 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 5e3c07418..e78a80ec3 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 fd9a5a351..1ec7b5fc1 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 849b6d1ab..8be1e62b6 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 b2208a135..1e8087952 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 4b06788e4..147a08d07 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 7911a9864..daabc26a0 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 64a5195b2..770298c77 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 207cc02f4..ff371a5f4 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 8dc70ac3e..55fcea6c6 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 a11048889..41cd0cd28 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 098a22e83..eb292e8d7 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 c207a2f63..713d324c7 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 b6c08c322..1ce004156 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 343ec0e59..fe65053bd 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 5e1e7e587..aa38a032e 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 f6716e3d6..6c014f9fe 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 cac1b1df9..54e485a30 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 c8fe54445..15e3b810e 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 5cc0bd530..19629e429 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 2f1d1eb8e..a0aad5655 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 7b2a6f481..158f6f8f2 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 f1e4088fd..457999a6e 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 5d7c61f06..97d4fa6f8 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 0e33b3d90..762534033 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 f6616beb9..8f16a25dc 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 e82dcc79a..2a8849624 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 8a30ad4d1..b28888c4f 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 024/111] 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 878b5fa10..d13dda4c4 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 dd25976c3..01d8399e0 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 147a08d07..816d780cb 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 daabc26a0..2646665ac 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 61f7afdf4..7a3024476 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 ff371a5f4..acee04fa5 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 caffc30a5..5a45f48db 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.createdBy, - this.frozen = false, - this.lastMessageAt, - required this.createdAt, - required this.updatedAt, - this.deletedAt, - this.memberCount = 0, - this.extraData, - this.team, - }); - - ChannelModel.temp({ - this.id, - this.type, - required this.cid, + this.cid = '', + ChannelConfig? config, this.createdBy, this.frozen = false, this.lastMessageAt, + DateTime? createdAt, + DateTime? updatedAt, 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 eb292e8d7..19c4c0854 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 5f9f02bf7..9d18f434d 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 713d324c7..67e98782c 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 dc613b3c9..24dcb6787 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 aa38a032e..651c5837e 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 735e4ff3e..ad5f64c8a 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 6c014f9fe..df9b6641e 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 8f260b1b8..f7fcfbf13 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 54e485a30..fddbdd615 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 9ebc633b7..2ba29aa0b 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 19629e429..5802060c0 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 3fccc20b6..ef20eb6ef 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 158f6f8f2..82aeba223 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 1488696ac..ab690e8f4 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 457999a6e..18408d763 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 97d4fa6f8..5eee577e3 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 8f16a25dc..156b203e3 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 775d68b0e..8f95b3a46 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 79b8b0fa7..98d085968 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 6d0475e0b..d2d78fd5b 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 4e657bdbc..a7c669dff 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 72b9ba098..0898e902d 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 2a8849624..49d226ea7 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 24b56cd8e..66efe809d 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 b28888c4f..8f56f7e16 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 025/111] 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 1e8087952..bf27a9903 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 156b203e3..b9c897a69 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 026/111] 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 816d780cb..e951ed781 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 027/111] 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 2646665ac..3f999ed82 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 7a3024476..877c292d4 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 5a45f48db..edd373aeb 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 295d6d8d1..621464b65 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 f7fcfbf13..b25662c03 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 2ba29aa0b..d22f706e1 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 cea2eaef0..5bbd89442 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 d7cb0ad84..6cd4cfbda 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 ab690e8f4..224e0f20b 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 028/111] 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 8be1e62b6..e1363d0c8 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 19c4c0854..58a9b61e3 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 67e98782c..39c6373ff 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 651c5837e..c4df99040 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 df9b6641e..091cb1094 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 fddbdd615..9dfea3c4c 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 15e3b810e..652a1bc39 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 5bbd89442..767f1057c 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 a0aad5655..203be6eb2 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 82aeba223..93c832d8b 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 e8a2de3fd..9e64caad2 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 029/111] 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 01d8399e0..c4bcef336 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 e09c367b1..c13db9c61 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 42df3fb22..70976d40f 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 e951ed781..1066c6709 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 3f999ed82..2dc2521c7 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 5eee577e3..cdeae39d1 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 8f95b3a46..4e880bed4 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 5d1429537..22e660788 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 899aec737..1ca451321 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 d2d78fd5b..a48c5a23b 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 27a94982b..5cbf015d2 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 0898e902d..a34911cf0 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 030/111] 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 c4bcef336..4ed2c939c 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 1066c6709..69b6321df 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 2dc2521c7..07424299b 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 031/111] 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 07424299b..502224998 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 032/111] 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 4ed2c939c..ce1d25aee 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 69b6321df..46eb21af4 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 cdeae39d1..4717f5243 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 033/111] 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 ce1d25aee..f868e6e0e 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 034/111] 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 f868e6e0e..90bd60255 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 a26fc626b..a64cd64e6 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 e1363d0c8..2358593dc 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 70976d40f..7b90f0c9f 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 46eb21af4..fa0074562 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 b91f1e130..16a307e79 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 770298c77..9ae999ed2 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 877c292d4..fd91454a0 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 acee04fa5..b7a7304d3 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 fa74d8011..d72cecbd3 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 1500aaf27..9ba36e4f6 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 41cd0cd28..723281c0c 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 edd373aeb..d68edbde6 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 58a9b61e3..e71112b6a 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 c8df72fdb..5ba0043c1 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 1ce004156..cf8be9713 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 24dcb6787..5dc98d25f 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 fe65053bd..5fcd9435d 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 621464b65..520926562 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 ad5f64c8a..49df170cc 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 091cb1094..a75b458d4 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 b25662c03..03a162c50 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 9dfea3c4c..ca094a831 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 65300d4ad..3ba262306 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 652a1bc39..e77b87076 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 d22f706e1..3ce951cfb 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 767f1057c..eefeaebed 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 ef20eb6ef..cbd47dd12 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 6cd4cfbda..d584912e9 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 224e0f20b..67fc99f05 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 4717f5243..e0a29cc80 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 4e880bed4..c93059d56 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 1ca451321..1342e72e6 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 98d085968..afe384f53 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 a48c5a23b..01c06fee1 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 a34911cf0..ddaed93f2 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 044eb6932..8745cb451 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 035/111] 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 90bd60255..6fad7868c 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 eefeaebed..82913218e 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 203be6eb2..963b4d215 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 e0a29cc80..b41214d23 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 036/111] 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 6fad7868c..63fc55959 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 b41214d23..041a3e490 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 ddaed93f2..1219e137f 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 49d226ea7..795e29675 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 037/111] 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 d13dda4c4..11e9779f2 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 2d010ab67..1b092b3e6 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 038/111] 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 ace20c3b1..8e0d5f121 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 039/111] 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 502224998..d93d95dab 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 040/111] fix persistence abstract class --- .../lib/src/db/chat_persistence_client.dart | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 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 d93d95dab..3fae32af7 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 users = channelStates + 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 = 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 041/111] 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 e87eea7ea..440e2cb6f 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 042/111] 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 7b90f0c9f..d00dd4e7a 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 043/111] 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 8b1cb51f3..d99ab96a3 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 044/111] 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 522a72037..ffee617ba 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 045/111] 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 bf27a9903..8b670f7fb 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 fa0074562..aecbce3b1 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 3fae32af7..c0f85f945 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 046/111] 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 aecbce3b1..f3464121c 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 520926562..a65801e75 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 047/111] 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 a65801e75..1ec087a50 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 c4df99040..66933d303 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 048/111] 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 1ec087a50..a70b2b3e4 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 66933d303..afe1f7c59 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 a7c669dff..265337bef 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 049/111] 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 a70b2b3e4..65633814d 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 afe1f7c59..e4889978f 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 050/111] 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 65633814d..ba95678da 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 e4889978f..d0bb0d871 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 051/111] 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 ba95678da..7e57de605 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 052/111] 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 265337bef..16e56cc32 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 053/111] 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 f3464121c..403e5a2b9 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 054/111] 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 16e56cc32..96efa8446 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 055/111] 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 3adea3123..70d97df7a 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 2b4968eb9..2e42c0af2 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 808c1dae9..1f07c8ce9 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 538444472..652092976 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 1c5c78de3..d41086cd7 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 72c5ea869..670196778 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 58a47d0ac..f4b6e6c79 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 15c2ddf36..42ec8a212 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 ace8d7e6a..d5e7af2c3 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 f1bd68eeb..d2412b979 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 928816197..2470fba78 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 4b3d81de4..195c4ee3e 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 f371acff2..f6d7fb50c 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 bd89420b5..42ea0d341 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 12a3e19d7..69a24f497 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 3816fa242..38e25a643 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 056/111] 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 2e42c0af2..b7ff3d858 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 652092976..219b4ab1d 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 d41086cd7..9f55b7e8c 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 057/111] 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 9f55b7e8c..fc858e5b3 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 058/111] 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 219b4ab1d..c376dddcf 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 059/111] 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 b7ff3d858..ad39809f9 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 060/111] 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 f4b6e6c79..c119f2073 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 061/111] 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 5802060c0..785efb04d 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 67fc99f05..8b965589b 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 18408d763..ef1ccf774 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 062/111] 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 cbb8ec05c..d813afc37 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 b11eb5b86..64284a9d6 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 75b006bb8..9d6ea11f7 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 f1d8c631b..f23bc79c4 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 063/111] modify entities according to NNBD models Signed-off-by: Sahil Kumar --- .../lib/src/db/moor_chat_database.g.dart | 2715 ++++++++--------- .../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, 1324 insertions(+), 1460 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 b83b16da0..1453e3d4f 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)); - } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); + map['status'] = Variable(converter.mapToSql(status)!); } - 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)); - } - if (!nullToAbsent || type != null) { - map['type'] = Variable(type); + map['status'] = Variable(converter.mapToSql(status)!); } - 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 6f60fac34..06a1045eb 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 c73166405..8d3d4a570 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 8840d85f5..7cb1326de 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 53cbdd112..2b7d12b50 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 7943ed9ed..1f59b3c8e 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 7ece96000..d496841b5 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 2c9d28fbc..3a8a5b5b3 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 064/111] 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 | 201 ++++---- .../test/stream_chat_core_test.dart | 60 +-- .../test/user_list_core_test.dart | 259 +++++------ .../test/users_bloc_test.dart | 160 +++---- 25 files changed, 989 insertions(+), 1267 deletions(-) diff --git a/packages/stream_chat_flutter_core/analysis_options.yaml b/packages/stream_chat_flutter_core/analysis_options.yaml index 545d5492c..039d02303 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 70d97df7a..322a27806 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 ad39809f9..7910ab7e1 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 1f07c8ce9..834d00b09 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 c376dddcf..f7bce6cd1 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 fc858e5b3..8586e1ffc 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 670196778..ca661dc69 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 c119f2073..34f58faa7 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 42ec8a212..a72425ed9 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 d5e7af2c3..ace8d7e6a 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 d2412b979..1443ef17b 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 2470fba78..dcc3252f5 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 195c4ee3e..750aef95a 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 b0fb9dede..5bbdaf394 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 e021e9b74..de8b0af12 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 19c1f2a2a..af59088fb 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 42ea0d341..c3a6629d9 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 95446f954..9aea9489b 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 e639c03b9..5b427c02b 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 aa4a47f9f..00aee32ad 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 a46b7b5cc..6ca243632 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 ef8cb394d..b48c02b1a 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: 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: beforePagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: '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: 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: updatedBeforePagination, + membersPagination: any(named: 'membersPagination'), + watchersPagination: any(named: 'watchersPagination'), + preferOffline: any(named: '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: anyNamed('options'), - messagesPagination: updatedAfterPagination, - 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: 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 4adc00627..bc5324b86 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 d1c7e83a9..a0f8bd516 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 e9ccd2e7d..8c3482113 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 065/111] 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 63fc55959..cc205772e 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 d68edbde6..2bc17761f 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 67fc99f05..3f4c37ef7 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 f491c39ae..ef1461802 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 066/111] 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 cc205772e..7389448ff 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 403e5a2b9..4689e9ec0 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 041a3e490..8f18d5e28 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 039d02303..545d5492c 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 aa705db1f..a9157e6eb 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 91cf4a011..e03eba6c4 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 322a27806..992cae5dc 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 de8b0af12..28136addd 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 067/111] 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 3ce951cfb..05936cdd8 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 785efb04d..5683440df 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 068/111] 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 a9157e6eb..de10574b6 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 069/111] 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 3ce951cfb..05936cdd8 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 785efb04d..5683440df 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 070/111] 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 63fc55959..09992d985 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 a64cd64e6..92493e8a3 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 c0f85f945..9f4c7afe6 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 fd91454a0..1bba82e73 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 b7a7304d3..d7e76e74f 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 d68edbde6..28a661dfb 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 e71112b6a..47f23071f 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 7e57de605..1d070230e 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 d0bb0d871..2715b4761 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 03a162c50..cec591c07 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 ca094a831..4168777ae 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 05936cdd8..292e0f4a3 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 5683440df..a5c27402d 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 82913218e..0e60e281a 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 963b4d215..24b60830b 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 8b965589b..88a7440c7 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 ef1ccf774..115383955 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 26001160a..ff0fc87ca 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 ce2e1db32..fc4885110 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 233fd4ef9..099f6798e 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 e80a814c2..4a169cd23 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 e6c039964..157b0a9a3 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 3b7f8c2f2..c2e1cde2a 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 4d56ba49c..af6497f9f 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 a2a92b2ef..89da60816 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 95e23d8bf..05d807fd0 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 73c7b0fa6..622e0a610 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 1453e3d4f..b85857bbe 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 06a1045eb..e6c280ed9 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 69538edc6..cf0819ae7 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 d496841b5..49fb46871 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 653155193..5c4ec5550 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 fc2a57ba5..c5cc41989 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 cbdcc297b..414cab16f 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 fdf93cc5c..c97cc92dd 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 ec2a0e918..1083e2395 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 208423074..003802b99 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 9664030eb..889776e24 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 3a8a5b5b3..3f955eb26 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 f23bc79c4..70c1e381b 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 53a9a178a..b421fad1f 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 a5bedd408..73ce24525 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 47c655a8b..d6ac81233 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 4566ca940..dee6e0763 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 2969751b7..536fe3bc5 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 dbb8c73c3..6654de4c2 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 f2440ae18..0d9865902 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 de7c89766..33bf96b11 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 94f8ebdd9..73ff63feb 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 52207f688..77b7a7f9d 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 8c5898a0a..5332a298d 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 dc02ee4b0..93789cc7c 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 240ce1a78..cb977aa51 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 0f7d4f11f..feca753bf 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 21a05da82..f73102f88 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 0aadf08a6..8b5f4db4c 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 5e40f2376..adc05c58f 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 909e58b0d..dec68db00 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 2d44ca155..26d0bfb5c 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 071/111] 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 992cae5dc..322a27806 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 072/111] 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 de10574b6..308e63de8 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 f7bce6cd1..e1fed859c 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 073/111] 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 308e63de8..de10574b6 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 074/111] 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 09992d985..e9291568b 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 9f4c7afe6..479743b28 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 000000000..a7d30f763 --- /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 b356d96b7..f61bfed04 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 7e3a1549e..e6c30c942 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 075/111] 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 de10574b6..f5f803c8d 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 ac995b2fc9810902b37ab25eb23d44dad54d4911 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 26 Apr 2021 19:55:50 +0530 Subject: [PATCH 076/111] 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 000000000..2a2f34108 --- /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 ef1461802..2ac56b10a 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 000000000..3a61a9c16 --- /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 077/111] 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 2a2f34108..423617c77 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 078/111] 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 f2e8e0fc9..2de3789a7 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 4689e9ec0..28be5ac6e 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 479743b28..e9c219560 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 fa9f6eab1..95145296d 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 423617c77..35fcf51f5 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 8f18d5e28..49a12cb41 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 c93059d56..553ad88b2 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 9751ad6d0..90afbd643 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 b4b2176cf..2d0e0af44 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 6431a3117..d7ece34e6 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 545d5492c..891101e08 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 f5f803c8d..e7ef400a0 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 322a27806..8ead26376 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 7910ab7e1..32e63bdcb 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 8586e1ffc..d6220cf3b 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 ca661dc69..2a2d31f5a 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 5bbdaf394..53303231d 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 28136addd..19d327bd9 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 5b427c02b..91c07435e 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 00aee32ad..1f7414d02 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 099f6798e..5bff63dc4 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 3f955eb26..20c4e327e 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 dee6e0763..3f04711d4 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 26d0bfb5c..8a2752015 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 079/111] 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 28be5ac6e..17e45e937 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 080/111] 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 17e45e937..bf6a3f3f9 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 081/111] 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 35fcf51f5..6cda6f912 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 553ad88b2..944b476f3 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 082/111] 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 891101e08..cf09a1706 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 083/111] 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 3a61a9c16..c73c0d0bd 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 75b59f0b518f0baf2c2a7f90e2359dc62386e7f1 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 28 Apr 2021 15:45:08 +0530 Subject: [PATCH 084/111] 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 6cda6f912..2356c57b5 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 085/111] 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 2026b101f..0432a0b41 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 8b3009c88..57ee471b7 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 3a34dc018..89613b829 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 5bdd54725..825893552 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 18ae5ff8a..b265eef32 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 110cc1dc6..7d732eacf 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 086/111] 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 2de3789a7..f5cc5482f 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 bf6a3f3f9..04bb8ae1c 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 944b476f3..b2a5bbace 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 2d0e0af44..70307936e 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 416f7ba04..c300971de 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 1443ef17b..694c52bc3 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 dcc3252f5..9b33f75cc 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 087/111] 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 ffee617ba..6ae2c732a 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 088/111] 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 6ae2c732a..4114051e7 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 089/111] 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 | 104 ++-- .../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 | 119 ++-- .../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, 2624 insertions(+), 2457 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index f5cc5482f..35989a727 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 04bb8ae1c..12820f8c8 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 1bba82e73..09ea4f58b 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 d7e76e74f..c42aaf5ff 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 d72cecbd3..8dbb70ad7 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 95145296d..98d86539f 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 47f23071f..78e939102 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 1d070230e..1675197de 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 2715b4761..b76c675ca 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 176bbbd3b..0ad06cc07 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 2425b9668..2aa715169 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, - ); - }; - - var failed = failedBuilder; - failed ??= (context, error) { - return _FailedState( - error: error, - messageId: messageId, - attachmentId: attachmentId, - ); - }; - - var success = successBuilder; - success ??= (context) => _SuccessState(); - - var preparing = preparingBuilder; - preparing ??= (context) => _PreparingState(attachmentId: attachmentId); + final inProgress = inProgressBuilder ?? + (context, int sent, int total) { + return _InProgressState( + sent: sent, + total: total, + attachmentId: attachmentId, + ); + }; + + final failed = failedBuilder ?? + (context, error) { + return _FailedState( + error: error, + messageId: messageId, + attachmentId: attachmentId, + ); + }; + + final success = successBuilder ?? (context) => _SuccessState(); + + 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 bd58462da..d80109c25 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 1003c7677..929cddc27 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 8b8bb4c17..cb8d35563 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 aa8d1246a..6ac48f591 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 d2ffc3357..05c511b5d 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 0c8eac7d1..642b8b005 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 f49fa98cb..3d38364a2 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 2f3a26b27..c42186328 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 c2927a6ee..82270e3b0 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 4b3edce35..041e0e036 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 1d6f6fe27..dfcaa749c 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 88b329e69..f3889e223 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 90afbd643..a210501ea 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 { } } + 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; + } + Widget _gridItemBuilder(BuildContext context, int i, List channels) { - var channel = channels[i]; + final channel = channels[i]; - var selected = widget.selectedChannels.contains(channel); + 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 53817563b..486574192 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 959342a60..f28e9368b 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 01632b1ba..620c41ac2 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 20b8151da..3674e0856 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 bd014770f..c03879828 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 daa8a69cf..ef73d9ad6 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 aa35ed155..4956fb28d 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 4cfd655ae..d6ec45e01 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 6833a52fc..a25d1be3f 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 39c2883a2..85e284978 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 023c8eaaf..1cec9c361 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 72a077cc1..deb30877b 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 ddaf2c24a..3ba266bc9 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 7d8b0ada1..37e4e9ad6 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 5cedc962a..6d4bd93cb 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 0fdabf7d2..5d97bdf66 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 70307936e..12ee9e93f 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 9af5bc4ac..f444224d9 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 e21710783..ed9534acd 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 4cf7b5edb..87b55aaa2 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 d7ece34e6..4eff2f8ff 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 3afa60f90..eb80b89b9 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 09feeb746..d39240abf 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 61ce939e1..03af584be 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 4d2b2e387..bc77e97a9 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 51ec7aa53..bcf3127c4 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 99b933282..66e19fe75 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 a33e7b528..7cd01eb73 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 3fca2fa91..68fc5a0d9 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 470591e77..d3a196a1b 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 e54c0fbe6..9b28d1f6d 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 35e989607..95b3c70e6 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 374b9ba16..8dc38ba2d 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 8280bf24b..90146e4c1 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 ac3176ce6..cdb9294fc 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 449d45842..9dc8f98c2 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 8a53c3e8c..aecc3bf18 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 0c30e9a1d..d55cd4f15 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 53c121e2a..ec06de8a1 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 9dc5cdc8f..d9beab201 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 41860a7f1..96753b993 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 cb82e3110..258855d87 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 c300971de..2f0e13b98 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 af2797cae..492baef53 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 8950a77c4..ebb2a22ec 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 30d06c556..a4d0d1ffb 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 80607b8e2..df8373ebd 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 4114051e7..942df52dc 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 1ef02cd90..361d7cfc1 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 b547e80fb..b1eb70e27 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 3ee88ed19..a9c5f7240 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 1a5cb79dd..c284cad52 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 cea48d2f4..1068bb523 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 6fc4d428c..f77ea04a0 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 bf6f7e55b..586a07765 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 d8ed9f72e..b913489ec 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 4856a0d3e..eb5e9b70f 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 bb93b28b7..854c24925 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 55adf7d3d..81c342eb4 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 zcmV;833B%97>gH>Kz|E0Nkl?+5)^_XKt^5#JH-~ISkPKv=@__*N=i|T8&$oHEOAI6uX#qH={9F$JjAfG{ z0kxAr0ZxB#=X!wQw54;azCY5eww^XclTo$ywB>rFQBf7Bl;7i5WuQ{CFRRx4(s{Wa z=@%m}kw~O-J;YE%lTmGGtJj9sdPS4bT#qv*_&t6tty-d`l}qIJ_;Wpuqd7W|h69`Q zMswZiFv`M{bnTB`UDI(?!RRrSM-SzA!qL&;pR9kdf4p{-PtbQ)#T5!3mXrzQ!8xn6 zAjl`m#d2&QuQ%6eL+ksJq$}HkTE3-M!AOr9$``7k{3i8!{Mt4CsvJ)_I%XdGQ6Tc_jB-K{jq6ceM<#;mZaas*Q1RoCCP}MZG2wiQeArKjuVO(#kI6*iSDnyFV~|vila{RHXPWb z4XyQ(q{r5$v@s4XyRs9NwDa$(+S8fkcy0 zz0q7JDYIL(GcZ$6%zZS+Q;mI3%zaeH-BYyUgDH}vm$$FsY~KZNOdzLD<6hYoR81(P z=W14Gw_!T1WM_-|c5PB*^V^Di^Do7Ae5hpSZYk}ky~~UC_}oWS6AJ0dwqWM8_{o1y zVGPbWfwX1*Q#6q=gLA|;>6^n_6-`D}7M`SFq%*6tRRi%} z^?df4(y4^}*DO)N)Hxc6N2L6G?@n8+Rn2uO3zsSsJgm*(t$aU{1xI{>G<7#glCEv>s-b*gR@=n3*DIY$ zC~)%w@?1V!vF-Ib-te-HZ+uD7Z~raN<+BvH`2n4X99E=pts{1~@{7hvl9GR&KU~!I z4dn|pV_%V^Gw&&EjtJz`Z>r{mj;f*jra{-Iy7wu0Xpele7RyyUNr{6y)PG>Ryj2TS zSTRS5@DJ*5|5mQzN%GBFtW@`YrMlZk`eh86J5VY~N`zaKPR1{I`-bwHbVFOWBwaGU zQyVHr1ac^r`N>FW^q3C%O9y{lf8tn3l2lM}jU*{`xJ~)q5*1b7DqqdgOgJTz&svl0 z+A|WP4_WF+yHZE?DV>bVbuy52Dz4bJ4Hx`ey&k{($$m*vEEyZtIe)1faSYPcmkFdi zdPp6<;GpYM$HQ{@Co0cfAW3phxLk?QP9;OTU+`$HIfIcljr3 zAl|EV;^b~bUcP*@f3JUhZ;28IzdvhjIuX-AyjQMqWy3!AP-PQs>PYs-PBV}FsFv|l zB}tWmX%{`$rNj||Oz~v`2^Wvo<1VAywZ=W6 zVwk2KKG~egJ<&7x`*BK1GO9>HCOWPY$&MoesR~p|lD3ykP;Gz57lW>M1xi&owMOy1 zo6p*%N*rub;^3Lno5{`=#rJMj;k4`J3QQUP!H38dC{^LflL+UN?;Dr(^VN2Ip>HZD zN;>nN!sduTW=)(WN&0SjT(uovWwnj(n%m@=GF_3zwTd-;tbu5^is#*};(2##Alj{1 z)5nT5u9eF_QAK|<7meijLsD2Z-+bJCS@ye1wH;q+_Z5AT&b+6vIpQqVg5W&)+&+bZ zht*kJqGjzL4gPIgGeVOOohwkP;_H{I zujTX8yHoi^n}Gxo$Ik2;l;0?QolEF4Zl`TzdOZ z&1&Mf0FDVn%3N2yv}%bY>DfCysy^PV7r%Z=CCNyRCmVZ8k`cZ5^;5c{Z?9gw~Vn{6^JC=UwxkzRotw2QCv^{@r3%lQLTU5xKhj7KhE`NW5%-fkF{>&O7(lA zdhYJ1;ze;Ss<@fsj*H-!W03!M>gEkn=C(g~ZRu5Mq*tGoFH}SMLPajI+x`?MqpI!r zQnz+|sm|gOt^LVue>f<=c#aK3%3MhG_ie8zmdgC3q%1s1*S2^yCv;TBiMXa8+n?hJ zM@MSQ_^FB%c(uK3g1(!6=EiY8w@(k%tY)`r2pk=Vq|?_}zt{Ty=}<0pKDSSIRNbzn zRor@Xs2m^2Pi+owRa1A9T2J1*x~*sIUj#KADg)Ex_xM#6sMM^9v-rdqM!%>)fZ^u2 zvsl1rB9p-Z6_Y>#mXkmMK7T`2fous4s3$SfqX1)%d$0vNkEFHwlcXeR#ca3ko$XSf zV8rJF#)P~?B9XQhtokIWPumAh{~ahG6kIw64P@>=`_+4n-K$ffJb0aJq-Fzh&O8Q5 zx9-u5qd%VWv7UtV=J%-)844IH&Kt;I8~>(1x4xh^KU{gd$326{dsPj#b0h$00000 LNkvXXu0mjf&_V!M delta 2665 zcmV-v3YPVY7wZ_1Kz|GsNkl~;Lss`c&E~ ztwrl=*OhvtFO9NPDwQks5Z5D~j%#m!hxYb$D4vd&dYpM7;0VqYv&(DiS^` zDIdxcYj$Wum_5o|IW~~zd)l?P?+=osJNm=gdT?Cf*qA!&H>$J#K8I(^ zti14vs?u?_4Zf|m!FLo5hO}pWl}S&eQuPN!$Lo zM6HpCcC~EN%YVxsD)D6V!9w4e1$vs&q_0>v~2DGQ;}i!&6FDCbhX~lOAvRPN_$8 z6h~d=?L56-d;2;hNk4okqmM!>_2j)z6UQ}-dxLuN-hZccszT%c7?d|K(f#{(U zPv$I+2_&A5>xG_nN%_O7k3uW;}| zM`LgPM%m=}oM*~Tl_@#qR&vb!xyPpeK08%5=jZ>diS9Qv{?>mMz1_Gss9i1FwDn;A zXPtg6F*`@RlYSt2Nbz)Bb&l`mVPY%(UfTymBB zl{$4q@uMAz9r&H9{^r{%^ZJUKDK_HOxq&w$ zrKBsbDQu1iWco8zYa-{>S%2TG>ocP#m46;NF8`{BdaA1o<1U9(*{*EtWhfZ z7flYlFL!W}{Hq>PX7rRYqXPxcGcm(2duGz5%;dFS(wVj?h9^`jNlHa~l}jg!-oCT` zK7Fl!RFZC)H6M+7pG$bV#{Hhw{80=2WQpSlo{B$YSZB}vMh?U&0}rOKAC%YWax zIUi2d;;YuAhmRLL&rA_~(c4H;=G=fX=T0h@PRe~LkX$CI#NoX~Ki9Z7pg?+3l9Wg% z<}}Z(#u4Wr!xQ;H>f<9C^oM6%pSc*7JFrk?o^nZ&r)H^Ak&l#)d@OI>N>w!8b9rr< zr(Ev9LS+*d3ZADTFs0bp;$FInz<*Rx>m@01_!Ui_K6=f^QbWh&KK7P;&3{|)^L<=> zr<%{7lvK|iq(F|?gA^~P+|-qm;)=$#s<{2H6?^|BrH6Vyzj`WN)JeL(rubj>xolJB zv1gj-eM{-+v7(yt8i!1YoLlOUC)hD8}u6G9(Dw`ZvE_LZJqRb`#>VJQb%U7k; znGdg8n@c5>O^(aGpswI~JQZ10FHIG^zItg&o{DV2>!%eGZRT9z=T0jxe4^g!Ws;=E z(DI_^xfMAgkR|?nAkkp8md1x>UGEKlMUs>}{@#_pEGtvx$_M4GU2)ag={263hN5Pw zX-LTzntiVjT$E8wL*X%fT7Myza;L^-e?6vErQ?c~=cD63k-|74kfuP)v%XRch{Ob_)cdE$U7mfs_HXi3p?)ix*PugT2#cyLih zi~p+lt4-6~p;}d3x`;3nmw!RQ&(}8iw*K6>P|}sx6gEc$vTETfNq^D@E0Ss(e7B%& z{C9mr-X$v(>)NeE_v^~WM-^Q6sDkSrRW?4VMEC28b?ufruuzpNADq)%E_Y6~%}IsA zpMMjudTHvq;+R&^wBCH&b6equN^OJh>e%fQlCHd_usPx?)`svp`8|F`!e=!UtkRZ& z*Jgh=z09S+SN~CoqkpezvcF4{{av41;%nL<-&g)lWp3|v&3D`KNd;;$IyX=umnWy1 zrG*`S6~Y~=Rq&pzn&^G&^5K-La)CUdT6x3GDsNa*(DQ5=cuhmWDn-I)<@fk`7rj7^ zxgX^B$9C)Yy}NZse^^g+4r=S)|GMChxo6|&l|I|2YqTps&q`x zzW0=FpE#jsAI_f-J+|Tz&OM6cs6Zs?@s{uC!G`;ltW4@B|1_mZUtD|kZP%88*GoOx zys>5Ab?w==U6a1Je*S1&$;zZ2Y`CBEjyZ75Iml;Ce}8#{l>geFM-GmwHa4z5)o)a1 z{YJ%Zv9J9JrsHZGd`Dj&d`CmUD((K%*Zy!)K6j1{M9SYt^&f|Sr9>wGk&?RTBHi8V z)0)V61yf0_xNxe(6W$!X)yots_vuJojXqd$<%{F|9>2cVx|74I>)_}>BwfD8`jx&v zTn^<{=Rx=Q^>EWSwYiC}9=%?U59HGhL=UNZv|D|bzP!4Btnd%=Iay<9xdPsRnnI0Q zwQv=Cj4#laDiGj{=D4d^z|BOF@!gYf3JiaSf&w`h$!aWhqelVeAJ1$HK024v&NtGM zq;0D``u1wKLghDnE?{0LOQljdYr&2;()!at_VS-Y<%EJ;$E<;@{jW)lU*qsb_C40VQXigXH>-tNHvV*L-X&CB67z=7tOf%oj5T@^4-Lu7BzKg#Ta>Kp67A;STsHe(>`!|OCWF`}n7x2XNiCsmnAUiJ7-KKO;6|Jq{x_SF9p2)?vt zyo%M<@PPjB^nP`#NBc5fz3}coE1UD_V0fiA3?J4&cv1&j%C)|Bo!S~6xS2BpH_iV8 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 zcmVzE>U({MTEnyBP9wswBms6-`Eov z&y44{2gfe3i{WdJ8SH#s(wxBKpT}>;<9T3W05pisC?53yr~aIyDnWptQfc)m zIzl{=Kq4MTGMPk=VIUr_e&Q>oe{k>KJv@E-G~yA4L$@+5EG%f>_qFf)y6iaGQcCA@ zIc?jvc3oF{o~J(*p66-Tb+v8VI-kpFODSD;oci)vSXgLjSznN;`3JA8tkf4qsaVwc zTuwU&2T{Lo7lw0ip!2z$E)|RQ<+QT067?G%O)!g#i`w^n?RlOq6bia%e_1*Rf~eo4 z3nvHyU9>E1DWyHn)4uQP#l^*_-}LhhW@cul{ufgy6m+RrjQV}K@Jhv^wv?(bw@fAz z^&8*RB;M#XO%re4yn$(&IN09@01ONcN@nrNDiw=}#bUVB*9WB(u3fv8F2Fywy|&E1)$yl#e|u4>R1gFKOw&Xr zlL>i*?Ij9kYHA7q;JGf0RC9m5*=!a7+O{|N**hNq@cCe@E%Og!{6;DT->U{)Tc({t z6ihmu1^{@z55q9TPv$sIeSwdTj*1_Dnqpbw*-mw@^7u$1^!bN1e#0=}ehj9zOgn|> z;Nz7mR{&IZ{U_q`e*@iXYin>E2S9a!JC1|h-CY2{*w~nOe#iagUu6`_)lIVT;W&zA zjURIX0N}>2S`R3!@yB8@Xk8uW(9lrJ<2p~Gjb7icuI^Q#fMMar+Q!BPHa0d+{CPT^ z#+^HNMDsjs>EAAv{^Oqb^RK^1;_rWuZ*$dzJ^tL@9?Z`^e**v{lWjR^pe@eje3)!D z+i+n6t!)#0`_69|3jq9ju=$tT?(ugL(NcW>{(bd9@wn&+^v=$XY*a$u{ja@8!^0lG z@B2u6T&rwvZ-+YXc55z}L;{r{IP>FbpYx4ezkXdZlp}uFJAEu){igZVe%RyJS|b*# zev9ksJ-u67e_H^6o@5fP+xR2ku-;Sl*QC?wmcDgdPBs1t{q+|~jE^+`krej$UDt)t z(*xlD%d}I7mg45-CIFzvFhs$e_Sbjw=1s{ke&(yO#@~ScJR#o&a9G&mcU>3BkHOTI zX{Qh$8p!2x7#bRaZQJM-92(f#+N$p*t*wSfM@KO|e?8sO$5G8cvmT!9RPe)I1^+Eo zU(#=f+<O6hz)&&y2md999JX3~ip zxyPqaE@aDOGFV+*tsi-8_ETI)+uPemF2?VMHOg^q35L2C|33_g zlhOuWll}%Se~~iG(=^*X7U~cU@9~#Tr*)xF(1k)lKX~v!o2D80eFr&3IyL@< zwY9Y)H^O8%pZwJL7nD-Jcn8&5I2Ygra5~@V-a^C@Z12!S+6tN|w*>+!BPCL@+fZQp8Qa0b%;R~k zFm?jF{P@~q20NdZG)f}u$4|!Z*faJ_#sXkKbWX9TCqVs$!ILouHj&V7e&wlO_D-HV60bhfs(pp?Sq<|fwH*Ri^~8ugSyXCb8PR@=6Xxw$#a&CS8K z?J$PydrB!RFE3+xc^OKnsHYQ(P=d*3vsheQY=;sA0esJc)*6ik}RFyd1Ym#y)vqmiYXO~#;w<* ze%>w(w_Z1;V$oD9mG*jCSy_qt36Cb2rKKgKwKl%*n{v5qDvo1MFsYLy}Tzb_?#W+f}*IOo&iTa7}Xc2Gp z+O~~1Z{EPRZPfSo0RUIW#w0_0wyKp1EX%^k=qQv@xN+k~-|an#Ke%h{HaE0}@B6rN z^=dEXHIS~3jluVQe>Ajie+``X5dI)0p{R zdNzDl^LOj@_FL25-d^NyG15i!3X{oXVB0o=AV8zhKx%kca(4hgp-{l??k--xel5Q5 zdE_^Mtv`ULzxHJKuIHZ`9!8_lKoA75Z5x?PCgd6RmMEB+f0-EofbV%orcxa}V4+X| zfS!FCoZo2v`t4Ue89tQxCsQeC-v{tGefc;!ISHWo(SJN9|Drpe&%Ndx-NEi zcL4y`u3Zz4e|Ovu|7k`80O`r*@3($${(0tR*TaW4f6KC9OmpXSe0;q7iCrMkZnf4L z0HDfceD(F$q6b=ATf^Gg+KE3;r_;E9|GsFof$p{m@*Ahe0sz-!e|;a(>-qN+Q84e{zi-|t7888~ z{qEg6*{p=t3OyY@^!aP85kGuY+1}m`b?jd2ESPv4jUYJp{aWwyr@48v^Nozn-_O3y z{l9lSeCYEx#=x>H02u5~dN(&W0RTgZ1U%0>_x*;?_`D{aPItA}VUM2v^X&J{jo?m& zJ%7*he~=s+0`PI2_6t!k>+9y?eLoUF!7Y$BkOo z1DtyE_tW11|Na5E)8#I8nDakyT{MCKsZVGvX9u=z-d&_7V+?B5DuN)ul`B_ZSr#5YevIeOpGQ5lj&=NgmdoXi9t2)0 z8P|2A-c-0W+{5j>PY(jm<#Lg4hjh7gH$dxP@YaT~Qp%J{C7xzdDj9X`G?RYZ$UVM< zaw1zMlR++*Yk%|FI+4xS8paqj4!^VHJeLbzPTc5y_39P=_uFr$UP9PA zIKY!9PcS__ed29s@cgNwOeWL5IQRZxhY!Y4$^=1h 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 d304d48e0..091c64c7a 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 c744a6bbd..8a6e8c33f 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 38e80565d..39e7ae3fb 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 8745cb451..e1136ac5c 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 1d2ac8bec..a8aba86aa 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 50f7ee6a2..842c1c2f7 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 d4a3b6a6e..fd5d31ce4 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 c472df85e..18d7ae55f 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 04967c39a..537418642 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 25987e31d..9e64abb78 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 bbdbeb3e9..8698de91b 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 870182630..9b1b5e1e2 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 5bff63dc4..2cacdb912 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 b85857bbe..8220ee4e2 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 5c4ec5550..71f5b708b 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 3f04711d4..50ff7a7c4 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 090/111] 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 731ef0801..1f3b51d2c 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 0432a0b41..1c833e235 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 1fd53f552..2a720c70d 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 57ee471b7..d1b397744 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 89613b829..fc55f6d95 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 825893552..5a85339f9 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 b265eef32..7a75c0379 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 7d732eacf..c49ffaf1b 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 af8595d56..106517fb4 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 cb8d35563..8e2ede71e 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 3ba266bc9..703fb092f 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 12ee9e93f..c7ddb5f1e 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 d39240abf..1e8049f80 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 091/111] 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 ddc691d0a..c0e141336 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 49a12cb41..c3409b7e3 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 afe384f53..a93166b4f 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 01c06fee1..1e69565c4 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 9bbd446fa..34ecc7503 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 50ff7a7c4..9b5e34704 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 092/111] 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 35989a727..17755b6da 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 0ad06cc07..e26866384 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 929cddc27..90ef92b1b 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 6ac48f591..1ff9b8a9a 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 05c511b5d..4ffbee1b8 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 c03879828..b3071904a 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 4956fb28d..f960b5ee4 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 a25d1be3f..27c92674c 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 85e284978..a08d13de2 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 1cec9c361..6837722cc 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 5d97bdf66..b737bd095 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 c7ddb5f1e..33917de0f 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 f444224d9..8f36baeba 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 ed9534acd..9fa35cb66 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 eb80b89b9..b0099eff1 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 1e8049f80..b05762ba4 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 bc77e97a9..369fb4dba 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 7cd01eb73..cd358b2a1 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 d3a196a1b..d7e3ecea2 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 ec06de8a1..e0e7e306a 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 d9beab201..e8e7b8a85 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 ebb2a22ec..79bd210e7 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 df8373ebd..3d9d5429a 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 361d7cfc1..762d4fe9e 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 8a6e8c33f..0be509ae8 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 093/111] 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 d7e3ecea2..f5461895d 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 094/111] 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 33917de0f..9d944567d 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 095/111] fix download image --- .../lib/src/attachment_actions_modal.dart | 129 +++++++++--------- 1 file changed, 63 insertions(+), 66 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 642b8b005..a91c18df0 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, - ), + 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, - ), + ), + 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 096/111] 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 041e0e036..e24fe08ba 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 d6ec45e01..f39abf59c 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 097/111] 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 369fb4dba..43b198eb6 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 098/111] 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 17755b6da..1674e5eb1 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 12820f8c8..675cf999f 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 a210501ea..6d317d977 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 4eff2f8ff..358623e1b 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 d6220cf3b..6c9be4f02 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 2a2d31f5a..cb8c9387a 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 099/111] 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 36d1fbb7f..370654dc4 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 000000000..617e372a4 --- /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 ef73d9ad6..11ff3253b 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 9d944567d..667848c43 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 942df52dc..ea2086479 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 100/111] 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 e03eba6c4..889ad5182 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 101/111] 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 106517fb4..a0f2b7db9 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 102/111] 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 7b5df2ba6..95f48ee98 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 103/111] update melos config --- melos.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/melos.yaml b/melos.yaml index 6a6c74dc4..3fcad03f4 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 104/111] 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 9d7c4ad54..f43e8a1f6 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 105/111] 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 f43e8a1f6..74cca71da 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 106/111] 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 163f3df19..01c067dab 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 17587cce6..9100e76d4 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 107/111] 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 a0f2b7db9..3feec1442 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 108/111] 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 667848c43..aa14882e5 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 109/111] 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 d6d08a3c6..2e6c3a78b 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 a18852e11..c03f9c04f 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 14f8ca310..6001ecf06 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 efbdfccc3..414f91217 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 74cca71da..6d30567bc 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 68d01d52b..d23fd7d96 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 ed8ff80f4..70bac70ab 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 e49719752..1b5c1dc2e 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 e637ed8ea..ec3eace2e 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 110/111] 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 8b670f7fb..b5bbbb697 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 111/111] 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 6d30567bc..c8c4c4695 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 70bac70ab..031d8711e 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 ec3eace2e..f31855819 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