From e19cc87362cceb06c1ac5dd72cf25c824085d7e3 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 20 Dec 2021 16:11:12 +0530 Subject: [PATCH 01/14] feat(ui): add support for OG Attachment preview. Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/channel.dart | 14 +- .../stream_chat/lib/src/client/client.dart | 14 +- .../lib/src/core/api/message_api.dart | 14 +- .../lib/src/core/models/attachment.dart | 43 ++++ .../lib/src/message_input/message_input.dart | 185 +++++++++++++++--- 5 files changed, 231 insertions(+), 39 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 51034d3a..1925e325 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -493,6 +493,7 @@ class Channel { Future sendMessage( Message message, { bool skipPush = false, + bool skipEnrichUrl = false, }) async { _checkInitialized(); // Cancelling previous completer in case it's called again in the process @@ -540,6 +541,7 @@ class Channel { id!, type, skipPush: skipPush, + skipEnrichUrl: skipEnrichUrl, ); state!.addMessage(response.message); if (cooldown > 0) cooldownStartedAt = DateTime.now(); @@ -556,7 +558,10 @@ class Channel { /// /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. - Future updateMessage(Message message) async { + Future updateMessage( + Message message, { + bool skipEnrichUrl = false, + }) async { final originalMessage = message; // Cancelling previous completer in case it's called again in the process @@ -594,7 +599,10 @@ class Channel { message = await attachmentsUploadCompleter.future; } - final response = await _client.updateMessage(message); + final response = await _client.updateMessage( + message, + skipEnrichUrl: skipEnrichUrl, + ); final m = response.message.copyWith( ownReactions: message.ownReactions, @@ -624,12 +632,14 @@ class Channel { Message message, { Map? set, List? unset, + bool skipEnrichUrl = false, }) async { try { final response = await _client.partialUpdateMessage( message.id, set: set, unset: unset, + skipEnrichUrl: skipEnrichUrl, ); final updatedMessage = response.message.copyWith( diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index b2ae3657..141bad48 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -1169,12 +1169,14 @@ class StreamChatClient { String channelId, String channelType, { bool skipPush = false, + bool skipEnrichUrl = false, }) => _chatApi.message.sendMessage( channelId, channelType, message, skipPush: skipPush, + skipEnrichUrl: skipEnrichUrl, ); /// Lists all the message replies for the [parentId] @@ -1198,8 +1200,14 @@ class StreamChatClient { ); /// Update the given message - Future updateMessage(Message message) => - _chatApi.message.updateMessage(message); + Future updateMessage( + Message message, { + bool skipEnrichUrl = false, + }) => + _chatApi.message.updateMessage( + message, + skipEnrichUrl: skipEnrichUrl, + ); /// Partially update the given [messageId] /// Use [set] to define values to be set @@ -1208,11 +1216,13 @@ class StreamChatClient { String messageId, { Map? set, List? unset, + bool skipEnrichUrl = false, }) => _chatApi.message.partialUpdateMessage( messageId, set: set, unset: unset, + skipEnrichUrl: skipEnrichUrl, ); /// Deletes the given message diff --git a/packages/stream_chat/lib/src/core/api/message_api.dart b/packages/stream_chat/lib/src/core/api/message_api.dart index 8894e14c..20cdfc51 100644 --- a/packages/stream_chat/lib/src/core/api/message_api.dart +++ b/packages/stream_chat/lib/src/core/api/message_api.dart @@ -16,12 +16,14 @@ class MessageApi { String channelType, Message message, { bool skipPush = false, + bool skipEnrichUrl = false, }) async { final response = await _client.post( '/channels/$channelType/$channelId/message', data: { 'message': message, 'skip_push': skipPush, + 'skip_enrich_url': skipEnrichUrl, }, ); return SendMessageResponse.fromJson(response.data); @@ -51,11 +53,15 @@ class MessageApi { /// Updates the given [message] Future updateMessage( - Message message, - ) async { + Message message, { + bool skipEnrichUrl = false, + }) async { final response = await _client.post( '/messages/${message.id}', - data: {'message': message}, + data: { + 'message': message, + 'skip_enrich_url': skipEnrichUrl, + }, ); return UpdateMessageResponse.fromJson(response.data); } @@ -67,12 +73,14 @@ class MessageApi { String messageId, { Map? set, List? unset, + bool skipEnrichUrl = false, }) async { final response = await _client.put( '/messages/$messageId', data: { if (set != null) 'set': set, if (unset != null) 'unset': unset, + 'skip_enrich_url': skipEnrichUrl, }, ); return UpdateMessageResponse.fromJson(response.data); diff --git a/packages/stream_chat/lib/src/core/models/attachment.dart b/packages/stream_chat/lib/src/core/models/attachment.dart index 09fe1083..325dbe11 100644 --- a/packages/stream_chat/lib/src/core/models/attachment.dart +++ b/packages/stream_chat/lib/src/core/models/attachment.dart @@ -2,6 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/models/action.dart'; import 'package:stream_chat/src/core/models/attachment_file.dart'; import 'package:stream_chat/src/core/util/serializer.dart'; @@ -66,6 +67,21 @@ class Attachment extends Equatable { topLevelFields + dbSpecificTopLevelFields, )); + factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) => + Attachment( + type: ogAttachment.type, + title: ogAttachment.title, + titleLink: ogAttachment.titleLink, + text: ogAttachment.text, + imageUrl: ogAttachment.imageUrl, + thumbUrl: ogAttachment.thumbUrl, + authorName: ogAttachment.authorName, + authorLink: ogAttachment.authorLink, + assetUrl: ogAttachment.assetUrl, + ogScrapeUrl: ogAttachment.ogScrapeUrl, + uploadState: const UploadState.success(), + ); + ///The attachment type based on the URL resource. This can be: audio, ///image or video final String? type; @@ -229,6 +245,33 @@ class Attachment extends Equatable { extraData: extraData ?? this.extraData, ); + Attachment merge(Attachment? other) { + if (other == null) return this; + return copyWith( + type: other.type, + titleLink: other.titleLink, + title: other.title, + thumbUrl: other.thumbUrl, + text: other.text, + pretext: other.pretext, + ogScrapeUrl: other.ogScrapeUrl, + imageUrl: other.imageUrl, + footerIcon: other.footerIcon, + footer: other.footer, + fields: other.fields, + fallback: other.fallback, + color: other.color, + authorName: other.authorName, + authorLink: other.authorLink, + authorIcon: other.authorIcon, + assetUrl: other.assetUrl, + actions: other.actions, + file: other.file, + uploadState: other.uploadState, + extraData: other.extraData, + ); + } + @override List get props => [ id, diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index 2ba5c038..46072348 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -354,6 +354,7 @@ class MessageInputState extends State final _imagePicker = ImagePicker(); late final _focusNode = widget.focusNode ?? FocusNode(); bool _inputEnabled = true; + bool get _commandEnabled => _effectiveController.value.command != null; bool _showCommandsOverlay = false; bool _showMentionsOverlay = false; @@ -371,6 +372,7 @@ class MessageInputState extends State _effectiveController.value.status != MessageSendingStatus.sending; RestorableMessageInputController? _controller; + MessageInputController get _effectiveController => widget.messageInputController ?? _controller!.value; @@ -517,6 +519,14 @@ class MessageInputState extends State ), ], ), + ) + else if (_ogAttachment != null) + OGAttachmentPreview( + attachment: _ogAttachment!, + onDismissPreviewPressed: () { + setState(() => _ogAttachment = null); + _focusNode.unfocus(); + }, ), Padding( padding: const EdgeInsets.symmetric(vertical: 8), @@ -538,7 +548,7 @@ class MessageInputState extends State ), ), ); - if (_isEditing) { + if (!_isEditing) { child = Material( elevation: 8, child: child, @@ -896,6 +906,7 @@ class MessageInputState extends State _actionsShrunk = value.isNotEmpty && actionsLength > 1; }); + _checkContainsUrlDebounced.call([value, context]); _checkCommands(value, context); _checkMentions(value, context); _checkEmoji(value, context); @@ -918,14 +929,52 @@ class MessageInputState extends State return context.translations.writeAMessageLabel; } - void _checkEmoji(String s, BuildContext context) { - if (s.isNotEmpty && + Attachment? _ogAttachment; + String? _lastSearchedContainsUrlText; + CancelableOperation? _enrichUrlOperation; + + late final _checkContainsUrlDebounced = debounce( + (String value, BuildContext context) async { + // Cancel the previous operation if it's still running + _enrichUrlOperation?.cancel(); + + // If the text is same as the last time, don't do anything + if (_lastSearchedContainsUrlText == value) return; + _lastSearchedContainsUrlText = value; + + final regex = + RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'); + final matchedUrls = regex.allMatches(value); + if (matchedUrls.isEmpty) return; + + final firstMatchedUrl = matchedUrls.first.group(0)!; + + // If the parsed url matches the ogAttachment url, don't do anything + if (_ogAttachment?.titleLink == firstMatchedUrl) return; + + final client = StreamChat.of(context).client; + + _enrichUrlOperation = CancelableOperation.fromFuture( + client.enrichUrl(firstMatchedUrl).then((ogAttachment) { + final attachment = Attachment.fromOGAttachment(ogAttachment); + setState(() => _ogAttachment = attachment); + }).onError((error, stackTrace) { + // Reset the ogAttachment if there was an error + setState(() => _ogAttachment = null); + if (error != null) { + widget.onError?.call(error, stackTrace); + } + }), + ); + }, + const Duration(seconds: 1), + ); + + void _checkEmoji(String value, BuildContext context) { + if (value.isNotEmpty && _effectiveController.baseOffset > 0 && _effectiveController.text - .substring( - 0, - _effectiveController.baseOffset, - ) + .substring(0, _effectiveController.baseOffset) .contains(':')) { final textToSelection = _effectiveController.text.substring( 0, @@ -941,14 +990,11 @@ class MessageInputState extends State } } - void _checkMentions(String s, BuildContext context) { - if (s.isNotEmpty && + void _checkMentions(String value, BuildContext context) { + if (value.isNotEmpty && _effectiveController.baseOffset > 0 && _effectiveController.text - .substring( - 0, - _effectiveController.baseOffset, - ) + .substring(0, _effectiveController.baseOffset) .split(' ') .last .contains('@')) { @@ -964,11 +1010,11 @@ class MessageInputState extends State } } - void _checkCommands(String s, BuildContext context) { - if (s.startsWith('/')) { + void _checkCommands(String value, BuildContext context) { + if (value.startsWith('/')) { final allCommands = StreamChannel.of(context).channel.config?.commands; final command = - allCommands?.firstWhereOrNull((it) => it.name == s.substring(1)); + allCommands?.firstWhereOrNull((it) => it.name == value.substring(1)); if (command != null) { return _setCommand(command); } else if (!_showCommandsOverlay) { @@ -1030,10 +1076,7 @@ class MessageInputState extends State } final splits = _effectiveController.text - .substring( - 0, - _effectiveController.selectionStart, - ) + .substring(0, _effectiveController.selectionStart) .split('@'); final query = splits.last.toLowerCase(); @@ -1082,10 +1125,7 @@ class MessageInputState extends State } final splits = _effectiveController.text - .substring( - 0, - _effectiveController.baseOffset, - ) + .substring(0, _effectiveController.baseOffset) .split(':'); final query = splits.last.toLowerCase(); @@ -1222,11 +1262,7 @@ class MessageInputState extends State focusElevation: 0, hoverElevation: 0, onPressed: () { - _effectiveController.value = _effectiveController.value.copyWith( - attachments: _effectiveController.attachments - .where((it) => it.id != attachment.id) - .toList(), - ); + _effectiveController.removeAttachmentById(attachment.id); }, fillColor: _streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), @@ -1572,10 +1608,19 @@ class MessageInputState extends State Future sendMessage() async { var message = _effectiveController.value; + // Add ogAttachment if present + final skipEnrichUrl = _ogAttachment == null; + if (!skipEnrichUrl) { + message = message.copyWith( + attachments: [...message.attachments, _ogAttachment!], + ); + } + var shouldKeepFocus = widget.shouldKeepFocusAfterMessage; shouldKeepFocus ??= !_commandEnabled; + _ogAttachment = null; _effectiveController.reset(); if (widget.preMessageSending != null) { @@ -1590,10 +1635,16 @@ class MessageInputState extends State try { Future sendingFuture; - if (!_isEditing) { - sendingFuture = channel.sendMessage(message); + if (_isEditing) { + sendingFuture = channel.updateMessage( + message, + skipEnrichUrl: skipEnrichUrl, + ); } else { - sendingFuture = channel.updateMessage(message); + sendingFuture = channel.sendMessage( + message, + skipEnrichUrl: skipEnrichUrl, + ); } if (shouldKeepFocus) { @@ -1702,3 +1753,73 @@ class MessageInputState extends State super.didChangeDependencies(); } } + +class OGAttachmentPreview extends StatelessWidget { + const OGAttachmentPreview({ + Key? key, + required this.attachment, + this.onDismissPreviewPressed, + }) : super(key: key); + + final Attachment attachment; + final VoidCallback? onDismissPreviewPressed; + + @override + Widget build(BuildContext context) { + final chatTheme = StreamChatTheme.of(context); + final textTheme = chatTheme.textTheme; + final colorTheme = chatTheme.colorTheme; + + final attachmentTitle = attachment.title; + final attachmentText = attachment.text; + + return Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + Icons.link, + color: colorTheme.accentPrimary, + ), + ), + Expanded( + child: Container( + decoration: BoxDecoration( + border: Border( + left: BorderSide( + color: colorTheme.accentPrimary, + width: 2, + ), + ), + ), + padding: const EdgeInsets.only(left: 6), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (attachmentTitle != null) + Text( + attachmentTitle.trim(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textTheme.body.copyWith(fontWeight: FontWeight.w700), + ), + if (attachmentText != null) + Text( + attachmentText, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: textTheme.body.copyWith(fontWeight: FontWeight.w400), + ), + ], + ), + ), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.closeSmall(), + onPressed: onDismissPreviewPressed, + ), + ], + ); + } +} From 50e9cb38e9033a95ee5f27baffc4dc1ec5f645cc Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 20 Dec 2021 16:40:58 +0530 Subject: [PATCH 02/14] chore(ui): minor fixes Signed-off-by: xsahil03x --- .../lib/src/message_input/message_input.dart | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index 46072348..a28754d3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -942,10 +942,15 @@ class MessageInputState extends State if (_lastSearchedContainsUrlText == value) return; _lastSearchedContainsUrlText = value; - final regex = - RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'); - final matchedUrls = regex.allMatches(value); - if (matchedUrls.isEmpty) return; + final matchedUrls = + RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+') + .allMatches(value); + + // Reset the og attachment if the text doesn't contain any url + if (matchedUrls.isEmpty) { + setState(() => _ogAttachment = null); + return; + } final firstMatchedUrl = matchedUrls.first.group(0)!; @@ -955,19 +960,20 @@ class MessageInputState extends State final client = StreamChat.of(context).client; _enrichUrlOperation = CancelableOperation.fromFuture( - client.enrichUrl(firstMatchedUrl).then((ogAttachment) { + client.enrichUrl(firstMatchedUrl), + ).then( + (ogAttachment) { final attachment = Attachment.fromOGAttachment(ogAttachment); setState(() => _ogAttachment = attachment); - }).onError((error, stackTrace) { + }, + onError: (error, stackTrace) { // Reset the ogAttachment if there was an error setState(() => _ogAttachment = null); - if (error != null) { - widget.onError?.call(error, stackTrace); - } - }), + widget.onError?.call(error, stackTrace); + }, ); }, - const Duration(seconds: 1), + const Duration(milliseconds: 650), ); void _checkEmoji(String value, BuildContext context) { From 56e030f40c83ab8cb86f9e0ac88ce657e72ececf Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 20 Dec 2021 18:25:56 +0530 Subject: [PATCH 03/14] feat(ui, core): handle ogAttachment modification via message_input_controller.dart. Signed-off-by: xsahil03x --- .../lib/src/message_input/message_input.dart | 47 +++--------- .../lib/src/message_input_controller.dart | 64 +++++++++++++---- .../src/message_text_field_controller.dart | 72 +++++++++++++++++++ 3 files changed, 133 insertions(+), 50 deletions(-) create mode 100644 packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index a28754d3..7e02e4b3 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -335,17 +335,6 @@ class MessageInput extends StatefulWidget { @override MessageInputState createState() => MessageInputState(); - - /// Use this method to get the current [StreamChatState] instance - static MessageInputState of(BuildContext context) { - MessageInputState? messageInputState; - messageInputState = context.findAncestorStateOfType(); - assert( - messageInputState != null, - 'You must have a MessageInput widget as ancestor of your widget tree', - ); - return messageInputState!; - } } /// State of [MessageInput] @@ -520,11 +509,11 @@ class MessageInputState extends State ], ), ) - else if (_ogAttachment != null) + else if (_effectiveController.ogAttachment != null) OGAttachmentPreview( - attachment: _ogAttachment!, + attachment: _effectiveController.ogAttachment!, onDismissPreviewPressed: () { - setState(() => _ogAttachment = null); + _effectiveController.clearOGAttachment(); _focusNode.unfocus(); }, ), @@ -929,7 +918,6 @@ class MessageInputState extends State return context.translations.writeAMessageLabel; } - Attachment? _ogAttachment; String? _lastSearchedContainsUrlText; CancelableOperation? _enrichUrlOperation; @@ -948,14 +936,16 @@ class MessageInputState extends State // Reset the og attachment if the text doesn't contain any url if (matchedUrls.isEmpty) { - setState(() => _ogAttachment = null); + _effectiveController.clearOGAttachment(); return; } final firstMatchedUrl = matchedUrls.first.group(0)!; // If the parsed url matches the ogAttachment url, don't do anything - if (_ogAttachment?.titleLink == firstMatchedUrl) return; + if (_effectiveController.ogAttachment?.titleLink == firstMatchedUrl) { + return; + } final client = StreamChat.of(context).client; @@ -964,11 +954,11 @@ class MessageInputState extends State ).then( (ogAttachment) { final attachment = Attachment.fromOGAttachment(ogAttachment); - setState(() => _ogAttachment = attachment); + _effectiveController.setOGAttachment(attachment); }, onError: (error, stackTrace) { // Reset the ogAttachment if there was an error - setState(() => _ogAttachment = null); + _effectiveController.clearOGAttachment(); widget.onError?.call(error, stackTrace); }, ); @@ -1470,14 +1460,6 @@ class MessageInputState extends State } } - /// Add an attachment to the sending message - /// Use this to add custom type attachments - /// - /// Note: Only meant to be used from outside the state. - void addAttachment(Attachment attachment) { - _addAttachments([attachment]); - } - /// Adds an attachment to the [messageInputController.attachments] map void _addAttachments(Iterable attachments) { final limit = widget.attachmentLimit; @@ -1612,21 +1594,14 @@ class MessageInputState extends State /// Sends the current message Future sendMessage() async { - var message = _effectiveController.value; + final skipEnrichUrl = _effectiveController.ogAttachment == null; - // Add ogAttachment if present - final skipEnrichUrl = _ogAttachment == null; - if (!skipEnrichUrl) { - message = message.copyWith( - attachments: [...message.attachments, _ogAttachment!], - ); - } + var message = _effectiveController.value; var shouldKeepFocus = widget.shouldKeepFocusAfterMessage; shouldKeepFocus ??= !_commandEnabled; - _ogAttachment = null; _effectiveController.reset(); if (widget.preMessageSending != null) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart index 29bd2db2..f218b9ca 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart @@ -1,8 +1,9 @@ import 'dart:convert'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/message_text_field_controller.dart'; /// A value listenable builder related to a [Message]. /// @@ -17,33 +18,46 @@ class MessageInputController extends ValueNotifier { /// message. factory MessageInputController({ Message? message, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: message ?? Message(), + textPatternStyle: textPatternStyle, ); /// Creates a controller for an editable text field from an initial [text]. - factory MessageInputController.fromText(String? text) => + factory MessageInputController.fromText( + String? text, { + Map? textPatternStyle, + }) => MessageInputController._( initialMessage: Message(text: text), + textPatternStyle: textPatternStyle, ); /// Creates a controller for an editable text field from initial /// [attachments]. factory MessageInputController.fromAttachments( - List attachments, - ) => + List attachments, { + Map? textPatternStyle, + }) => MessageInputController._( initialMessage: Message(attachments: attachments), + textPatternStyle: textPatternStyle, ); MessageInputController._({ required Message initialMessage, - }) : _textEditingController = - TextEditingController.fromValue(TextEditingValue( - text: initialMessage.text ?? '', - composing: TextRange.collapsed(initialMessage.text?.length ?? 0), - )), + Map? textPatternStyle, + }) : _textEditingController = MessageTextFieldController.fromValue( + initialMessage.text == null + ? const TextEditingValue() + : TextEditingValue( + text: initialMessage.text!, + composing: TextRange.collapsed(initialMessage.text!.length), + ), + textPatternStyle: textPatternStyle, + ), _initialMessage = initialMessage, super(initialMessage) { addListener(_textEditingSyncer); @@ -73,8 +87,9 @@ class MessageInputController extends ValueNotifier { Message get message => value; /// Returns the controller of the text field linked to this controller. - TextEditingController get textEditingController => _textEditingController; - final TextEditingController _textEditingController; + MessageTextFieldController get textEditingController => + _textEditingController; + final MessageTextFieldController _textEditingController; /// Returns the text of the message. String get text => _textEditingController.text; @@ -174,6 +189,28 @@ class MessageInputController extends ValueNotifier { attachments = []; } + // Only used to store the value locally in order to remove it if we call + // [clearOGAttachment] or [setOGAttachment] again. + Attachment? _ogAttachment; + + /// Returns the og attachment of the message if set + Attachment? get ogAttachment => + attachments.firstWhereOrNull((it) => it.id == _ogAttachment?.id); + + /// Sets the og attachment in the message. + void setOGAttachment(Attachment attachment) { + attachments = [...attachments] + ..remove(_ogAttachment) + ..insert(0, attachment); + _ogAttachment = attachment; + } + + /// Removes the og attachment. + void clearOGAttachment() { + attachments = [...attachments]..remove(_ogAttachment); + _ogAttachment = null; + } + /// Returns the list of mentioned users in the message. List get mentionedUsers => value.mentionedUsers; @@ -220,9 +257,8 @@ class MessageInputController extends ValueNotifier { /// Sets the [value] to the initial [Message] value. void reset({bool resetId = true}) { if (resetId) { - _initialMessage = _initialMessage.copyWith( - id: const Uuid().v4(), - ); + final newId = const Uuid().v4(); + _initialMessage = _initialMessage.copyWith(id: newId); } value = _initialMessage; } diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart new file mode 100644 index 00000000..82a60709 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; + +class MessageTextFieldController extends TextEditingController { + MessageTextFieldController({ + String? text, + this.textPatternStyle, + }) : super(text: text); + + MessageTextFieldController.fromValue( + TextEditingValue? value, { + this.textPatternStyle, + }) : super.fromValue(value); + + final Map? textPatternStyle; + + /// + @override + TextSpan buildTextSpan({ + required BuildContext context, + TextStyle? style, + required bool withComposing, + }) { + final pattern = textPatternStyle; + if (pattern == null) { + return super.buildTextSpan( + context: context, + style: style, + withComposing: withComposing, + ); + } + + return TextSpan(text: text, style: style).splitMapJoin( + RegExp(pattern.keys.map((it) => it.pattern).join('|')), + onMatch: (match) { + final text = match[0]!; + final key = pattern.keys.firstWhere((it) => it.hasMatch(text)); + return TextSpan( + text: text, + style: pattern[key], + ); + }, + ); + } +} + +extension _TextSpanX on TextSpan { + TextSpan splitMapJoin( + Pattern pattern, { + TextSpan Function(Match)? onMatch, + TextSpan Function(TextSpan)? onNonMatch, + }) { + final children = []; + + toPlainText().splitMapJoin( + pattern, + onMatch: (match) { + final span = TextSpan(text: match.group(0), style: style); + final updated = onMatch?.call(match); + children.add(updated ?? span); + return span.toPlainText(); + }, + onNonMatch: (text) { + final span = TextSpan(text: text, style: style); + final updatedSpan = onNonMatch?.call(span); + children.add(updatedSpan ?? span); + return span.toPlainText(); + }, + ); + + return TextSpan(style: style, children: children); + } +} From 8b6c732844c31ffa09e01ea91eeb4e02422fd607 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 21 Dec 2021 14:11:23 +0530 Subject: [PATCH 04/14] feat(ui, core): minor fixes and improvements Signed-off-by: xsahil03x --- .../lib/src/message_input/message_input.dart | 112 ++++++++++-------- .../lib/src/message_input_controller.dart | 9 +- 2 files changed, 68 insertions(+), 53 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index 7e02e4b3..be63b10b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -895,7 +895,7 @@ class MessageInputState extends State _actionsShrunk = value.isNotEmpty && actionsLength > 1; }); - _checkContainsUrlDebounced.call([value, context]); + _checkContainsUrl(value, context); _checkCommands(value, context); _checkMentions(value, context); _checkEmoji(value, context); @@ -920,52 +920,65 @@ class MessageInputState extends State String? _lastSearchedContainsUrlText; CancelableOperation? _enrichUrlOperation; - - late final _checkContainsUrlDebounced = debounce( - (String value, BuildContext context) async { - // Cancel the previous operation if it's still running - _enrichUrlOperation?.cancel(); - - // If the text is same as the last time, don't do anything - if (_lastSearchedContainsUrlText == value) return; - _lastSearchedContainsUrlText = value; - - final matchedUrls = - RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+') - .allMatches(value); - - // Reset the og attachment if the text doesn't contain any url - if (matchedUrls.isEmpty) { - _effectiveController.clearOGAttachment(); - return; - } - - final firstMatchedUrl = matchedUrls.first.group(0)!; - - // If the parsed url matches the ogAttachment url, don't do anything - if (_effectiveController.ogAttachment?.titleLink == firstMatchedUrl) { - return; - } - - final client = StreamChat.of(context).client; - - _enrichUrlOperation = CancelableOperation.fromFuture( - client.enrichUrl(firstMatchedUrl), - ).then( - (ogAttachment) { - final attachment = Attachment.fromOGAttachment(ogAttachment); - _effectiveController.setOGAttachment(attachment); - }, - onError: (error, stackTrace) { - // Reset the ogAttachment if there was an error - _effectiveController.clearOGAttachment(); - widget.onError?.call(error, stackTrace); - }, - ); - }, - const Duration(milliseconds: 650), + final _urlRegex = RegExp( + r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+', ); + void _checkContainsUrl(String value, BuildContext context) async { + // Cancel the previous operation if it's still running + _enrichUrlOperation?.cancel(); + + // If the text is same as the last time, don't do anything + if (_lastSearchedContainsUrlText == value) return; + _lastSearchedContainsUrlText = value; + + final matchedUrls = _urlRegex.allMatches(value); + + // Reset the og attachment if the text doesn't contain any url + if (matchedUrls.isEmpty) { + _effectiveController.clearOGAttachment(); + return; + } + + final firstMatchedUrl = matchedUrls.first.group(0)!; + + // If the parsed url matches the ogAttachment url, don't do anything + if (_effectiveController.ogAttachment?.titleLink == firstMatchedUrl) { + return; + } + + final client = StreamChat.of(context).client; + + _enrichUrlOperation = CancelableOperation.fromFuture( + _enrichUrl(firstMatchedUrl, client), + ).then( + (ogAttachment) { + final attachment = Attachment.fromOGAttachment(ogAttachment); + _effectiveController.setOGAttachment(attachment); + }, + onError: (error, stackTrace) { + // Reset the ogAttachment if there was an error + _effectiveController.clearOGAttachment(); + widget.onError?.call(error, stackTrace); + }, + ); + } + + final _ogAttachmentCache = {}; + + Future _enrichUrl( + String url, + StreamChatClient client, + ) async { + var response = _ogAttachmentCache[url]; + if (response == null) { + final client = StreamChat.of(context).client; + response = await client.enrichUrl(url); + _ogAttachmentCache[url] = response; + } + return response; + } + void _checkEmoji(String value, BuildContext context) { if (value.isNotEmpty && _effectiveController.baseOffset > 0 && @@ -1169,11 +1182,14 @@ class MessageInputState extends State } Widget _buildAttachments() { - if (_effectiveController.attachments.isEmpty) return const Offstage(); - final fileAttachments = _effectiveController.attachments + final nonOGAttachments = _effectiveController.attachments.where( + (it) => it.titleLink == null, + ); + if (nonOGAttachments.isEmpty) return const Offstage(); + final fileAttachments = nonOGAttachments .where((it) => it.type == 'file') .toList(growable: false); - final remainingAttachments = _effectiveController.attachments + final remainingAttachments = nonOGAttachments .where((it) => it.type != 'file') .toList(growable: false); return Column( diff --git a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart index f218b9ca..6af8db6e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart @@ -66,10 +66,7 @@ class MessageInputController extends ValueNotifier { void _textEditingSyncer() { final cleanText = value.command == null ? value.text - : value.text?.replaceFirst( - '/${value.command} ', - '', - ); + : value.text?.replaceFirst('/${value.command} ', ''); if (cleanText != _textEditingController.text) { final previousOffset = _textEditingController.value.selection.start; @@ -207,7 +204,9 @@ class MessageInputController extends ValueNotifier { /// Removes the og attachment. void clearOGAttachment() { - attachments = [...attachments]..remove(_ogAttachment); + if (_ogAttachment != null) { + removeAttachment(_ogAttachment!); + } _ogAttachment = null; } From 8278e1f7452dddfebfb12d791a7e6bbc31848fe7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Dec 2021 11:42:08 +0100 Subject: [PATCH 05/14] fix analysis --- .../lib/src/message_input/message_input.dart | 7 ++++++- .../lib/src/message_text_field_controller.dart | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index be63b10b..d0d5e882 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -1751,14 +1751,19 @@ class MessageInputState extends State } } +/// Preview of an Open Graph attachment. class OGAttachmentPreview extends StatelessWidget { + /// Returns a new instance of [OGAttachmentPreview] const OGAttachmentPreview({ Key? key, required this.attachment, this.onDismissPreviewPressed, }) : super(key: key); + /// The attachment to be rendered. final Attachment attachment; + + /// Called when the dismiss button is pressed. final VoidCallback? onDismissPreviewPressed; @override @@ -1773,7 +1778,7 @@ class OGAttachmentPreview extends StatelessWidget { return Row( children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8), child: Icon( Icons.link, color: colorTheme.accentPrimary, diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart index 82a60709..30065ff4 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart @@ -1,19 +1,24 @@ import 'package:flutter/material.dart'; +/// Controller for the [StreamTextField] widget. class MessageTextFieldController extends TextEditingController { + /// Returns a new MessageTextFieldController MessageTextFieldController({ String? text, this.textPatternStyle, }) : super(text: text); + /// Returns a new MessageTextFieldController with the given text [value]. MessageTextFieldController.fromValue( TextEditingValue? value, { this.textPatternStyle, }) : super.fromValue(value); + /// A map of style to apply to the text matching the RegExp patterns. final Map? textPatternStyle; - /// + /// Builds a [TextSpan] from the current text, + /// highlighting the matches for [textPatternStyle]. @override TextSpan buildTextSpan({ required BuildContext context, From e85b1a8b3f7f24417e08b12c348fb1207024e602 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Dec 2021 15:19:27 +0100 Subject: [PATCH 06/14] move message input controllers to ui --- .../lib/src/message_input}/message_input_controller.dart | 3 +-- .../lib/src/message_input}/message_text_field_controller.dart | 0 .../lib/src/message_input/stream_message_text_field.dart | 2 +- packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 2 ++ .../test/src/message_input}/message_input_controller_test.dart | 2 +- .../stream_chat_flutter_core/lib/stream_chat_flutter_core.dart | 1 - 6 files changed, 5 insertions(+), 5 deletions(-) rename packages/{stream_chat_flutter_core/lib/src => stream_chat_flutter/lib/src/message_input}/message_input_controller.dart (98%) rename packages/{stream_chat_flutter_core/lib/src => stream_chat_flutter/lib/src/message_input}/message_text_field_controller.dart (100%) rename packages/{stream_chat_flutter_core/test => stream_chat_flutter/test/src/message_input}/message_input_controller_test.dart (82%) diff --git a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart similarity index 98% rename from packages/stream_chat_flutter_core/lib/src/message_input_controller.dart rename to packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart index 6af8db6e..45599239 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_input_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart @@ -2,8 +2,7 @@ import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/src/message_text_field_controller.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A value listenable builder related to a [Message]. /// diff --git a/packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart similarity index 100% rename from packages/stream_chat_flutter_core/lib/src/message_text_field_controller.dart rename to packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart index 61466aaf..8e4a34eb 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_text_field.dart @@ -8,7 +8,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; export 'package:flutter/services.dart' show diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index a4e9d715..ad3f968d 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -25,6 +25,8 @@ export 'src/mention_tile.dart'; export 'src/message_action.dart'; export 'src/message_input/countdown_button.dart'; export 'src/message_input/message_input.dart'; +export 'src/message_input/message_input_controller.dart'; +export 'src/message_input/message_text_field_controller.dart'; export 'src/message_input/stream_attachment_picker.dart'; export 'src/message_input/stream_message_send_button.dart'; export 'src/message_input/stream_message_text_field.dart'; diff --git a/packages/stream_chat_flutter_core/test/message_input_controller_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_controller_test.dart similarity index 82% rename from packages/stream_chat_flutter_core/test/message_input_controller_test.dart rename to packages/stream_chat_flutter/test/src/message_input/message_input_controller_test.dart index 5e7934bd..1b496784 100644 --- a/packages/stream_chat_flutter_core/test/message_input_controller_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_controller_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() { testWidgets( diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index bf191ace..8bd41c76 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -7,7 +7,6 @@ export 'src/better_stream_builder.dart'; export 'src/channel_list_core.dart' hide ChannelListCoreState; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; -export 'src/message_input_controller.dart'; export 'src/message_list_core.dart' hide MessageListCoreState; export 'src/message_search_bloc.dart'; export 'src/message_search_list_core.dart' hide MessageSearchListCoreState; From fbf8694f582d277da6ead368e3408d20dbc18d51 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 Jan 2022 09:20:47 +0100 Subject: [PATCH 07/14] use a function for textPatternStyle and add default to theme --- .../message_input_controller.dart | 8 ++++---- .../message_text_field_controller.dart | 18 ++++++++++++++---- .../lib/src/stream_chat_theme.dart | 1 + .../lib/src/theme/message_input_theme.dart | 18 +++++++++++++++--- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart index 45599239..185c875c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input_controller.dart @@ -17,7 +17,7 @@ class MessageInputController extends ValueNotifier { /// message. factory MessageInputController({ Message? message, - Map? textPatternStyle, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: message ?? Message(), @@ -27,7 +27,7 @@ class MessageInputController extends ValueNotifier { /// Creates a controller for an editable text field from an initial [text]. factory MessageInputController.fromText( String? text, { - Map? textPatternStyle, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: Message(text: text), @@ -38,7 +38,7 @@ class MessageInputController extends ValueNotifier { /// [attachments]. factory MessageInputController.fromAttachments( List attachments, { - Map? textPatternStyle, + Map? textPatternStyle, }) => MessageInputController._( initialMessage: Message(attachments: attachments), @@ -47,7 +47,7 @@ class MessageInputController extends ValueNotifier { MessageInputController._({ required Message initialMessage, - Map? textPatternStyle, + Map? textPatternStyle, }) : _textEditingController = MessageTextFieldController.fromValue( initialMessage.text == null ? const TextEditingValue() diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart index 30065ff4..12ec4831 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart @@ -1,4 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// A function that takes a [BuildContext] and returns a [TextStyle]. +typedef TextStyleBuilder = TextStyle Function(BuildContext context); /// Controller for the [StreamTextField] widget. class MessageTextFieldController extends TextEditingController { @@ -15,7 +19,7 @@ class MessageTextFieldController extends TextEditingController { }) : super.fromValue(value); /// A map of style to apply to the text matching the RegExp patterns. - final Map? textPatternStyle; + final Map? textPatternStyle; /// Builds a [TextSpan] from the current text, /// highlighting the matches for [textPatternStyle]. @@ -25,8 +29,14 @@ class MessageTextFieldController extends TextEditingController { TextStyle? style, required bool withComposing, }) { - final pattern = textPatternStyle; - if (pattern == null) { + final pattern = textPatternStyle ?? + { + RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'): + (context) => TextStyle( + color: MessageInputTheme.of(context).linkHighlightColor, + ), + }; + if (pattern.isEmpty) { return super.buildTextSpan( context: context, style: style, @@ -41,7 +51,7 @@ class MessageTextFieldController extends TextEditingController { final key = pattern.keys.firstWhere((it) => it.hasMatch(text)); return TextSpan( text: text, - style: pattern[key], + style: pattern[key]?.call(context), ); }, ); 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 f3818b2e..8a210b96 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -259,6 +259,7 @@ class StreamChatThemeData { sendButtonIdleColor: colorTheme.disabled, inputBackgroundColor: colorTheme.barsBg, inputTextStyle: textTheme.body, + linkHighlightColor: colorTheme.accentPrimary, idleBorderGradient: LinearGradient( colors: [ colorTheme.disabled, diff --git a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart index 26a995a9..1348af37 100644 --- a/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart +++ b/packages/stream_chat_flutter/lib/src/theme/message_input_theme.dart @@ -65,6 +65,7 @@ class MessageInputThemeData with Diagnosticable { this.idleBorderGradient, this.borderRadius, this.expandButtonColor, + this.linkHighlightColor, }); /// Duration of the [MessageInput] send button animation @@ -73,6 +74,9 @@ class MessageInputThemeData with Diagnosticable { /// Background color of [MessageInput] send button final Color? sendButtonColor; + /// Color of a link + final Color? linkHighlightColor; + /// Background color of [MessageInput] action buttons final Color? actionButtonColor; @@ -110,6 +114,7 @@ class MessageInputThemeData with Diagnosticable { Color? actionButtonColor, Color? sendButtonColor, Color? actionButtonIdleColor, + Color? linkHighlightColor, Color? sendButtonIdleColor, Color? expandButtonColor, TextStyle? inputTextStyle, @@ -133,6 +138,7 @@ class MessageInputThemeData with Diagnosticable { activeBorderGradient: activeBorderGradient ?? this.activeBorderGradient, idleBorderGradient: idleBorderGradient ?? this.idleBorderGradient, borderRadius: borderRadius ?? this.borderRadius, + linkHighlightColor: linkHighlightColor ?? this.linkHighlightColor, ); /// Linearly interpolate from one [MessageInputThemeData] to another. @@ -161,6 +167,8 @@ class MessageInputThemeData with Diagnosticable { Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t), sendAnimationDuration: a.sendAnimationDuration, inputDecoration: a.inputDecoration, + linkHighlightColor: + Color.lerp(a.linkHighlightColor, b.linkHighlightColor, t), ); /// Merges [this] [MessageInputThemeData] with the [other] @@ -181,6 +189,7 @@ class MessageInputThemeData with Diagnosticable { idleBorderGradient: other.idleBorderGradient, borderRadius: other.borderRadius, expandButtonColor: other.expandButtonColor, + linkHighlightColor: other.linkHighlightColor, ); } @@ -200,7 +209,8 @@ class MessageInputThemeData with Diagnosticable { inputDecoration == other.inputDecoration && idleBorderGradient == other.idleBorderGradient && activeBorderGradient == other.activeBorderGradient && - borderRadius == other.borderRadius; + borderRadius == other.borderRadius && + linkHighlightColor == other.linkHighlightColor; @override int get hashCode => @@ -215,7 +225,8 @@ class MessageInputThemeData with Diagnosticable { inputDecoration.hashCode ^ idleBorderGradient.hashCode ^ activeBorderGradient.hashCode ^ - borderRadius.hashCode; + borderRadius.hashCode ^ + linkHighlightColor.hashCode; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { @@ -232,6 +243,7 @@ class MessageInputThemeData with Diagnosticable { ..add(DiagnosticsProperty('activeBorderGradient', activeBorderGradient)) ..add(DiagnosticsProperty('idleBorderGradient', idleBorderGradient)) ..add(DiagnosticsProperty('borderRadius', borderRadius)) - ..add(ColorProperty('expandButtonColor', expandButtonColor)); + ..add(ColorProperty('expandButtonColor', expandButtonColor)) + ..add(ColorProperty('linkHighlightColor', linkHighlightColor)); } } From dbcdd5aea6e59adb006d03c30691f268ee757681 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 Jan 2022 09:34:19 +0100 Subject: [PATCH 08/14] fix tests --- .../test/src/core/api/message_api_test.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/test/src/core/api/message_api_test.dart b/packages/stream_chat/test/src/core/api/message_api_test.dart index e89491a2..ed7fefd6 100644 --- a/packages/stream_chat/test/src/core/api/message_api_test.dart +++ b/packages/stream_chat/test/src/core/api/message_api_test.dart @@ -32,6 +32,7 @@ void main() { data: { 'message': message, 'skip_push': false, + 'skip_enrich_url': false, }, )).thenAnswer((_) async => successResponse(path, data: { 'message': message.toJson(), @@ -58,6 +59,7 @@ void main() { data: { 'message': message, 'skip_push': true, + 'skip_enrich_url': false, }, )).thenAnswer((_) async => successResponse(path, data: { 'message': message.toJson(), @@ -137,7 +139,10 @@ void main() { when(() => client.post( path, - data: {'message': message}, + data: { + 'message': message, + 'skip_enrich_url': false, + }, )).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -162,7 +167,11 @@ void main() { when(() => client.put( path, - data: {'set': set, 'unset': unset}, + data: { + 'set': set, + 'unset': unset, + 'skip_enrich_url': false, + }, )).thenAnswer( (_) async => successResponse(path, data: {'message': message.toJson()}), ); @@ -180,7 +189,11 @@ void main() { verify(() => client.put( path, - data: {'set': set, 'unset': unset}, + data: { + 'set': set, + 'unset': unset, + 'skip_enrich_url': false, + }, )).called(1); verifyNoMoreInteractions(client); }); From 504e72deca272a9a7f941c8bb9aed93448a1ec53 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 3 Jan 2022 16:11:46 +0100 Subject: [PATCH 09/14] add TLD validity check --- .../lib/src/message_input/message_input.dart | 4 +- .../message_text_field_controller.dart | 20 +- .../lib/src/message_input/tld.dart | 1498 +++++++++++++++++ 3 files changed, 1516 insertions(+), 6 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/message_input/tld.dart diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index d0d5e882..180c3d12 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -13,6 +13,7 @@ import 'package:stream_chat_flutter/src/commands_overlay.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/message_input/tld.dart'; import 'package:stream_chat_flutter/src/multi_overlay.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; @@ -932,7 +933,8 @@ class MessageInputState extends State if (_lastSearchedContainsUrlText == value) return; _lastSearchedContainsUrlText = value; - final matchedUrls = _urlRegex.allMatches(value); + final matchedUrls = _urlRegex.allMatches(value).toList() + ..removeWhere((it) => it.group(0)?.split('.').last.isValidTLD() == false); // Reset the og attachment if the text doesn't contain any url if (matchedUrls.isEmpty) { diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart b/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart index 12ec4831..4a6c3708 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_text_field_controller.dart @@ -1,8 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/message_input/tld.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A function that takes a [BuildContext] and returns a [TextStyle]. -typedef TextStyleBuilder = TextStyle Function(BuildContext context); +typedef TextStyleBuilder = TextStyle? Function( + BuildContext context, + String text, +); /// Controller for the [StreamTextField] widget. class MessageTextFieldController extends TextEditingController { @@ -32,9 +36,12 @@ class MessageTextFieldController extends TextEditingController { final pattern = textPatternStyle ?? { RegExp(r'(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+'): - (context) => TextStyle( - color: MessageInputTheme.of(context).linkHighlightColor, - ), + (context, text) { + if (!text.split('.').last.isValidTLD()) return null; + return TextStyle( + color: MessageInputTheme.of(context).linkHighlightColor, + ); + }, }; if (pattern.isEmpty) { return super.buildTextSpan( @@ -51,7 +58,10 @@ class MessageTextFieldController extends TextEditingController { final key = pattern.keys.firstWhere((it) => it.hasMatch(text)); return TextSpan( text: text, - style: pattern[key]?.call(context), + style: pattern[key]?.call( + context, + text, + ), ); }, ); diff --git a/packages/stream_chat_flutter/lib/src/message_input/tld.dart b/packages/stream_chat_flutter/lib/src/message_input/tld.dart new file mode 100644 index 00000000..504aa17b --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/message_input/tld.dart @@ -0,0 +1,1498 @@ +/// Extension on String adding utilities checking TLD validity. +extension TLDString on String { + /// Returns true if the string is a valid TLD. + bool isValidTLD() => tlds.contains(toUpperCase()); +} + +/// List of valid TLDs. +/// https://data.iana.org/TLD/tlds-alpha-by-domain.txt +const tlds = [ + 'AAA', + 'AARP', + 'ABARTH', + 'ABB', + 'ABBOTT', + 'ABBVIE', + 'ABC', + 'ABLE', + 'ABOGADO', + 'ABUDHABI', + 'AC', + 'ACADEMY', + 'ACCENTURE', + 'ACCOUNTANT', + 'ACCOUNTANTS', + 'ACO', + 'ACTOR', + 'AD', + 'ADAC', + 'ADS', + 'ADULT', + 'AE', + 'AEG', + 'AERO', + 'AETNA', + 'AF', + 'AFL', + 'AFRICA', + 'AG', + 'AGAKHAN', + 'AGENCY', + 'AI', + 'AIG', + 'AIRBUS', + 'AIRFORCE', + 'AIRTEL', + 'AKDN', + 'AL', + 'ALFAROMEO', + 'ALIBABA', + 'ALIPAY', + 'ALLFINANZ', + 'ALLSTATE', + 'ALLY', + 'ALSACE', + 'ALSTOM', + 'AM', + 'AMAZON', + 'AMERICANEXPRESS', + 'AMERICANFAMILY', + 'AMEX', + 'AMFAM', + 'AMICA', + 'AMSTERDAM', + 'ANALYTICS', + 'ANDROID', + 'ANQUAN', + 'ANZ', + 'AO', + 'AOL', + 'APARTMENTS', + 'APP', + 'APPLE', + 'AQ', + 'AQUARELLE', + 'AR', + 'ARAB', + 'ARAMCO', + 'ARCHI', + 'ARMY', + 'ARPA', + 'ART', + 'ARTE', + 'AS', + 'ASDA', + 'ASIA', + 'ASSOCIATES', + 'AT', + 'ATHLETA', + 'ATTORNEY', + 'AU', + 'AUCTION', + 'AUDI', + 'AUDIBLE', + 'AUDIO', + 'AUSPOST', + 'AUTHOR', + 'AUTO', + 'AUTOS', + 'AVIANCA', + 'AW', + 'AWS', + 'AX', + 'AXA', + 'AZ', + 'AZURE', + 'BA', + 'BABY', + 'BAIDU', + 'BANAMEX', + 'BANANAREPUBLIC', + 'BAND', + 'BANK', + 'BAR', + 'BARCELONA', + 'BARCLAYCARD', + 'BARCLAYS', + 'BAREFOOT', + 'BARGAINS', + 'BASEBALL', + 'BASKETBALL', + 'BAUHAUS', + 'BAYERN', + 'BB', + 'BBC', + 'BBT', + 'BBVA', + 'BCG', + 'BCN', + 'BD', + 'BE', + 'BEATS', + 'BEAUTY', + 'BEER', + 'BENTLEY', + 'BERLIN', + 'BEST', + 'BESTBUY', + 'BET', + 'BF', + 'BG', + 'BH', + 'BHARTI', + 'BI', + 'BIBLE', + 'BID', + 'BIKE', + 'BING', + 'BINGO', + 'BIO', + 'BIZ', + 'BJ', + 'BLACK', + 'BLACKFRIDAY', + 'BLOCKBUSTER', + 'BLOG', + 'BLOOMBERG', + 'BLUE', + 'BM', + 'BMS', + 'BMW', + 'BN', + 'BNPPARIBAS', + 'BO', + 'BOATS', + 'BOEHRINGER', + 'BOFA', + 'BOM', + 'BOND', + 'BOO', + 'BOOK', + 'BOOKING', + 'BOSCH', + 'BOSTIK', + 'BOSTON', + 'BOT', + 'BOUTIQUE', + 'BOX', + 'BR', + 'BRADESCO', + 'BRIDGESTONE', + 'BROADWAY', + 'BROKER', + 'BROTHER', + 'BRUSSELS', + 'BS', + 'BT', + 'BUDAPEST', + 'BUGATTI', + 'BUILD', + 'BUILDERS', + 'BUSINESS', + 'BUY', + 'BUZZ', + 'BV', + 'BW', + 'BY', + 'BZ', + 'BZH', + 'CA', + 'CAB', + 'CAFE', + 'CAL', + 'CALL', + 'CALVINKLEIN', + 'CAM', + 'CAMERA', + 'CAMP', + 'CANCERRESEARCH', + 'CANON', + 'CAPETOWN', + 'CAPITAL', + 'CAPITALONE', + 'CAR', + 'CARAVAN', + 'CARDS', + 'CARE', + 'CAREER', + 'CAREERS', + 'CARS', + 'CASA', + 'CASE', + 'CASH', + 'CASINO', + 'CAT', + 'CATERING', + 'CATHOLIC', + 'CBA', + 'CBN', + 'CBRE', + 'CBS', + 'CC', + 'CD', + 'CENTER', + 'CEO', + 'CERN', + 'CF', + 'CFA', + 'CFD', + 'CG', + 'CH', + 'CHANEL', + 'CHANNEL', + 'CHARITY', + 'CHASE', + 'CHAT', + 'CHEAP', + 'CHINTAI', + 'CHRISTMAS', + 'CHROME', + 'CHURCH', + 'CI', + 'CIPRIANI', + 'CIRCLE', + 'CISCO', + 'CITADEL', + 'CITI', + 'CITIC', + 'CITY', + 'CITYEATS', + 'CK', + 'CL', + 'CLAIMS', + 'CLEANING', + 'CLICK', + 'CLINIC', + 'CLINIQUE', + 'CLOTHING', + 'CLOUD', + 'CLUB', + 'CLUBMED', + 'CM', + 'CN', + 'CO', + 'COACH', + 'CODES', + 'COFFEE', + 'COLLEGE', + 'COLOGNE', + 'COM', + 'COMCAST', + 'COMMBANK', + 'COMMUNITY', + 'COMPANY', + 'COMPARE', + 'COMPUTER', + 'COMSEC', + 'CONDOS', + 'CONSTRUCTION', + 'CONSULTING', + 'CONTACT', + 'CONTRACTORS', + 'COOKING', + 'COOKINGCHANNEL', + 'COOL', + 'COOP', + 'CORSICA', + 'COUNTRY', + 'COUPON', + 'COUPONS', + 'COURSES', + 'CPA', + 'CR', + 'CREDIT', + 'CREDITCARD', + 'CREDITUNION', + 'CRICKET', + 'CROWN', + 'CRS', + 'CRUISE', + 'CRUISES', + 'CSC', + 'CU', + 'CUISINELLA', + 'CV', + 'CW', + 'CX', + 'CY', + 'CYMRU', + 'CYOU', + 'CZ', + 'DABUR', + 'DAD', + 'DANCE', + 'DATA', + 'DATE', + 'DATING', + 'DATSUN', + 'DAY', + 'DCLK', + 'DDS', + 'DE', + 'DEAL', + 'DEALER', + 'DEALS', + 'DEGREE', + 'DELIVERY', + 'DELL', + 'DELOITTE', + 'DELTA', + 'DEMOCRAT', + 'DENTAL', + 'DENTIST', + 'DESI', + 'DESIGN', + 'DEV', + 'DHL', + 'DIAMONDS', + 'DIET', + 'DIGITAL', + 'DIRECT', + 'DIRECTORY', + 'DISCOUNT', + 'DISCOVER', + 'DISH', + 'DIY', + 'DJ', + 'DK', + 'DM', + 'DNP', + 'DO', + 'DOCS', + 'DOCTOR', + 'DOG', + 'DOMAINS', + 'DOT', + 'DOWNLOAD', + 'DRIVE', + 'DTV', + 'DUBAI', + 'DUNLOP', + 'DUPONT', + 'DURBAN', + 'DVAG', + 'DVR', + 'DZ', + 'EARTH', + 'EAT', + 'EC', + 'ECO', + 'EDEKA', + 'EDU', + 'EDUCATION', + 'EE', + 'EG', + 'EMAIL', + 'EMERCK', + 'ENERGY', + 'ENGINEER', + 'ENGINEERING', + 'ENTERPRISES', + 'EPSON', + 'EQUIPMENT', + 'ER', + 'ERICSSON', + 'ERNI', + 'ES', + 'ESQ', + 'ESTATE', + 'ET', + 'ETISALAT', + 'EU', + 'EUROVISION', + 'EUS', + 'EVENTS', + 'EXCHANGE', + 'EXPERT', + 'EXPOSED', + 'EXPRESS', + 'EXTRASPACE', + 'FAGE', + 'FAIL', + 'FAIRWINDS', + 'FAITH', + 'FAMILY', + 'FAN', + 'FANS', + 'FARM', + 'FARMERS', + 'FASHION', + 'FAST', + 'FEDEX', + 'FEEDBACK', + 'FERRARI', + 'FERRERO', + 'FI', + 'FIAT', + 'FIDELITY', + 'FIDO', + 'FILM', + 'FINAL', + 'FINANCE', + 'FINANCIAL', + 'FIRE', + 'FIRESTONE', + 'FIRMDALE', + 'FISH', + 'FISHING', + 'FIT', + 'FITNESS', + 'FJ', + 'FK', + 'FLICKR', + 'FLIGHTS', + 'FLIR', + 'FLORIST', + 'FLOWERS', + 'FLY', + 'FM', + 'FO', + 'FOO', + 'FOOD', + 'FOODNETWORK', + 'FOOTBALL', + 'FORD', + 'FOREX', + 'FORSALE', + 'FORUM', + 'FOUNDATION', + 'FOX', + 'FR', + 'FREE', + 'FRESENIUS', + 'FRL', + 'FROGANS', + 'FRONTDOOR', + 'FRONTIER', + 'FTR', + 'FUJITSU', + 'FUN', + 'FUND', + 'FURNITURE', + 'FUTBOL', + 'FYI', + 'GA', + 'GAL', + 'GALLERY', + 'GALLO', + 'GALLUP', + 'GAME', + 'GAMES', + 'GAP', + 'GARDEN', + 'GAY', + 'GB', + 'GBIZ', + 'GD', + 'GDN', + 'GE', + 'GEA', + 'GENT', + 'GENTING', + 'GEORGE', + 'GF', + 'GG', + 'GGEE', + 'GH', + 'GI', + 'GIFT', + 'GIFTS', + 'GIVES', + 'GIVING', + 'GL', + 'GLASS', + 'GLE', + 'GLOBAL', + 'GLOBO', + 'GM', + 'GMAIL', + 'GMBH', + 'GMO', + 'GMX', + 'GN', + 'GODADDY', + 'GOLD', + 'GOLDPOINT', + 'GOLF', + 'GOO', + 'GOODYEAR', + 'GOOG', + 'GOOGLE', + 'GOP', + 'GOT', + 'GOV', + 'GP', + 'GQ', + 'GR', + 'GRAINGER', + 'GRAPHICS', + 'GRATIS', + 'GREEN', + 'GRIPE', + 'GROCERY', + 'GROUP', + 'GS', + 'GT', + 'GU', + 'GUARDIAN', + 'GUCCI', + 'GUGE', + 'GUIDE', + 'GUITARS', + 'GURU', + 'GW', + 'GY', + 'HAIR', + 'HAMBURG', + 'HANGOUT', + 'HAUS', + 'HBO', + 'HDFC', + 'HDFCBANK', + 'HEALTH', + 'HEALTHCARE', + 'HELP', + 'HELSINKI', + 'HERE', + 'HERMES', + 'HGTV', + 'HIPHOP', + 'HISAMITSU', + 'HITACHI', + 'HIV', + 'HK', + 'HKT', + 'HM', + 'HN', + 'HOCKEY', + 'HOLDINGS', + 'HOLIDAY', + 'HOMEDEPOT', + 'HOMEGOODS', + 'HOMES', + 'HOMESENSE', + 'HONDA', + 'HORSE', + 'HOSPITAL', + 'HOST', + 'HOSTING', + 'HOT', + 'HOTELES', + 'HOTELS', + 'HOTMAIL', + 'HOUSE', + 'HOW', + 'HR', + 'HSBC', + 'HT', + 'HU', + 'HUGHES', + 'HYATT', + 'HYUNDAI', + 'IBM', + 'ICBC', + 'ICE', + 'ICU', + 'ID', + 'IE', + 'IEEE', + 'IFM', + 'IKANO', + 'IL', + 'IM', + 'IMAMAT', + 'IMDB', + 'IMMO', + 'IMMOBILIEN', + 'IN', + 'INC', + 'INDUSTRIES', + 'INFINITI', + 'INFO', + 'ING', + 'INK', + 'INSTITUTE', + 'INSURANCE', + 'INSURE', + 'INT', + 'INTERNATIONAL', + 'INTUIT', + 'INVESTMENTS', + 'IO', + 'IPIRANGA', + 'IQ', + 'IR', + 'IRISH', + 'IS', + 'ISMAILI', + 'IST', + 'ISTANBUL', + 'IT', + 'ITAU', + 'ITV', + 'JAGUAR', + 'JAVA', + 'JCB', + 'JE', + 'JEEP', + 'JETZT', + 'JEWELRY', + 'JIO', + 'JLL', + 'JM', + 'JMP', + 'JNJ', + 'JO', + 'JOBS', + 'JOBURG', + 'JOT', + 'JOY', + 'JP', + 'JPMORGAN', + 'JPRS', + 'JUEGOS', + 'JUNIPER', + 'KAUFEN', + 'KDDI', + 'KE', + 'KERRYHOTELS', + 'KERRYLOGISTICS', + 'KERRYPROPERTIES', + 'KFH', + 'KG', + 'KH', + 'KI', + 'KIA', + 'KIM', + 'KINDER', + 'KINDLE', + 'KITCHEN', + 'KIWI', + 'KM', + 'KN', + 'KOELN', + 'KOMATSU', + 'KOSHER', + 'KP', + 'KPMG', + 'KPN', + 'KR', + 'KRD', + 'KRED', + 'KUOKGROUP', + 'KW', + 'KY', + 'KYOTO', + 'KZ', + 'LA', + 'LACAIXA', + 'LAMBORGHINI', + 'LAMER', + 'LANCASTER', + 'LANCIA', + 'LAND', + 'LANDROVER', + 'LANXESS', + 'LASALLE', + 'LAT', + 'LATINO', + 'LATROBE', + 'LAW', + 'LAWYER', + 'LB', + 'LC', + 'LDS', + 'LEASE', + 'LECLERC', + 'LEFRAK', + 'LEGAL', + 'LEGO', + 'LEXUS', + 'LGBT', + 'LI', + 'LIDL', + 'LIFE', + 'LIFEINSURANCE', + 'LIFESTYLE', + 'LIGHTING', + 'LIKE', + 'LILLY', + 'LIMITED', + 'LIMO', + 'LINCOLN', + 'LINDE', + 'LINK', + 'LIPSY', + 'LIVE', + 'LIVING', + 'LK', + 'LLC', + 'LLP', + 'LOAN', + 'LOANS', + 'LOCKER', + 'LOCUS', + 'LOFT', + 'LOL', + 'LONDON', + 'LOTTE', + 'LOTTO', + 'LOVE', + 'LPL', + 'LPLFINANCIAL', + 'LR', + 'LS', + 'LT', + 'LTD', + 'LTDA', + 'LU', + 'LUNDBECK', + 'LUXE', + 'LUXURY', + 'LV', + 'LY', + 'MA', + 'MACYS', + 'MADRID', + 'MAIF', + 'MAISON', + 'MAKEUP', + 'MAN', + 'MANAGEMENT', + 'MANGO', + 'MAP', + 'MARKET', + 'MARKETING', + 'MARKETS', + 'MARRIOTT', + 'MARSHALLS', + 'MASERATI', + 'MATTEL', + 'MBA', + 'MC', + 'MCKINSEY', + 'MD', + 'ME', + 'MED', + 'MEDIA', + 'MEET', + 'MELBOURNE', + 'MEME', + 'MEMORIAL', + 'MEN', + 'MENU', + 'MERCKMSD', + 'MG', + 'MH', + 'MIAMI', + 'MICROSOFT', + 'MIL', + 'MINI', + 'MINT', + 'MIT', + 'MITSUBISHI', + 'MK', + 'ML', + 'MLB', + 'MLS', + 'MM', + 'MMA', + 'MN', + 'MO', + 'MOBI', + 'MOBILE', + 'MODA', + 'MOE', + 'MOI', + 'MOM', + 'MONASH', + 'MONEY', + 'MONSTER', + 'MORMON', + 'MORTGAGE', + 'MOSCOW', + 'MOTO', + 'MOTORCYCLES', + 'MOV', + 'MOVIE', + 'MP', + 'MQ', + 'MR', + 'MS', + 'MSD', + 'MT', + 'MTN', + 'MTR', + 'MU', + 'MUSEUM', + 'MUSIC', + 'MUTUAL', + 'MV', + 'MW', + 'MX', + 'MY', + 'MZ', + 'NA', + 'NAB', + 'NAGOYA', + 'NAME', + 'NATURA', + 'NAVY', + 'NBA', + 'NC', + 'NE', + 'NEC', + 'NET', + 'NETBANK', + 'NETFLIX', + 'NETWORK', + 'NEUSTAR', + 'NEW', + 'NEWS', + 'NEXT', + 'NEXTDIRECT', + 'NEXUS', + 'NF', + 'NFL', + 'NG', + 'NGO', + 'NHK', + 'NI', + 'NICO', + 'NIKE', + 'NIKON', + 'NINJA', + 'NISSAN', + 'NISSAY', + 'NL', + 'NO', + 'NOKIA', + 'NORTHWESTERNMUTUAL', + 'NORTON', + 'NOW', + 'NOWRUZ', + 'NOWTV', + 'NP', + 'NR', + 'NRA', + 'NRW', + 'NTT', + 'NU', + 'NYC', + 'NZ', + 'OBI', + 'OBSERVER', + 'OFFICE', + 'OKINAWA', + 'OLAYAN', + 'OLAYANGROUP', + 'OLDNAVY', + 'OLLO', + 'OM', + 'OMEGA', + 'ONE', + 'ONG', + 'ONL', + 'ONLINE', + 'OOO', + 'OPEN', + 'ORACLE', + 'ORANGE', + 'ORG', + 'ORGANIC', + 'ORIGINS', + 'OSAKA', + 'OTSUKA', + 'OTT', + 'OVH', + 'PA', + 'PAGE', + 'PANASONIC', + 'PARIS', + 'PARS', + 'PARTNERS', + 'PARTS', + 'PARTY', + 'PASSAGENS', + 'PAY', + 'PCCW', + 'PE', + 'PET', + 'PF', + 'PFIZER', + 'PG', + 'PH', + 'PHARMACY', + 'PHD', + 'PHILIPS', + 'PHONE', + 'PHOTO', + 'PHOTOGRAPHY', + 'PHOTOS', + 'PHYSIO', + 'PICS', + 'PICTET', + 'PICTURES', + 'PID', + 'PIN', + 'PING', + 'PINK', + 'PIONEER', + 'PIZZA', + 'PK', + 'PL', + 'PLACE', + 'PLAY', + 'PLAYSTATION', + 'PLUMBING', + 'PLUS', + 'PM', + 'PN', + 'PNC', + 'POHL', + 'POKER', + 'POLITIE', + 'PORN', + 'POST', + 'PR', + 'PRAMERICA', + 'PRAXI', + 'PRESS', + 'PRIME', + 'PRO', + 'PROD', + 'PRODUCTIONS', + 'PROF', + 'PROGRESSIVE', + 'PROMO', + 'PROPERTIES', + 'PROPERTY', + 'PROTECTION', + 'PRU', + 'PRUDENTIAL', + 'PS', + 'PT', + 'PUB', + 'PW', + 'PWC', + 'PY', + 'QA', + 'QPON', + 'QUEBEC', + 'QUEST', + 'RACING', + 'RADIO', + 'RE', + 'READ', + 'REALESTATE', + 'REALTOR', + 'REALTY', + 'RECIPES', + 'RED', + 'REDSTONE', + 'REDUMBRELLA', + 'REHAB', + 'REISE', + 'REISEN', + 'REIT', + 'RELIANCE', + 'REN', + 'RENT', + 'RENTALS', + 'REPAIR', + 'REPORT', + 'REPUBLICAN', + 'REST', + 'RESTAURANT', + 'REVIEW', + 'REVIEWS', + 'REXROTH', + 'RICH', + 'RICHARDLI', + 'RICOH', + 'RIL', + 'RIO', + 'RIP', + 'RO', + 'ROCHER', + 'ROCKS', + 'RODEO', + 'ROGERS', + 'ROOM', + 'RS', + 'RSVP', + 'RU', + 'RUGBY', + 'RUHR', + 'RUN', + 'RW', + 'RWE', + 'RYUKYU', + 'SA', + 'SAARLAND', + 'SAFE', + 'SAFETY', + 'SAKURA', + 'SALE', + 'SALON', + 'SAMSCLUB', + 'SAMSUNG', + 'SANDVIK', + 'SANDVIKCOROMANT', + 'SANOFI', + 'SAP', + 'SARL', + 'SAS', + 'SAVE', + 'SAXO', + 'SB', + 'SBI', + 'SBS', + 'SC', + 'SCA', + 'SCB', + 'SCHAEFFLER', + 'SCHMIDT', + 'SCHOLARSHIPS', + 'SCHOOL', + 'SCHULE', + 'SCHWARZ', + 'SCIENCE', + 'SCOT', + 'SD', + 'SE', + 'SEARCH', + 'SEAT', + 'SECURE', + 'SECURITY', + 'SEEK', + 'SELECT', + 'SENER', + 'SERVICES', + 'SES', + 'SEVEN', + 'SEW', + 'SEX', + 'SEXY', + 'SFR', + 'SG', + 'SH', + 'SHANGRILA', + 'SHARP', + 'SHAW', + 'SHELL', + 'SHIA', + 'SHIKSHA', + 'SHOES', + 'SHOP', + 'SHOPPING', + 'SHOUJI', + 'SHOW', + 'SHOWTIME', + 'SI', + 'SILK', + 'SINA', + 'SINGLES', + 'SITE', + 'SJ', + 'SK', + 'SKI', + 'SKIN', + 'SKY', + 'SKYPE', + 'SL', + 'SLING', + 'SM', + 'SMART', + 'SMILE', + 'SN', + 'SNCF', + 'SO', + 'SOCCER', + 'SOCIAL', + 'SOFTBANK', + 'SOFTWARE', + 'SOHU', + 'SOLAR', + 'SOLUTIONS', + 'SONG', + 'SONY', + 'SOY', + 'SPA', + 'SPACE', + 'SPORT', + 'SPOT', + 'SR', + 'SRL', + 'SS', + 'ST', + 'STADA', + 'STAPLES', + 'STAR', + 'STATEBANK', + 'STATEFARM', + 'STC', + 'STCGROUP', + 'STOCKHOLM', + 'STORAGE', + 'STORE', + 'STREAM', + 'STUDIO', + 'STUDY', + 'STYLE', + 'SU', + 'SUCKS', + 'SUPPLIES', + 'SUPPLY', + 'SUPPORT', + 'SURF', + 'SURGERY', + 'SUZUKI', + 'SV', + 'SWATCH', + 'SWISS', + 'SX', + 'SY', + 'SYDNEY', + 'SYSTEMS', + 'SZ', + 'TAB', + 'TAIPEI', + 'TALK', + 'TAOBAO', + 'TARGET', + 'TATAMOTORS', + 'TATAR', + 'TATTOO', + 'TAX', + 'TAXI', + 'TC', + 'TCI', + 'TD', + 'TDK', + 'TEAM', + 'TECH', + 'TECHNOLOGY', + 'TEL', + 'TEMASEK', + 'TENNIS', + 'TEVA', + 'TF', + 'TG', + 'TH', + 'THD', + 'THEATER', + 'THEATRE', + 'TIAA', + 'TICKETS', + 'TIENDA', + 'TIFFANY', + 'TIPS', + 'TIRES', + 'TIROL', + 'TJ', + 'TJMAXX', + 'TJX', + 'TK', + 'TKMAXX', + 'TL', + 'TM', + 'TMALL', + 'TN', + 'TO', + 'TODAY', + 'TOKYO', + 'TOOLS', + 'TOP', + 'TORAY', + 'TOSHIBA', + 'TOTAL', + 'TOURS', + 'TOWN', + 'TOYOTA', + 'TOYS', + 'TR', + 'TRADE', + 'TRADING', + 'TRAINING', + 'TRAVEL', + 'TRAVELCHANNEL', + 'TRAVELERS', + 'TRAVELERSINSURANCE', + 'TRUST', + 'TRV', + 'TT', + 'TUBE', + 'TUI', + 'TUNES', + 'TUSHU', + 'TV', + 'TVS', + 'TW', + 'TZ', + 'UA', + 'UBANK', + 'UBS', + 'UG', + 'UK', + 'UNICOM', + 'UNIVERSITY', + 'UNO', + 'UOL', + 'UPS', + 'US', + 'UY', + 'UZ', + 'VA', + 'VACATIONS', + 'VANA', + 'VANGUARD', + 'VC', + 'VE', + 'VEGAS', + 'VENTURES', + 'VERISIGN', + 'VERSICHERUNG', + 'VET', + 'VG', + 'VI', + 'VIAJES', + 'VIDEO', + 'VIG', + 'VIKING', + 'VILLAS', + 'VIN', + 'VIP', + 'VIRGIN', + 'VISA', + 'VISION', + 'VIVA', + 'VIVO', + 'VLAANDEREN', + 'VN', + 'VODKA', + 'VOLKSWAGEN', + 'VOLVO', + 'VOTE', + 'VOTING', + 'VOTO', + 'VOYAGE', + 'VU', + 'VUELOS', + 'WALES', + 'WALMART', + 'WALTER', + 'WANG', + 'WANGGOU', + 'WATCH', + 'WATCHES', + 'WEATHER', + 'WEATHERCHANNEL', + 'WEBCAM', + 'WEBER', + 'WEBSITE', + 'WED', + 'WEDDING', + 'WEIBO', + 'WEIR', + 'WF', + 'WHOSWHO', + 'WIEN', + 'WIKI', + 'WILLIAMHILL', + 'WIN', + 'WINDOWS', + 'WINE', + 'WINNERS', + 'WME', + 'WOLTERSKLUWER', + 'WOODSIDE', + 'WORK', + 'WORKS', + 'WORLD', + 'WOW', + 'WS', + 'WTC', + 'WTF', + 'XBOX', + 'XEROX', + 'XFINITY', + 'XIHUAN', + 'XIN', + 'XN--11B4C3D', + 'XN--1CK2E1B', + 'XN--1QQW23A', + 'XN--2SCRJ9C', + 'XN--30RR7Y', + 'XN--3BST00M', + 'XN--3DS443G', + 'XN--3E0B707E', + 'XN--3HCRJ9C', + 'XN--3PXU8K', + 'XN--42C2D9A', + 'XN--45BR5CYL', + 'XN--45BRJ9C', + 'XN--45Q11C', + 'XN--4DBRK0CE', + 'XN--4GBRIM', + 'XN--54B7FTA0CC', + 'XN--55QW42G', + 'XN--55QX5D', + 'XN--5SU34J936BGSG', + 'XN--5TZM5G', + 'XN--6FRZ82G', + 'XN--6QQ986B3XL', + 'XN--80ADXHKS', + 'XN--80AO21A', + 'XN--80AQECDR1A', + 'XN--80ASEHDB', + 'XN--80ASWG', + 'XN--8Y0A063A', + 'XN--90A3AC', + 'XN--90AE', + 'XN--90AIS', + 'XN--9DBQ2A', + 'XN--9ET52U', + 'XN--9KRT00A', + 'XN--B4W605FERD', + 'XN--BCK1B9A5DRE4C', + 'XN--C1AVG', + 'XN--C2BR7G', + 'XN--CCK2B3B', + 'XN--CCKWCXETD', + 'XN--CG4BKI', + 'XN--CLCHC0EA0B2G2A9GCD', + 'XN--CZR694B', + 'XN--CZRS0T', + 'XN--CZRU2D', + 'XN--D1ACJ3B', + 'XN--D1ALF', + 'XN--E1A4C', + 'XN--ECKVDTC9D', + 'XN--EFVY88H', + 'XN--FCT429K', + 'XN--FHBEI', + 'XN--FIQ228C5HS', + 'XN--FIQ64B', + 'XN--FIQS8S', + 'XN--FIQZ9S', + 'XN--FJQ720A', + 'XN--FLW351E', + 'XN--FPCRJ9C3D', + 'XN--FZC2C9E2C', + 'XN--FZYS8D69UVGM', + 'XN--G2XX48C', + 'XN--GCKR3F0F', + 'XN--GECRJ9C', + 'XN--GK3AT1E', + 'XN--H2BREG3EVE', + 'XN--H2BRJ9C', + 'XN--H2BRJ9C8C', + 'XN--HXT814E', + 'XN--I1B6B1A6A2E', + 'XN--IMR513N', + 'XN--IO0A7I', + 'XN--J1AEF', + 'XN--J1AMH', + 'XN--J6W193G', + 'XN--JLQ480N2RG', + 'XN--JLQ61U9W7B', + 'XN--JVR189M', + 'XN--KCRX77D1X4A', + 'XN--KPRW13D', + 'XN--KPRY57D', + 'XN--KPUT3I', + 'XN--L1ACC', + 'XN--LGBBAT1AD8J', + 'XN--MGB9AWBF', + 'XN--MGBA3A3EJT', + 'XN--MGBA3A4F16A', + 'XN--MGBA7C0BBN0A', + 'XN--MGBAAKC7DVF', + 'XN--MGBAAM7A8H', + 'XN--MGBAB2BD', + 'XN--MGBAH1A3HJKRD', + 'XN--MGBAI9AZGQP6J', + 'XN--MGBAYH7GPA', + 'XN--MGBBH1A', + 'XN--MGBBH1A71E', + 'XN--MGBC0A9AZCG', + 'XN--MGBCA7DZDO', + 'XN--MGBCPQ6GPA1A', + 'XN--MGBERP4A5D4AR', + 'XN--MGBGU82A', + 'XN--MGBI4ECEXP', + 'XN--MGBPL2FH', + 'XN--MGBT3DHD', + 'XN--MGBTX2B', + 'XN--MGBX4CD0AB', + 'XN--MIX891F', + 'XN--MK1BU44C', + 'XN--MXTQ1M', + 'XN--NGBC5AZD', + 'XN--NGBE9E0A', + 'XN--NGBRX', + 'XN--NODE', + 'XN--NQV7F', + 'XN--NQV7FS00EMA', + 'XN--NYQY26A', + 'XN--O3CW4H', + 'XN--OGBPF8FL', + 'XN--OTU796D', + 'XN--P1ACF', + 'XN--P1AI', + 'XN--PGBS0DH', + 'XN--PSSY2U', + 'XN--Q7CE6A', + 'XN--Q9JYB4C', + 'XN--QCKA1PMC', + 'XN--QXA6A', + 'XN--QXAM', + 'XN--RHQV96G', + 'XN--ROVU88B', + 'XN--RVC1E0AM3E', + 'XN--S9BRJ9C', + 'XN--SES554G', + 'XN--T60B56A', + 'XN--TCKWE', + 'XN--TIQ49XQYJ', + 'XN--UNUP4Y', + 'XN--VERMGENSBERATER-CTB', + 'XN--VERMGENSBERATUNG-PWB', + 'XN--VHQUV', + 'XN--VUQ861B', + 'XN--W4R85EL8FHU5DNRA', + 'XN--W4RS40L', + 'XN--WGBH1C', + 'XN--WGBL6A', + 'XN--XHQ521B', + 'XN--XKC2AL3HYE2A', + 'XN--XKC2DL3A5EE0H', + 'XN--Y9A3AQ', + 'XN--YFRO4I67O', + 'XN--YGBI2AMMX', + 'XN--ZFR164B', + 'XXX', + 'XYZ', + 'YACHTS', + 'YAHOO', + 'YAMAXUN', + 'YANDEX', + 'YE', + 'YODOBASHI', + 'YOGA', + 'YOKOHAMA', + 'YOU', + 'YOUTUBE', + 'YT', + 'YUN', + 'ZA', + 'ZAPPOS', + 'ZARA', + 'ZERO', + 'ZIP', + 'ZM', + 'ZONE', + 'ZUERICH', + 'ZW', +]; From 631ab689900e48bb5eb50f44b92650b5cd650d38 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 4 Jan 2022 14:37:34 +0530 Subject: [PATCH 10/14] make the tld non-linear-searchy --- .../lib/src/message_input/tld.dart | 3037 +++++++++-------- 1 file changed, 1546 insertions(+), 1491 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/tld.dart b/packages/stream_chat_flutter/lib/src/message_input/tld.dart index 504aa17b..2bfa5257 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/tld.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/tld.dart @@ -1,1498 +1,1553 @@ /// Extension on String adding utilities checking TLD validity. extension TLDString on String { /// Returns true if the string is a valid TLD. - bool isValidTLD() => tlds.contains(toUpperCase()); + bool isValidTLD() => + isNotEmpty && + tlds.containsKey(this[0].toUpperCase()) && + tlds[this[0].toUpperCase()]!.contains(toUpperCase()); } /// List of valid TLDs. /// https://data.iana.org/TLD/tlds-alpha-by-domain.txt -const tlds = [ - 'AAA', - 'AARP', - 'ABARTH', - 'ABB', - 'ABBOTT', - 'ABBVIE', - 'ABC', - 'ABLE', - 'ABOGADO', - 'ABUDHABI', - 'AC', - 'ACADEMY', - 'ACCENTURE', - 'ACCOUNTANT', - 'ACCOUNTANTS', - 'ACO', - 'ACTOR', - 'AD', - 'ADAC', - 'ADS', - 'ADULT', - 'AE', - 'AEG', - 'AERO', - 'AETNA', - 'AF', - 'AFL', - 'AFRICA', - 'AG', - 'AGAKHAN', - 'AGENCY', - 'AI', - 'AIG', - 'AIRBUS', - 'AIRFORCE', - 'AIRTEL', - 'AKDN', - 'AL', - 'ALFAROMEO', - 'ALIBABA', - 'ALIPAY', - 'ALLFINANZ', - 'ALLSTATE', - 'ALLY', - 'ALSACE', - 'ALSTOM', - 'AM', - 'AMAZON', - 'AMERICANEXPRESS', - 'AMERICANFAMILY', - 'AMEX', - 'AMFAM', - 'AMICA', - 'AMSTERDAM', - 'ANALYTICS', - 'ANDROID', - 'ANQUAN', - 'ANZ', - 'AO', - 'AOL', - 'APARTMENTS', - 'APP', - 'APPLE', - 'AQ', - 'AQUARELLE', - 'AR', - 'ARAB', - 'ARAMCO', - 'ARCHI', - 'ARMY', - 'ARPA', - 'ART', - 'ARTE', - 'AS', - 'ASDA', - 'ASIA', - 'ASSOCIATES', - 'AT', - 'ATHLETA', - 'ATTORNEY', - 'AU', - 'AUCTION', - 'AUDI', - 'AUDIBLE', - 'AUDIO', - 'AUSPOST', - 'AUTHOR', - 'AUTO', - 'AUTOS', - 'AVIANCA', - 'AW', - 'AWS', - 'AX', - 'AXA', - 'AZ', - 'AZURE', - 'BA', - 'BABY', - 'BAIDU', - 'BANAMEX', - 'BANANAREPUBLIC', - 'BAND', - 'BANK', - 'BAR', - 'BARCELONA', - 'BARCLAYCARD', - 'BARCLAYS', - 'BAREFOOT', - 'BARGAINS', - 'BASEBALL', - 'BASKETBALL', - 'BAUHAUS', - 'BAYERN', - 'BB', - 'BBC', - 'BBT', - 'BBVA', - 'BCG', - 'BCN', - 'BD', - 'BE', - 'BEATS', - 'BEAUTY', - 'BEER', - 'BENTLEY', - 'BERLIN', - 'BEST', - 'BESTBUY', - 'BET', - 'BF', - 'BG', - 'BH', - 'BHARTI', - 'BI', - 'BIBLE', - 'BID', - 'BIKE', - 'BING', - 'BINGO', - 'BIO', - 'BIZ', - 'BJ', - 'BLACK', - 'BLACKFRIDAY', - 'BLOCKBUSTER', - 'BLOG', - 'BLOOMBERG', - 'BLUE', - 'BM', - 'BMS', - 'BMW', - 'BN', - 'BNPPARIBAS', - 'BO', - 'BOATS', - 'BOEHRINGER', - 'BOFA', - 'BOM', - 'BOND', - 'BOO', - 'BOOK', - 'BOOKING', - 'BOSCH', - 'BOSTIK', - 'BOSTON', - 'BOT', - 'BOUTIQUE', - 'BOX', - 'BR', - 'BRADESCO', - 'BRIDGESTONE', - 'BROADWAY', - 'BROKER', - 'BROTHER', - 'BRUSSELS', - 'BS', - 'BT', - 'BUDAPEST', - 'BUGATTI', - 'BUILD', - 'BUILDERS', - 'BUSINESS', - 'BUY', - 'BUZZ', - 'BV', - 'BW', - 'BY', - 'BZ', - 'BZH', - 'CA', - 'CAB', - 'CAFE', - 'CAL', - 'CALL', - 'CALVINKLEIN', - 'CAM', - 'CAMERA', - 'CAMP', - 'CANCERRESEARCH', - 'CANON', - 'CAPETOWN', - 'CAPITAL', - 'CAPITALONE', - 'CAR', - 'CARAVAN', - 'CARDS', - 'CARE', - 'CAREER', - 'CAREERS', - 'CARS', - 'CASA', - 'CASE', - 'CASH', - 'CASINO', - 'CAT', - 'CATERING', - 'CATHOLIC', - 'CBA', - 'CBN', - 'CBRE', - 'CBS', - 'CC', - 'CD', - 'CENTER', - 'CEO', - 'CERN', - 'CF', - 'CFA', - 'CFD', - 'CG', - 'CH', - 'CHANEL', - 'CHANNEL', - 'CHARITY', - 'CHASE', - 'CHAT', - 'CHEAP', - 'CHINTAI', - 'CHRISTMAS', - 'CHROME', - 'CHURCH', - 'CI', - 'CIPRIANI', - 'CIRCLE', - 'CISCO', - 'CITADEL', - 'CITI', - 'CITIC', - 'CITY', - 'CITYEATS', - 'CK', - 'CL', - 'CLAIMS', - 'CLEANING', - 'CLICK', - 'CLINIC', - 'CLINIQUE', - 'CLOTHING', - 'CLOUD', - 'CLUB', - 'CLUBMED', - 'CM', - 'CN', - 'CO', - 'COACH', - 'CODES', - 'COFFEE', - 'COLLEGE', - 'COLOGNE', - 'COM', - 'COMCAST', - 'COMMBANK', - 'COMMUNITY', - 'COMPANY', - 'COMPARE', - 'COMPUTER', - 'COMSEC', - 'CONDOS', - 'CONSTRUCTION', - 'CONSULTING', - 'CONTACT', - 'CONTRACTORS', - 'COOKING', - 'COOKINGCHANNEL', - 'COOL', - 'COOP', - 'CORSICA', - 'COUNTRY', - 'COUPON', - 'COUPONS', - 'COURSES', - 'CPA', - 'CR', - 'CREDIT', - 'CREDITCARD', - 'CREDITUNION', - 'CRICKET', - 'CROWN', - 'CRS', - 'CRUISE', - 'CRUISES', - 'CSC', - 'CU', - 'CUISINELLA', - 'CV', - 'CW', - 'CX', - 'CY', - 'CYMRU', - 'CYOU', - 'CZ', - 'DABUR', - 'DAD', - 'DANCE', - 'DATA', - 'DATE', - 'DATING', - 'DATSUN', - 'DAY', - 'DCLK', - 'DDS', - 'DE', - 'DEAL', - 'DEALER', - 'DEALS', - 'DEGREE', - 'DELIVERY', - 'DELL', - 'DELOITTE', - 'DELTA', - 'DEMOCRAT', - 'DENTAL', - 'DENTIST', - 'DESI', - 'DESIGN', - 'DEV', - 'DHL', - 'DIAMONDS', - 'DIET', - 'DIGITAL', - 'DIRECT', - 'DIRECTORY', - 'DISCOUNT', - 'DISCOVER', - 'DISH', - 'DIY', - 'DJ', - 'DK', - 'DM', - 'DNP', - 'DO', - 'DOCS', - 'DOCTOR', - 'DOG', - 'DOMAINS', - 'DOT', - 'DOWNLOAD', - 'DRIVE', - 'DTV', - 'DUBAI', - 'DUNLOP', - 'DUPONT', - 'DURBAN', - 'DVAG', - 'DVR', - 'DZ', - 'EARTH', - 'EAT', - 'EC', - 'ECO', - 'EDEKA', - 'EDU', - 'EDUCATION', - 'EE', - 'EG', - 'EMAIL', - 'EMERCK', - 'ENERGY', - 'ENGINEER', - 'ENGINEERING', - 'ENTERPRISES', - 'EPSON', - 'EQUIPMENT', - 'ER', - 'ERICSSON', - 'ERNI', - 'ES', - 'ESQ', - 'ESTATE', - 'ET', - 'ETISALAT', - 'EU', - 'EUROVISION', - 'EUS', - 'EVENTS', - 'EXCHANGE', - 'EXPERT', - 'EXPOSED', - 'EXPRESS', - 'EXTRASPACE', - 'FAGE', - 'FAIL', - 'FAIRWINDS', - 'FAITH', - 'FAMILY', - 'FAN', - 'FANS', - 'FARM', - 'FARMERS', - 'FASHION', - 'FAST', - 'FEDEX', - 'FEEDBACK', - 'FERRARI', - 'FERRERO', - 'FI', - 'FIAT', - 'FIDELITY', - 'FIDO', - 'FILM', - 'FINAL', - 'FINANCE', - 'FINANCIAL', - 'FIRE', - 'FIRESTONE', - 'FIRMDALE', - 'FISH', - 'FISHING', - 'FIT', - 'FITNESS', - 'FJ', - 'FK', - 'FLICKR', - 'FLIGHTS', - 'FLIR', - 'FLORIST', - 'FLOWERS', - 'FLY', - 'FM', - 'FO', - 'FOO', - 'FOOD', - 'FOODNETWORK', - 'FOOTBALL', - 'FORD', - 'FOREX', - 'FORSALE', - 'FORUM', - 'FOUNDATION', - 'FOX', - 'FR', - 'FREE', - 'FRESENIUS', - 'FRL', - 'FROGANS', - 'FRONTDOOR', - 'FRONTIER', - 'FTR', - 'FUJITSU', - 'FUN', - 'FUND', - 'FURNITURE', - 'FUTBOL', - 'FYI', - 'GA', - 'GAL', - 'GALLERY', - 'GALLO', - 'GALLUP', - 'GAME', - 'GAMES', - 'GAP', - 'GARDEN', - 'GAY', - 'GB', - 'GBIZ', - 'GD', - 'GDN', - 'GE', - 'GEA', - 'GENT', - 'GENTING', - 'GEORGE', - 'GF', - 'GG', - 'GGEE', - 'GH', - 'GI', - 'GIFT', - 'GIFTS', - 'GIVES', - 'GIVING', - 'GL', - 'GLASS', - 'GLE', - 'GLOBAL', - 'GLOBO', - 'GM', - 'GMAIL', - 'GMBH', - 'GMO', - 'GMX', - 'GN', - 'GODADDY', - 'GOLD', - 'GOLDPOINT', - 'GOLF', - 'GOO', - 'GOODYEAR', - 'GOOG', - 'GOOGLE', - 'GOP', - 'GOT', - 'GOV', - 'GP', - 'GQ', - 'GR', - 'GRAINGER', - 'GRAPHICS', - 'GRATIS', - 'GREEN', - 'GRIPE', - 'GROCERY', - 'GROUP', - 'GS', - 'GT', - 'GU', - 'GUARDIAN', - 'GUCCI', - 'GUGE', - 'GUIDE', - 'GUITARS', - 'GURU', - 'GW', - 'GY', - 'HAIR', - 'HAMBURG', - 'HANGOUT', - 'HAUS', - 'HBO', - 'HDFC', - 'HDFCBANK', - 'HEALTH', - 'HEALTHCARE', - 'HELP', - 'HELSINKI', - 'HERE', - 'HERMES', - 'HGTV', - 'HIPHOP', - 'HISAMITSU', - 'HITACHI', - 'HIV', - 'HK', - 'HKT', - 'HM', - 'HN', - 'HOCKEY', - 'HOLDINGS', - 'HOLIDAY', - 'HOMEDEPOT', - 'HOMEGOODS', - 'HOMES', - 'HOMESENSE', - 'HONDA', - 'HORSE', - 'HOSPITAL', - 'HOST', - 'HOSTING', - 'HOT', - 'HOTELES', - 'HOTELS', - 'HOTMAIL', - 'HOUSE', - 'HOW', - 'HR', - 'HSBC', - 'HT', - 'HU', - 'HUGHES', - 'HYATT', - 'HYUNDAI', - 'IBM', - 'ICBC', - 'ICE', - 'ICU', - 'ID', - 'IE', - 'IEEE', - 'IFM', - 'IKANO', - 'IL', - 'IM', - 'IMAMAT', - 'IMDB', - 'IMMO', - 'IMMOBILIEN', - 'IN', - 'INC', - 'INDUSTRIES', - 'INFINITI', - 'INFO', - 'ING', - 'INK', - 'INSTITUTE', - 'INSURANCE', - 'INSURE', - 'INT', - 'INTERNATIONAL', - 'INTUIT', - 'INVESTMENTS', - 'IO', - 'IPIRANGA', - 'IQ', - 'IR', - 'IRISH', - 'IS', - 'ISMAILI', - 'IST', - 'ISTANBUL', - 'IT', - 'ITAU', - 'ITV', - 'JAGUAR', - 'JAVA', - 'JCB', - 'JE', - 'JEEP', - 'JETZT', - 'JEWELRY', - 'JIO', - 'JLL', - 'JM', - 'JMP', - 'JNJ', - 'JO', - 'JOBS', - 'JOBURG', - 'JOT', - 'JOY', - 'JP', - 'JPMORGAN', - 'JPRS', - 'JUEGOS', - 'JUNIPER', - 'KAUFEN', - 'KDDI', - 'KE', - 'KERRYHOTELS', - 'KERRYLOGISTICS', - 'KERRYPROPERTIES', - 'KFH', - 'KG', - 'KH', - 'KI', - 'KIA', - 'KIM', - 'KINDER', - 'KINDLE', - 'KITCHEN', - 'KIWI', - 'KM', - 'KN', - 'KOELN', - 'KOMATSU', - 'KOSHER', - 'KP', - 'KPMG', - 'KPN', - 'KR', - 'KRD', - 'KRED', - 'KUOKGROUP', - 'KW', - 'KY', - 'KYOTO', - 'KZ', - 'LA', - 'LACAIXA', - 'LAMBORGHINI', - 'LAMER', - 'LANCASTER', - 'LANCIA', - 'LAND', - 'LANDROVER', - 'LANXESS', - 'LASALLE', - 'LAT', - 'LATINO', - 'LATROBE', - 'LAW', - 'LAWYER', - 'LB', - 'LC', - 'LDS', - 'LEASE', - 'LECLERC', - 'LEFRAK', - 'LEGAL', - 'LEGO', - 'LEXUS', - 'LGBT', - 'LI', - 'LIDL', - 'LIFE', - 'LIFEINSURANCE', - 'LIFESTYLE', - 'LIGHTING', - 'LIKE', - 'LILLY', - 'LIMITED', - 'LIMO', - 'LINCOLN', - 'LINDE', - 'LINK', - 'LIPSY', - 'LIVE', - 'LIVING', - 'LK', - 'LLC', - 'LLP', - 'LOAN', - 'LOANS', - 'LOCKER', - 'LOCUS', - 'LOFT', - 'LOL', - 'LONDON', - 'LOTTE', - 'LOTTO', - 'LOVE', - 'LPL', - 'LPLFINANCIAL', - 'LR', - 'LS', - 'LT', - 'LTD', - 'LTDA', - 'LU', - 'LUNDBECK', - 'LUXE', - 'LUXURY', - 'LV', - 'LY', - 'MA', - 'MACYS', - 'MADRID', - 'MAIF', - 'MAISON', - 'MAKEUP', - 'MAN', - 'MANAGEMENT', - 'MANGO', - 'MAP', - 'MARKET', - 'MARKETING', - 'MARKETS', - 'MARRIOTT', - 'MARSHALLS', - 'MASERATI', - 'MATTEL', - 'MBA', - 'MC', - 'MCKINSEY', - 'MD', - 'ME', - 'MED', - 'MEDIA', - 'MEET', - 'MELBOURNE', - 'MEME', - 'MEMORIAL', - 'MEN', - 'MENU', - 'MERCKMSD', - 'MG', - 'MH', - 'MIAMI', - 'MICROSOFT', - 'MIL', - 'MINI', - 'MINT', - 'MIT', - 'MITSUBISHI', - 'MK', - 'ML', - 'MLB', - 'MLS', - 'MM', - 'MMA', - 'MN', - 'MO', - 'MOBI', - 'MOBILE', - 'MODA', - 'MOE', - 'MOI', - 'MOM', - 'MONASH', - 'MONEY', - 'MONSTER', - 'MORMON', - 'MORTGAGE', - 'MOSCOW', - 'MOTO', - 'MOTORCYCLES', - 'MOV', - 'MOVIE', - 'MP', - 'MQ', - 'MR', - 'MS', - 'MSD', - 'MT', - 'MTN', - 'MTR', - 'MU', - 'MUSEUM', - 'MUSIC', - 'MUTUAL', - 'MV', - 'MW', - 'MX', - 'MY', - 'MZ', - 'NA', - 'NAB', - 'NAGOYA', - 'NAME', - 'NATURA', - 'NAVY', - 'NBA', - 'NC', - 'NE', - 'NEC', - 'NET', - 'NETBANK', - 'NETFLIX', - 'NETWORK', - 'NEUSTAR', - 'NEW', - 'NEWS', - 'NEXT', - 'NEXTDIRECT', - 'NEXUS', - 'NF', - 'NFL', - 'NG', - 'NGO', - 'NHK', - 'NI', - 'NICO', - 'NIKE', - 'NIKON', - 'NINJA', - 'NISSAN', - 'NISSAY', - 'NL', - 'NO', - 'NOKIA', - 'NORTHWESTERNMUTUAL', - 'NORTON', - 'NOW', - 'NOWRUZ', - 'NOWTV', - 'NP', - 'NR', - 'NRA', - 'NRW', - 'NTT', - 'NU', - 'NYC', - 'NZ', - 'OBI', - 'OBSERVER', - 'OFFICE', - 'OKINAWA', - 'OLAYAN', - 'OLAYANGROUP', - 'OLDNAVY', - 'OLLO', - 'OM', - 'OMEGA', - 'ONE', - 'ONG', - 'ONL', - 'ONLINE', - 'OOO', - 'OPEN', - 'ORACLE', - 'ORANGE', - 'ORG', - 'ORGANIC', - 'ORIGINS', - 'OSAKA', - 'OTSUKA', - 'OTT', - 'OVH', - 'PA', - 'PAGE', - 'PANASONIC', - 'PARIS', - 'PARS', - 'PARTNERS', - 'PARTS', - 'PARTY', - 'PASSAGENS', - 'PAY', - 'PCCW', - 'PE', - 'PET', - 'PF', - 'PFIZER', - 'PG', - 'PH', - 'PHARMACY', - 'PHD', - 'PHILIPS', - 'PHONE', - 'PHOTO', - 'PHOTOGRAPHY', - 'PHOTOS', - 'PHYSIO', - 'PICS', - 'PICTET', - 'PICTURES', - 'PID', - 'PIN', - 'PING', - 'PINK', - 'PIONEER', - 'PIZZA', - 'PK', - 'PL', - 'PLACE', - 'PLAY', - 'PLAYSTATION', - 'PLUMBING', - 'PLUS', - 'PM', - 'PN', - 'PNC', - 'POHL', - 'POKER', - 'POLITIE', - 'PORN', - 'POST', - 'PR', - 'PRAMERICA', - 'PRAXI', - 'PRESS', - 'PRIME', - 'PRO', - 'PROD', - 'PRODUCTIONS', - 'PROF', - 'PROGRESSIVE', - 'PROMO', - 'PROPERTIES', - 'PROPERTY', - 'PROTECTION', - 'PRU', - 'PRUDENTIAL', - 'PS', - 'PT', - 'PUB', - 'PW', - 'PWC', - 'PY', - 'QA', - 'QPON', - 'QUEBEC', - 'QUEST', - 'RACING', - 'RADIO', - 'RE', - 'READ', - 'REALESTATE', - 'REALTOR', - 'REALTY', - 'RECIPES', - 'RED', - 'REDSTONE', - 'REDUMBRELLA', - 'REHAB', - 'REISE', - 'REISEN', - 'REIT', - 'RELIANCE', - 'REN', - 'RENT', - 'RENTALS', - 'REPAIR', - 'REPORT', - 'REPUBLICAN', - 'REST', - 'RESTAURANT', - 'REVIEW', - 'REVIEWS', - 'REXROTH', - 'RICH', - 'RICHARDLI', - 'RICOH', - 'RIL', - 'RIO', - 'RIP', - 'RO', - 'ROCHER', - 'ROCKS', - 'RODEO', - 'ROGERS', - 'ROOM', - 'RS', - 'RSVP', - 'RU', - 'RUGBY', - 'RUHR', - 'RUN', - 'RW', - 'RWE', - 'RYUKYU', - 'SA', - 'SAARLAND', - 'SAFE', - 'SAFETY', - 'SAKURA', - 'SALE', - 'SALON', - 'SAMSCLUB', - 'SAMSUNG', - 'SANDVIK', - 'SANDVIKCOROMANT', - 'SANOFI', - 'SAP', - 'SARL', - 'SAS', - 'SAVE', - 'SAXO', - 'SB', - 'SBI', - 'SBS', - 'SC', - 'SCA', - 'SCB', - 'SCHAEFFLER', - 'SCHMIDT', - 'SCHOLARSHIPS', - 'SCHOOL', - 'SCHULE', - 'SCHWARZ', - 'SCIENCE', - 'SCOT', - 'SD', - 'SE', - 'SEARCH', - 'SEAT', - 'SECURE', - 'SECURITY', - 'SEEK', - 'SELECT', - 'SENER', - 'SERVICES', - 'SES', - 'SEVEN', - 'SEW', - 'SEX', - 'SEXY', - 'SFR', - 'SG', - 'SH', - 'SHANGRILA', - 'SHARP', - 'SHAW', - 'SHELL', - 'SHIA', - 'SHIKSHA', - 'SHOES', - 'SHOP', - 'SHOPPING', - 'SHOUJI', - 'SHOW', - 'SHOWTIME', - 'SI', - 'SILK', - 'SINA', - 'SINGLES', - 'SITE', - 'SJ', - 'SK', - 'SKI', - 'SKIN', - 'SKY', - 'SKYPE', - 'SL', - 'SLING', - 'SM', - 'SMART', - 'SMILE', - 'SN', - 'SNCF', - 'SO', - 'SOCCER', - 'SOCIAL', - 'SOFTBANK', - 'SOFTWARE', - 'SOHU', - 'SOLAR', - 'SOLUTIONS', - 'SONG', - 'SONY', - 'SOY', - 'SPA', - 'SPACE', - 'SPORT', - 'SPOT', - 'SR', - 'SRL', - 'SS', - 'ST', - 'STADA', - 'STAPLES', - 'STAR', - 'STATEBANK', - 'STATEFARM', - 'STC', - 'STCGROUP', - 'STOCKHOLM', - 'STORAGE', - 'STORE', - 'STREAM', - 'STUDIO', - 'STUDY', - 'STYLE', - 'SU', - 'SUCKS', - 'SUPPLIES', - 'SUPPLY', - 'SUPPORT', - 'SURF', - 'SURGERY', - 'SUZUKI', - 'SV', - 'SWATCH', - 'SWISS', - 'SX', - 'SY', - 'SYDNEY', - 'SYSTEMS', - 'SZ', - 'TAB', - 'TAIPEI', - 'TALK', - 'TAOBAO', - 'TARGET', - 'TATAMOTORS', - 'TATAR', - 'TATTOO', - 'TAX', - 'TAXI', - 'TC', - 'TCI', - 'TD', - 'TDK', - 'TEAM', - 'TECH', - 'TECHNOLOGY', - 'TEL', - 'TEMASEK', - 'TENNIS', - 'TEVA', - 'TF', - 'TG', - 'TH', - 'THD', - 'THEATER', - 'THEATRE', - 'TIAA', - 'TICKETS', - 'TIENDA', - 'TIFFANY', - 'TIPS', - 'TIRES', - 'TIROL', - 'TJ', - 'TJMAXX', - 'TJX', - 'TK', - 'TKMAXX', - 'TL', - 'TM', - 'TMALL', - 'TN', - 'TO', - 'TODAY', - 'TOKYO', - 'TOOLS', - 'TOP', - 'TORAY', - 'TOSHIBA', - 'TOTAL', - 'TOURS', - 'TOWN', - 'TOYOTA', - 'TOYS', - 'TR', - 'TRADE', - 'TRADING', - 'TRAINING', - 'TRAVEL', - 'TRAVELCHANNEL', - 'TRAVELERS', - 'TRAVELERSINSURANCE', - 'TRUST', - 'TRV', - 'TT', - 'TUBE', - 'TUI', - 'TUNES', - 'TUSHU', - 'TV', - 'TVS', - 'TW', - 'TZ', - 'UA', - 'UBANK', - 'UBS', - 'UG', - 'UK', - 'UNICOM', - 'UNIVERSITY', - 'UNO', - 'UOL', - 'UPS', - 'US', - 'UY', - 'UZ', - 'VA', - 'VACATIONS', - 'VANA', - 'VANGUARD', - 'VC', - 'VE', - 'VEGAS', - 'VENTURES', - 'VERISIGN', - 'VERSICHERUNG', - 'VET', - 'VG', - 'VI', - 'VIAJES', - 'VIDEO', - 'VIG', - 'VIKING', - 'VILLAS', - 'VIN', - 'VIP', - 'VIRGIN', - 'VISA', - 'VISION', - 'VIVA', - 'VIVO', - 'VLAANDEREN', - 'VN', - 'VODKA', - 'VOLKSWAGEN', - 'VOLVO', - 'VOTE', - 'VOTING', - 'VOTO', - 'VOYAGE', - 'VU', - 'VUELOS', - 'WALES', - 'WALMART', - 'WALTER', - 'WANG', - 'WANGGOU', - 'WATCH', - 'WATCHES', - 'WEATHER', - 'WEATHERCHANNEL', - 'WEBCAM', - 'WEBER', - 'WEBSITE', - 'WED', - 'WEDDING', - 'WEIBO', - 'WEIR', - 'WF', - 'WHOSWHO', - 'WIEN', - 'WIKI', - 'WILLIAMHILL', - 'WIN', - 'WINDOWS', - 'WINE', - 'WINNERS', - 'WME', - 'WOLTERSKLUWER', - 'WOODSIDE', - 'WORK', - 'WORKS', - 'WORLD', - 'WOW', - 'WS', - 'WTC', - 'WTF', - 'XBOX', - 'XEROX', - 'XFINITY', - 'XIHUAN', - 'XIN', - 'XN--11B4C3D', - 'XN--1CK2E1B', - 'XN--1QQW23A', - 'XN--2SCRJ9C', - 'XN--30RR7Y', - 'XN--3BST00M', - 'XN--3DS443G', - 'XN--3E0B707E', - 'XN--3HCRJ9C', - 'XN--3PXU8K', - 'XN--42C2D9A', - 'XN--45BR5CYL', - 'XN--45BRJ9C', - 'XN--45Q11C', - 'XN--4DBRK0CE', - 'XN--4GBRIM', - 'XN--54B7FTA0CC', - 'XN--55QW42G', - 'XN--55QX5D', - 'XN--5SU34J936BGSG', - 'XN--5TZM5G', - 'XN--6FRZ82G', - 'XN--6QQ986B3XL', - 'XN--80ADXHKS', - 'XN--80AO21A', - 'XN--80AQECDR1A', - 'XN--80ASEHDB', - 'XN--80ASWG', - 'XN--8Y0A063A', - 'XN--90A3AC', - 'XN--90AE', - 'XN--90AIS', - 'XN--9DBQ2A', - 'XN--9ET52U', - 'XN--9KRT00A', - 'XN--B4W605FERD', - 'XN--BCK1B9A5DRE4C', - 'XN--C1AVG', - 'XN--C2BR7G', - 'XN--CCK2B3B', - 'XN--CCKWCXETD', - 'XN--CG4BKI', - 'XN--CLCHC0EA0B2G2A9GCD', - 'XN--CZR694B', - 'XN--CZRS0T', - 'XN--CZRU2D', - 'XN--D1ACJ3B', - 'XN--D1ALF', - 'XN--E1A4C', - 'XN--ECKVDTC9D', - 'XN--EFVY88H', - 'XN--FCT429K', - 'XN--FHBEI', - 'XN--FIQ228C5HS', - 'XN--FIQ64B', - 'XN--FIQS8S', - 'XN--FIQZ9S', - 'XN--FJQ720A', - 'XN--FLW351E', - 'XN--FPCRJ9C3D', - 'XN--FZC2C9E2C', - 'XN--FZYS8D69UVGM', - 'XN--G2XX48C', - 'XN--GCKR3F0F', - 'XN--GECRJ9C', - 'XN--GK3AT1E', - 'XN--H2BREG3EVE', - 'XN--H2BRJ9C', - 'XN--H2BRJ9C8C', - 'XN--HXT814E', - 'XN--I1B6B1A6A2E', - 'XN--IMR513N', - 'XN--IO0A7I', - 'XN--J1AEF', - 'XN--J1AMH', - 'XN--J6W193G', - 'XN--JLQ480N2RG', - 'XN--JLQ61U9W7B', - 'XN--JVR189M', - 'XN--KCRX77D1X4A', - 'XN--KPRW13D', - 'XN--KPRY57D', - 'XN--KPUT3I', - 'XN--L1ACC', - 'XN--LGBBAT1AD8J', - 'XN--MGB9AWBF', - 'XN--MGBA3A3EJT', - 'XN--MGBA3A4F16A', - 'XN--MGBA7C0BBN0A', - 'XN--MGBAAKC7DVF', - 'XN--MGBAAM7A8H', - 'XN--MGBAB2BD', - 'XN--MGBAH1A3HJKRD', - 'XN--MGBAI9AZGQP6J', - 'XN--MGBAYH7GPA', - 'XN--MGBBH1A', - 'XN--MGBBH1A71E', - 'XN--MGBC0A9AZCG', - 'XN--MGBCA7DZDO', - 'XN--MGBCPQ6GPA1A', - 'XN--MGBERP4A5D4AR', - 'XN--MGBGU82A', - 'XN--MGBI4ECEXP', - 'XN--MGBPL2FH', - 'XN--MGBT3DHD', - 'XN--MGBTX2B', - 'XN--MGBX4CD0AB', - 'XN--MIX891F', - 'XN--MK1BU44C', - 'XN--MXTQ1M', - 'XN--NGBC5AZD', - 'XN--NGBE9E0A', - 'XN--NGBRX', - 'XN--NODE', - 'XN--NQV7F', - 'XN--NQV7FS00EMA', - 'XN--NYQY26A', - 'XN--O3CW4H', - 'XN--OGBPF8FL', - 'XN--OTU796D', - 'XN--P1ACF', - 'XN--P1AI', - 'XN--PGBS0DH', - 'XN--PSSY2U', - 'XN--Q7CE6A', - 'XN--Q9JYB4C', - 'XN--QCKA1PMC', - 'XN--QXA6A', - 'XN--QXAM', - 'XN--RHQV96G', - 'XN--ROVU88B', - 'XN--RVC1E0AM3E', - 'XN--S9BRJ9C', - 'XN--SES554G', - 'XN--T60B56A', - 'XN--TCKWE', - 'XN--TIQ49XQYJ', - 'XN--UNUP4Y', - 'XN--VERMGENSBERATER-CTB', - 'XN--VERMGENSBERATUNG-PWB', - 'XN--VHQUV', - 'XN--VUQ861B', - 'XN--W4R85EL8FHU5DNRA', - 'XN--W4RS40L', - 'XN--WGBH1C', - 'XN--WGBL6A', - 'XN--XHQ521B', - 'XN--XKC2AL3HYE2A', - 'XN--XKC2DL3A5EE0H', - 'XN--Y9A3AQ', - 'XN--YFRO4I67O', - 'XN--YGBI2AMMX', - 'XN--ZFR164B', - 'XXX', - 'XYZ', - 'YACHTS', - 'YAHOO', - 'YAMAXUN', - 'YANDEX', - 'YE', - 'YODOBASHI', - 'YOGA', - 'YOKOHAMA', - 'YOU', - 'YOUTUBE', - 'YT', - 'YUN', - 'ZA', - 'ZAPPOS', - 'ZARA', - 'ZERO', - 'ZIP', - 'ZM', - 'ZONE', - 'ZUERICH', - 'ZW', -]; +const tlds = { + 'A': [ + 'AAA', + 'AARP', + 'ABARTH', + 'ABB', + 'ABBOTT', + 'ABBVIE', + 'ABC', + 'ABLE', + 'ABOGADO', + 'ABUDHABI', + 'AC', + 'ACADEMY', + 'ACCENTURE', + 'ACCOUNTANT', + 'ACCOUNTANTS', + 'ACO', + 'ACTOR', + 'AD', + 'ADAC', + 'ADS', + 'ADULT', + 'AE', + 'AEG', + 'AERO', + 'AETNA', + 'AF', + 'AFL', + 'AFRICA', + 'AG', + 'AGAKHAN', + 'AGENCY', + 'AI', + 'AIG', + 'AIRBUS', + 'AIRFORCE', + 'AIRTEL', + 'AKDN', + 'AL', + 'ALFAROMEO', + 'ALIBABA', + 'ALIPAY', + 'ALLFINANZ', + 'ALLSTATE', + 'ALLY', + 'ALSACE', + 'ALSTOM', + 'AM', + 'AMAZON', + 'AMERICANEXPRESS', + 'AMERICANFAMILY', + 'AMEX', + 'AMFAM', + 'AMICA', + 'AMSTERDAM', + 'ANALYTICS', + 'ANDROID', + 'ANQUAN', + 'ANZ', + 'AO', + 'AOL', + 'APARTMENTS', + 'APP', + 'APPLE', + 'AQ', + 'AQUARELLE', + 'AR', + 'ARAB', + 'ARAMCO', + 'ARCHI', + 'ARMY', + 'ARPA', + 'ART', + 'ARTE', + 'AS', + 'ASDA', + 'ASIA', + 'ASSOCIATES', + 'AT', + 'ATHLETA', + 'ATTORNEY', + 'AU', + 'AUCTION', + 'AUDI', + 'AUDIBLE', + 'AUDIO', + 'AUSPOST', + 'AUTHOR', + 'AUTO', + 'AUTOS', + 'AVIANCA', + 'AW', + 'AWS', + 'AX', + 'AXA', + 'AZ', + 'AZURE', + ], + 'B': [ + 'BA', + 'BABY', + 'BAIDU', + 'BANAMEX', + 'BANANAREPUBLIC', + 'BAND', + 'BANK', + 'BAR', + 'BARCELONA', + 'BARCLAYCARD', + 'BARCLAYS', + 'BAREFOOT', + 'BARGAINS', + 'BASEBALL', + 'BASKETBALL', + 'BAUHAUS', + 'BAYERN', + 'BB', + 'BBC', + 'BBT', + 'BBVA', + 'BCG', + 'BCN', + 'BD', + 'BE', + 'BEATS', + 'BEAUTY', + 'BEER', + 'BENTLEY', + 'BERLIN', + 'BEST', + 'BESTBUY', + 'BET', + 'BF', + 'BG', + 'BH', + 'BHARTI', + 'BI', + 'BIBLE', + 'BID', + 'BIKE', + 'BING', + 'BINGO', + 'BIO', + 'BIZ', + 'BJ', + 'BLACK', + 'BLACKFRIDAY', + 'BLOCKBUSTER', + 'BLOG', + 'BLOOMBERG', + 'BLUE', + 'BM', + 'BMS', + 'BMW', + 'BN', + 'BNPPARIBAS', + 'BO', + 'BOATS', + 'BOEHRINGER', + 'BOFA', + 'BOM', + 'BOND', + 'BOO', + 'BOOK', + 'BOOKING', + 'BOSCH', + 'BOSTIK', + 'BOSTON', + 'BOT', + 'BOUTIQUE', + 'BOX', + 'BR', + 'BRADESCO', + 'BRIDGESTONE', + 'BROADWAY', + 'BROKER', + 'BROTHER', + 'BRUSSELS', + 'BS', + 'BT', + 'BUDAPEST', + 'BUGATTI', + 'BUILD', + 'BUILDERS', + 'BUSINESS', + 'BUY', + 'BUZZ', + 'BV', + 'BW', + 'BY', + 'BZ', + 'BZH', + ], + 'C': [ + 'CA', + 'CAB', + 'CAFE', + 'CAL', + 'CALL', + 'CALVINKLEIN', + 'CAM', + 'CAMERA', + 'CAMP', + 'CANCERRESEARCH', + 'CANON', + 'CAPETOWN', + 'CAPITAL', + 'CAPITALONE', + 'CAR', + 'CARAVAN', + 'CARDS', + 'CARE', + 'CAREER', + 'CAREERS', + 'CARS', + 'CASA', + 'CASE', + 'CASH', + 'CASINO', + 'CAT', + 'CATERING', + 'CATHOLIC', + 'CBA', + 'CBN', + 'CBRE', + 'CBS', + 'CC', + 'CD', + 'CENTER', + 'CEO', + 'CERN', + 'CF', + 'CFA', + 'CFD', + 'CG', + 'CH', + 'CHANEL', + 'CHANNEL', + 'CHARITY', + 'CHASE', + 'CHAT', + 'CHEAP', + 'CHINTAI', + 'CHRISTMAS', + 'CHROME', + 'CHURCH', + 'CI', + 'CIPRIANI', + 'CIRCLE', + 'CISCO', + 'CITADEL', + 'CITI', + 'CITIC', + 'CITY', + 'CITYEATS', + 'CK', + 'CL', + 'CLAIMS', + 'CLEANING', + 'CLICK', + 'CLINIC', + 'CLINIQUE', + 'CLOTHING', + 'CLOUD', + 'CLUB', + 'CLUBMED', + 'CM', + 'CN', + 'CO', + 'COACH', + 'CODES', + 'COFFEE', + 'COLLEGE', + 'COLOGNE', + 'COM', + 'COMCAST', + 'COMMBANK', + 'COMMUNITY', + 'COMPANY', + 'COMPARE', + 'COMPUTER', + 'COMSEC', + 'CONDOS', + 'CONSTRUCTION', + 'CONSULTING', + 'CONTACT', + 'CONTRACTORS', + 'COOKING', + 'COOKINGCHANNEL', + 'COOL', + 'COOP', + 'CORSICA', + 'COUNTRY', + 'COUPON', + 'COUPONS', + 'COURSES', + 'CPA', + 'CR', + 'CREDIT', + 'CREDITCARD', + 'CREDITUNION', + 'CRICKET', + 'CROWN', + 'CRS', + 'CRUISE', + 'CRUISES', + 'CSC', + 'CU', + 'CUISINELLA', + 'CV', + 'CW', + 'CX', + 'CY', + 'CYMRU', + 'CYOU', + 'CZ', + ], + 'D': [ + 'DABUR', + 'DAD', + 'DANCE', + 'DATA', + 'DATE', + 'DATING', + 'DATSUN', + 'DAY', + 'DCLK', + 'DDS', + 'DE', + 'DEAL', + 'DEALER', + 'DEALS', + 'DEGREE', + 'DELIVERY', + 'DELL', + 'DELOITTE', + 'DELTA', + 'DEMOCRAT', + 'DENTAL', + 'DENTIST', + 'DESI', + 'DESIGN', + 'DEV', + 'DHL', + 'DIAMONDS', + 'DIET', + 'DIGITAL', + 'DIRECT', + 'DIRECTORY', + 'DISCOUNT', + 'DISCOVER', + 'DISH', + 'DIY', + 'DJ', + 'DK', + 'DM', + 'DNP', + 'DO', + 'DOCS', + 'DOCTOR', + 'DOG', + 'DOMAINS', + 'DOT', + 'DOWNLOAD', + 'DRIVE', + 'DTV', + 'DUBAI', + 'DUNLOP', + 'DUPONT', + 'DURBAN', + 'DVAG', + 'DVR', + 'DZ', + ], + 'E': [ + 'EARTH', + 'EAT', + 'EC', + 'ECO', + 'EDEKA', + 'EDU', + 'EDUCATION', + 'EE', + 'EG', + 'EMAIL', + 'EMERCK', + 'ENERGY', + 'ENGINEER', + 'ENGINEERING', + 'ENTERPRISES', + 'EPSON', + 'EQUIPMENT', + 'ER', + 'ERICSSON', + 'ERNI', + 'ES', + 'ESQ', + 'ESTATE', + 'ET', + 'ETISALAT', + 'EU', + 'EUROVISION', + 'EUS', + 'EVENTS', + 'EXCHANGE', + 'EXPERT', + 'EXPOSED', + 'EXPRESS', + 'EXTRASPACE', + ], + 'F': [ + 'FAGE', + 'FAIL', + 'FAIRWINDS', + 'FAITH', + 'FAMILY', + 'FAN', + 'FANS', + 'FARM', + 'FARMERS', + 'FASHION', + 'FAST', + 'FEDEX', + 'FEEDBACK', + 'FERRARI', + 'FERRERO', + 'FI', + 'FIAT', + 'FIDELITY', + 'FIDO', + 'FILM', + 'FINAL', + 'FINANCE', + 'FINANCIAL', + 'FIRE', + 'FIRESTONE', + 'FIRMDALE', + 'FISH', + 'FISHING', + 'FIT', + 'FITNESS', + 'FJ', + 'FK', + 'FLICKR', + 'FLIGHTS', + 'FLIR', + 'FLORIST', + 'FLOWERS', + 'FLY', + 'FM', + 'FO', + 'FOO', + 'FOOD', + 'FOODNETWORK', + 'FOOTBALL', + 'FORD', + 'FOREX', + 'FORSALE', + 'FORUM', + 'FOUNDATION', + 'FOX', + 'FR', + 'FREE', + 'FRESENIUS', + 'FRL', + 'FROGANS', + 'FRONTDOOR', + 'FRONTIER', + 'FTR', + 'FUJITSU', + 'FUN', + 'FUND', + 'FURNITURE', + 'FUTBOL', + 'FYI', + ], + 'G': [ + 'GA', + 'GAL', + 'GALLERY', + 'GALLO', + 'GALLUP', + 'GAME', + 'GAMES', + 'GAP', + 'GARDEN', + 'GAY', + 'GB', + 'GBIZ', + 'GD', + 'GDN', + 'GE', + 'GEA', + 'GENT', + 'GENTING', + 'GEORGE', + 'GF', + 'GG', + 'GGEE', + 'GH', + 'GI', + 'GIFT', + 'GIFTS', + 'GIVES', + 'GIVING', + 'GL', + 'GLASS', + 'GLE', + 'GLOBAL', + 'GLOBO', + 'GM', + 'GMAIL', + 'GMBH', + 'GMO', + 'GMX', + 'GN', + 'GODADDY', + 'GOLD', + 'GOLDPOINT', + 'GOLF', + 'GOO', + 'GOODYEAR', + 'GOOG', + 'GOOGLE', + 'GOP', + 'GOT', + 'GOV', + 'GP', + 'GQ', + 'GR', + 'GRAINGER', + 'GRAPHICS', + 'GRATIS', + 'GREEN', + 'GRIPE', + 'GROCERY', + 'GROUP', + 'GS', + 'GT', + 'GU', + 'GUARDIAN', + 'GUCCI', + 'GUGE', + 'GUIDE', + 'GUITARS', + 'GURU', + 'GW', + 'GY', + ], + 'H': [ + 'HAIR', + 'HAMBURG', + 'HANGOUT', + 'HAUS', + 'HBO', + 'HDFC', + 'HDFCBANK', + 'HEALTH', + 'HEALTHCARE', + 'HELP', + 'HELSINKI', + 'HERE', + 'HERMES', + 'HGTV', + 'HIPHOP', + 'HISAMITSU', + 'HITACHI', + 'HIV', + 'HK', + 'HKT', + 'HM', + 'HN', + 'HOCKEY', + 'HOLDINGS', + 'HOLIDAY', + 'HOMEDEPOT', + 'HOMEGOODS', + 'HOMES', + 'HOMESENSE', + 'HONDA', + 'HORSE', + 'HOSPITAL', + 'HOST', + 'HOSTING', + 'HOT', + 'HOTELES', + 'HOTELS', + 'HOTMAIL', + 'HOUSE', + 'HOW', + 'HR', + 'HSBC', + 'HT', + 'HU', + 'HUGHES', + 'HYATT', + 'HYUNDAI', + ], + 'I': [ + 'IBM', + 'ICBC', + 'ICE', + 'ICU', + 'ID', + 'IE', + 'IEEE', + 'IFM', + 'IKANO', + 'IL', + 'IM', + 'IMAMAT', + 'IMDB', + 'IMMO', + 'IMMOBILIEN', + 'IN', + 'INC', + 'INDUSTRIES', + 'INFINITI', + 'INFO', + 'ING', + 'INK', + 'INSTITUTE', + 'INSURANCE', + 'INSURE', + 'INT', + 'INTERNATIONAL', + 'INTUIT', + 'INVESTMENTS', + 'IO', + 'IPIRANGA', + 'IQ', + 'IR', + 'IRISH', + 'IS', + 'ISMAILI', + 'IST', + 'ISTANBUL', + 'IT', + 'ITAU', + 'ITV', + ], + 'J': [ + 'JAGUAR', + 'JAVA', + 'JCB', + 'JE', + 'JEEP', + 'JETZT', + 'JEWELRY', + 'JIO', + 'JLL', + 'JM', + 'JMP', + 'JNJ', + 'JO', + 'JOBS', + 'JOBURG', + 'JOT', + 'JOY', + 'JP', + 'JPMORGAN', + 'JPRS', + 'JUEGOS', + 'JUNIPER', + ], + 'K': [ + 'KAUFEN', + 'KDDI', + 'KE', + 'KERRYHOTELS', + 'KERRYLOGISTICS', + 'KERRYPROPERTIES', + 'KFH', + 'KG', + 'KH', + 'KI', + 'KIA', + 'KIM', + 'KINDER', + 'KINDLE', + 'KITCHEN', + 'KIWI', + 'KM', + 'KN', + 'KOELN', + 'KOMATSU', + 'KOSHER', + 'KP', + 'KPMG', + 'KPN', + 'KR', + 'KRD', + 'KRED', + 'KUOKGROUP', + 'KW', + 'KY', + 'KYOTO', + 'KZ', + ], + 'L': [ + 'LA', + 'LACAIXA', + 'LAMBORGHINI', + 'LAMER', + 'LANCASTER', + 'LANCIA', + 'LAND', + 'LANDROVER', + 'LANXESS', + 'LASALLE', + 'LAT', + 'LATINO', + 'LATROBE', + 'LAW', + 'LAWYER', + 'LB', + 'LC', + 'LDS', + 'LEASE', + 'LECLERC', + 'LEFRAK', + 'LEGAL', + 'LEGO', + 'LEXUS', + 'LGBT', + 'LI', + 'LIDL', + 'LIFE', + 'LIFEINSURANCE', + 'LIFESTYLE', + 'LIGHTING', + 'LIKE', + 'LILLY', + 'LIMITED', + 'LIMO', + 'LINCOLN', + 'LINDE', + 'LINK', + 'LIPSY', + 'LIVE', + 'LIVING', + 'LK', + 'LLC', + 'LLP', + 'LOAN', + 'LOANS', + 'LOCKER', + 'LOCUS', + 'LOFT', + 'LOL', + 'LONDON', + 'LOTTE', + 'LOTTO', + 'LOVE', + 'LPL', + 'LPLFINANCIAL', + 'LR', + 'LS', + 'LT', + 'LTD', + 'LTDA', + 'LU', + 'LUNDBECK', + 'LUXE', + 'LUXURY', + 'LV', + 'LY', + ], + 'M': [ + 'MA', + 'MACYS', + 'MADRID', + 'MAIF', + 'MAISON', + 'MAKEUP', + 'MAN', + 'MANAGEMENT', + 'MANGO', + 'MAP', + 'MARKET', + 'MARKETING', + 'MARKETS', + 'MARRIOTT', + 'MARSHALLS', + 'MASERATI', + 'MATTEL', + 'MBA', + 'MC', + 'MCKINSEY', + 'MD', + 'ME', + 'MED', + 'MEDIA', + 'MEET', + 'MELBOURNE', + 'MEME', + 'MEMORIAL', + 'MEN', + 'MENU', + 'MERCKMSD', + 'MG', + 'MH', + 'MIAMI', + 'MICROSOFT', + 'MIL', + 'MINI', + 'MINT', + 'MIT', + 'MITSUBISHI', + 'MK', + 'ML', + 'MLB', + 'MLS', + 'MM', + 'MMA', + 'MN', + 'MO', + 'MOBI', + 'MOBILE', + 'MODA', + 'MOE', + 'MOI', + 'MOM', + 'MONASH', + 'MONEY', + 'MONSTER', + 'MORMON', + 'MORTGAGE', + 'MOSCOW', + 'MOTO', + 'MOTORCYCLES', + 'MOV', + 'MOVIE', + 'MP', + 'MQ', + 'MR', + 'MS', + 'MSD', + 'MT', + 'MTN', + 'MTR', + 'MU', + 'MUSEUM', + 'MUSIC', + 'MUTUAL', + 'MV', + 'MW', + 'MX', + 'MY', + 'MZ', + ], + 'N': [ + 'NA', + 'NAB', + 'NAGOYA', + 'NAME', + 'NATURA', + 'NAVY', + 'NBA', + 'NC', + 'NE', + 'NEC', + 'NET', + 'NETBANK', + 'NETFLIX', + 'NETWORK', + 'NEUSTAR', + 'NEW', + 'NEWS', + 'NEXT', + 'NEXTDIRECT', + 'NEXUS', + 'NF', + 'NFL', + 'NG', + 'NGO', + 'NHK', + 'NI', + 'NICO', + 'NIKE', + 'NIKON', + 'NINJA', + 'NISSAN', + 'NISSAY', + 'NL', + 'NO', + 'NOKIA', + 'NORTHWESTERNMUTUAL', + 'NORTON', + 'NOW', + 'NOWRUZ', + 'NOWTV', + 'NP', + 'NR', + 'NRA', + 'NRW', + 'NTT', + 'NU', + 'NYC', + 'NZ', + ], + 'O': [ + 'OBI', + 'OBSERVER', + 'OFFICE', + 'OKINAWA', + 'OLAYAN', + 'OLAYANGROUP', + 'OLDNAVY', + 'OLLO', + 'OM', + 'OMEGA', + 'ONE', + 'ONG', + 'ONL', + 'ONLINE', + 'OOO', + 'OPEN', + 'ORACLE', + 'ORANGE', + 'ORG', + 'ORGANIC', + 'ORIGINS', + 'OSAKA', + 'OTSUKA', + 'OTT', + 'OVH', + ], + 'P': [ + 'PA', + 'PAGE', + 'PANASONIC', + 'PARIS', + 'PARS', + 'PARTNERS', + 'PARTS', + 'PARTY', + 'PASSAGENS', + 'PAY', + 'PCCW', + 'PE', + 'PET', + 'PF', + 'PFIZER', + 'PG', + 'PH', + 'PHARMACY', + 'PHD', + 'PHILIPS', + 'PHONE', + 'PHOTO', + 'PHOTOGRAPHY', + 'PHOTOS', + 'PHYSIO', + 'PICS', + 'PICTET', + 'PICTURES', + 'PID', + 'PIN', + 'PING', + 'PINK', + 'PIONEER', + 'PIZZA', + 'PK', + 'PL', + 'PLACE', + 'PLAY', + 'PLAYSTATION', + 'PLUMBING', + 'PLUS', + 'PM', + 'PN', + 'PNC', + 'POHL', + 'POKER', + 'POLITIE', + 'PORN', + 'POST', + 'PR', + 'PRAMERICA', + 'PRAXI', + 'PRESS', + 'PRIME', + 'PRO', + 'PROD', + 'PRODUCTIONS', + 'PROF', + 'PROGRESSIVE', + 'PROMO', + 'PROPERTIES', + 'PROPERTY', + 'PROTECTION', + 'PRU', + 'PRUDENTIAL', + 'PS', + 'PT', + 'PUB', + 'PW', + 'PWC', + 'PY', + ], + 'Q': [ + 'QA', + 'QPON', + 'QUEBEC', + 'QUEST', + ], + 'R': [ + 'RACING', + 'RADIO', + 'RE', + 'READ', + 'REALESTATE', + 'REALTOR', + 'REALTY', + 'RECIPES', + 'RED', + 'REDSTONE', + 'REDUMBRELLA', + 'REHAB', + 'REISE', + 'REISEN', + 'REIT', + 'RELIANCE', + 'REN', + 'RENT', + 'RENTALS', + 'REPAIR', + 'REPORT', + 'REPUBLICAN', + 'REST', + 'RESTAURANT', + 'REVIEW', + 'REVIEWS', + 'REXROTH', + 'RICH', + 'RICHARDLI', + 'RICOH', + 'RIL', + 'RIO', + 'RIP', + 'RO', + 'ROCHER', + 'ROCKS', + 'RODEO', + 'ROGERS', + 'ROOM', + 'RS', + 'RSVP', + 'RU', + 'RUGBY', + 'RUHR', + 'RUN', + 'RW', + 'RWE', + 'RYUKYU', + ], + 'S': [ + 'SA', + 'SAARLAND', + 'SAFE', + 'SAFETY', + 'SAKURA', + 'SALE', + 'SALON', + 'SAMSCLUB', + 'SAMSUNG', + 'SANDVIK', + 'SANDVIKCOROMANT', + 'SANOFI', + 'SAP', + 'SARL', + 'SAS', + 'SAVE', + 'SAXO', + 'SB', + 'SBI', + 'SBS', + 'SC', + 'SCA', + 'SCB', + 'SCHAEFFLER', + 'SCHMIDT', + 'SCHOLARSHIPS', + 'SCHOOL', + 'SCHULE', + 'SCHWARZ', + 'SCIENCE', + 'SCOT', + 'SD', + 'SE', + 'SEARCH', + 'SEAT', + 'SECURE', + 'SECURITY', + 'SEEK', + 'SELECT', + 'SENER', + 'SERVICES', + 'SES', + 'SEVEN', + 'SEW', + 'SEX', + 'SEXY', + 'SFR', + 'SG', + 'SH', + 'SHANGRILA', + 'SHARP', + 'SHAW', + 'SHELL', + 'SHIA', + 'SHIKSHA', + 'SHOES', + 'SHOP', + 'SHOPPING', + 'SHOUJI', + 'SHOW', + 'SHOWTIME', + 'SI', + 'SILK', + 'SINA', + 'SINGLES', + 'SITE', + 'SJ', + 'SK', + 'SKI', + 'SKIN', + 'SKY', + 'SKYPE', + 'SL', + 'SLING', + 'SM', + 'SMART', + 'SMILE', + 'SN', + 'SNCF', + 'SO', + 'SOCCER', + 'SOCIAL', + 'SOFTBANK', + 'SOFTWARE', + 'SOHU', + 'SOLAR', + 'SOLUTIONS', + 'SONG', + 'SONY', + 'SOY', + 'SPA', + 'SPACE', + 'SPORT', + 'SPOT', + 'SR', + 'SRL', + 'SS', + 'ST', + 'STADA', + 'STAPLES', + 'STAR', + 'STATEBANK', + 'STATEFARM', + 'STC', + 'STCGROUP', + 'STOCKHOLM', + 'STORAGE', + 'STORE', + 'STREAM', + 'STUDIO', + 'STUDY', + 'STYLE', + 'SU', + 'SUCKS', + 'SUPPLIES', + 'SUPPLY', + 'SUPPORT', + 'SURF', + 'SURGERY', + 'SUZUKI', + 'SV', + 'SWATCH', + 'SWISS', + 'SX', + 'SY', + 'SYDNEY', + 'SYSTEMS', + 'SZ', + ], + 'T': [ + 'TAB', + 'TAIPEI', + 'TALK', + 'TAOBAO', + 'TARGET', + 'TATAMOTORS', + 'TATAR', + 'TATTOO', + 'TAX', + 'TAXI', + 'TC', + 'TCI', + 'TD', + 'TDK', + 'TEAM', + 'TECH', + 'TECHNOLOGY', + 'TEL', + 'TEMASEK', + 'TENNIS', + 'TEVA', + 'TF', + 'TG', + 'TH', + 'THD', + 'THEATER', + 'THEATRE', + 'TIAA', + 'TICKETS', + 'TIENDA', + 'TIFFANY', + 'TIPS', + 'TIRES', + 'TIROL', + 'TJ', + 'TJMAXX', + 'TJX', + 'TK', + 'TKMAXX', + 'TL', + 'TM', + 'TMALL', + 'TN', + 'TO', + 'TODAY', + 'TOKYO', + 'TOOLS', + 'TOP', + 'TORAY', + 'TOSHIBA', + 'TOTAL', + 'TOURS', + 'TOWN', + 'TOYOTA', + 'TOYS', + 'TR', + 'TRADE', + 'TRADING', + 'TRAINING', + 'TRAVEL', + 'TRAVELCHANNEL', + 'TRAVELERS', + 'TRAVELERSINSURANCE', + 'TRUST', + 'TRV', + 'TT', + 'TUBE', + 'TUI', + 'TUNES', + 'TUSHU', + 'TV', + 'TVS', + 'TW', + 'TZ', + ], + 'U': [ + 'UA', + 'UBANK', + 'UBS', + 'UG', + 'UK', + 'UNICOM', + 'UNIVERSITY', + 'UNO', + 'UOL', + 'UPS', + 'US', + 'UY', + 'UZ', + ], + 'V': [ + 'VA', + 'VACATIONS', + 'VANA', + 'VANGUARD', + 'VC', + 'VE', + 'VEGAS', + 'VENTURES', + 'VERISIGN', + 'VERSICHERUNG', + 'VET', + 'VG', + 'VI', + 'VIAJES', + 'VIDEO', + 'VIG', + 'VIKING', + 'VILLAS', + 'VIN', + 'VIP', + 'VIRGIN', + 'VISA', + 'VISION', + 'VIVA', + 'VIVO', + 'VLAANDEREN', + 'VN', + 'VODKA', + 'VOLKSWAGEN', + 'VOLVO', + 'VOTE', + 'VOTING', + 'VOTO', + 'VOYAGE', + 'VU', + 'VUELOS', + ], + 'W': [ + 'WALES', + 'WALMART', + 'WALTER', + 'WANG', + 'WANGGOU', + 'WATCH', + 'WATCHES', + 'WEATHER', + 'WEATHERCHANNEL', + 'WEBCAM', + 'WEBER', + 'WEBSITE', + 'WED', + 'WEDDING', + 'WEIBO', + 'WEIR', + 'WF', + 'WHOSWHO', + 'WIEN', + 'WIKI', + 'WILLIAMHILL', + 'WIN', + 'WINDOWS', + 'WINE', + 'WINNERS', + 'WME', + 'WOLTERSKLUWER', + 'WOODSIDE', + 'WORK', + 'WORKS', + 'WORLD', + 'WOW', + 'WS', + 'WTC', + 'WTF', + ], + 'X': [ + 'XBOX', + 'XEROX', + 'XFINITY', + 'XIHUAN', + 'XIN', + 'XN--11B4C3D', + 'XN--1CK2E1B', + 'XN--1QQW23A', + 'XN--2SCRJ9C', + 'XN--30RR7Y', + 'XN--3BST00M', + 'XN--3DS443G', + 'XN--3E0B707E', + 'XN--3HCRJ9C', + 'XN--3PXU8K', + 'XN--42C2D9A', + 'XN--45BR5CYL', + 'XN--45BRJ9C', + 'XN--45Q11C', + 'XN--4DBRK0CE', + 'XN--4GBRIM', + 'XN--54B7FTA0CC', + 'XN--55QW42G', + 'XN--55QX5D', + 'XN--5SU34J936BGSG', + 'XN--5TZM5G', + 'XN--6FRZ82G', + 'XN--6QQ986B3XL', + 'XN--80ADXHKS', + 'XN--80AO21A', + 'XN--80AQECDR1A', + 'XN--80ASEHDB', + 'XN--80ASWG', + 'XN--8Y0A063A', + 'XN--90A3AC', + 'XN--90AE', + 'XN--90AIS', + 'XN--9DBQ2A', + 'XN--9ET52U', + 'XN--9KRT00A', + 'XN--B4W605FERD', + 'XN--BCK1B9A5DRE4C', + 'XN--C1AVG', + 'XN--C2BR7G', + 'XN--CCK2B3B', + 'XN--CCKWCXETD', + 'XN--CG4BKI', + 'XN--CLCHC0EA0B2G2A9GCD', + 'XN--CZR694B', + 'XN--CZRS0T', + 'XN--CZRU2D', + 'XN--D1ACJ3B', + 'XN--D1ALF', + 'XN--E1A4C', + 'XN--ECKVDTC9D', + 'XN--EFVY88H', + 'XN--FCT429K', + 'XN--FHBEI', + 'XN--FIQ228C5HS', + 'XN--FIQ64B', + 'XN--FIQS8S', + 'XN--FIQZ9S', + 'XN--FJQ720A', + 'XN--FLW351E', + 'XN--FPCRJ9C3D', + 'XN--FZC2C9E2C', + 'XN--FZYS8D69UVGM', + 'XN--G2XX48C', + 'XN--GCKR3F0F', + 'XN--GECRJ9C', + 'XN--GK3AT1E', + 'XN--H2BREG3EVE', + 'XN--H2BRJ9C', + 'XN--H2BRJ9C8C', + 'XN--HXT814E', + 'XN--I1B6B1A6A2E', + 'XN--IMR513N', + 'XN--IO0A7I', + 'XN--J1AEF', + 'XN--J1AMH', + 'XN--J6W193G', + 'XN--JLQ480N2RG', + 'XN--JLQ61U9W7B', + 'XN--JVR189M', + 'XN--KCRX77D1X4A', + 'XN--KPRW13D', + 'XN--KPRY57D', + 'XN--KPUT3I', + 'XN--L1ACC', + 'XN--LGBBAT1AD8J', + 'XN--MGB9AWBF', + 'XN--MGBA3A3EJT', + 'XN--MGBA3A4F16A', + 'XN--MGBA7C0BBN0A', + 'XN--MGBAAKC7DVF', + 'XN--MGBAAM7A8H', + 'XN--MGBAB2BD', + 'XN--MGBAH1A3HJKRD', + 'XN--MGBAI9AZGQP6J', + 'XN--MGBAYH7GPA', + 'XN--MGBBH1A', + 'XN--MGBBH1A71E', + 'XN--MGBC0A9AZCG', + 'XN--MGBCA7DZDO', + 'XN--MGBCPQ6GPA1A', + 'XN--MGBERP4A5D4AR', + 'XN--MGBGU82A', + 'XN--MGBI4ECEXP', + 'XN--MGBPL2FH', + 'XN--MGBT3DHD', + 'XN--MGBTX2B', + 'XN--MGBX4CD0AB', + 'XN--MIX891F', + 'XN--MK1BU44C', + 'XN--MXTQ1M', + 'XN--NGBC5AZD', + 'XN--NGBE9E0A', + 'XN--NGBRX', + 'XN--NODE', + 'XN--NQV7F', + 'XN--NQV7FS00EMA', + 'XN--NYQY26A', + 'XN--O3CW4H', + 'XN--OGBPF8FL', + 'XN--OTU796D', + 'XN--P1ACF', + 'XN--P1AI', + 'XN--PGBS0DH', + 'XN--PSSY2U', + 'XN--Q7CE6A', + 'XN--Q9JYB4C', + 'XN--QCKA1PMC', + 'XN--QXA6A', + 'XN--QXAM', + 'XN--RHQV96G', + 'XN--ROVU88B', + 'XN--RVC1E0AM3E', + 'XN--S9BRJ9C', + 'XN--SES554G', + 'XN--T60B56A', + 'XN--TCKWE', + 'XN--TIQ49XQYJ', + 'XN--UNUP4Y', + 'XN--VERMGENSBERATER-CTB', + 'XN--VERMGENSBERATUNG-PWB', + 'XN--VHQUV', + 'XN--VUQ861B', + 'XN--W4R85EL8FHU5DNRA', + 'XN--W4RS40L', + 'XN--WGBH1C', + 'XN--WGBL6A', + 'XN--XHQ521B', + 'XN--XKC2AL3HYE2A', + 'XN--XKC2DL3A5EE0H', + 'XN--Y9A3AQ', + 'XN--YFRO4I67O', + 'XN--YGBI2AMMX', + 'XN--ZFR164B', + 'XXX', + 'XYZ', + ], + 'Y': [ + 'YACHTS', + 'YAHOO', + 'YAMAXUN', + 'YANDEX', + 'YE', + 'YODOBASHI', + 'YOGA', + 'YOKOHAMA', + 'YOU', + 'YOUTUBE', + 'YT', + 'YUN', + ], + 'Z': [ + 'ZA', + 'ZAPPOS', + 'ZARA', + 'ZERO', + 'ZIP', + 'ZM', + 'ZONE', + 'ZUERICH', + 'ZW', + ], +}; From fa5e993d299e10e4a52d5360e2f98672e0a3c884 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 4 Jan 2022 15:29:03 +0530 Subject: [PATCH 11/14] tried fix for mip bug --- .../lib/src/message_input/message_input.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index 180c3d12..43806c5b 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -938,7 +938,9 @@ class MessageInputState extends State // Reset the og attachment if the text doesn't contain any url if (matchedUrls.isEmpty) { - _effectiveController.clearOGAttachment(); + _effectiveController + ..text = value + ..clearOGAttachment(); return; } From dcbdfc32eadd34673cec6383a5ed7975203c7b6b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 Jan 2022 12:48:42 +0530 Subject: [PATCH 12/14] added mip didUpdateWidget for focusnode --- packages/stream_chat_flutter/CHANGELOG.md | 4 ++++ .../lib/src/message_input/message_input.dart | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 258e4cc5..db9fef23 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -12,6 +12,10 @@ - Videos can now be auto-played in `FullScreenMedia` +🔄 Changed + +- Add `didUpdateWidget` override in `MessageInput` widget to handle changes to `focusNode`. + ## 3.3.2 - Updated `stream_chat_flutter_core` dependency to [`3.3.1`](https://pub.dev/packages/stream_chat_flutter_core/changelog). diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index e55fac19..c9443007 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -352,7 +352,7 @@ class MessageInput extends StatefulWidget { class MessageInputState extends State with RestorationMixin { final _imagePicker = ImagePicker(); - late final _focusNode = widget.focusNode ?? FocusNode(); + late var _focusNode = widget.focusNode ?? FocusNode(); bool _inputEnabled = true; bool get _commandEnabled => _effectiveController.value.command != null; bool _showCommandsOverlay = false; @@ -419,6 +419,13 @@ class MessageInputState extends State _controller!.dispose(); _controller = null; } + + // Update _focusNode + if (widget.focusNode != null && oldWidget.focusNode != widget.focusNode) { + _focusNode.removeListener(_focusNodeListener); + _focusNode = widget.focusNode!; + _focusNode.addListener(_focusNodeListener); + } } @override From 8bd826d08d58b98d2766d60d1cf384999e8c9e45 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 Jan 2022 12:50:02 +0530 Subject: [PATCH 13/14] change var to focusnode --- .../lib/src/message_input/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index c9443007..54170b4c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -352,7 +352,7 @@ class MessageInput extends StatefulWidget { class MessageInputState extends State with RestorationMixin { final _imagePicker = ImagePicker(); - late var _focusNode = widget.focusNode ?? FocusNode(); + late FocusNode _focusNode = widget.focusNode ?? FocusNode(); bool _inputEnabled = true; bool get _commandEnabled => _effectiveController.value.command != null; bool _showCommandsOverlay = false; From e21d32941adcd4f36323757ae6905b4cc82ad434 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 11 Jan 2022 16:07:11 +0530 Subject: [PATCH 14/14] add listener to effective controller --- .../lib/src/message_input/message_input.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart index 54170b4c..adcb046a 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input.dart @@ -398,11 +398,7 @@ class MessageInputState extends State if (widget.messageInputController == null) { _createLocalController(); } else { - _effectiveController.textEditingController - .removeListener(_onChangedDebounced); - _effectiveController.textEditingController - .addListener(_onChangedDebounced); - if (!_isEditing && _timeOut <= 0) _startSlowMode(); + _initialiseEffectiveController(); } _focusNode.addListener(_focusNodeListener); } @@ -418,6 +414,7 @@ class MessageInputState extends State unregisterFromRestoration(_controller!); _controller!.dispose(); _controller = null; + _initialiseEffectiveController(); } // Update _focusNode @@ -447,6 +444,13 @@ class MessageInputState extends State int _timeOut = 0; Timer? _slowModeTimer; + void _initialiseEffectiveController() { + _effectiveController.textEditingController + .removeListener(_onChangedDebounced); + _effectiveController.textEditingController.addListener(_onChangedDebounced); + if (!_isEditing && _timeOut <= 0) _startSlowMode(); + } + void _startSlowMode() { if (!mounted) { return;