feat(ui, core): handle ogAttachment modification via message_input_controller.dart.
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
@@ -335,17 +335,6 @@ class MessageInput extends StatefulWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
MessageInputState createState() => MessageInputState();
|
MessageInputState createState() => MessageInputState();
|
||||||
|
|
||||||
/// Use this method to get the current [StreamChatState] instance
|
|
||||||
static MessageInputState of(BuildContext context) {
|
|
||||||
MessageInputState? messageInputState;
|
|
||||||
messageInputState = context.findAncestorStateOfType<MessageInputState>();
|
|
||||||
assert(
|
|
||||||
messageInputState != null,
|
|
||||||
'You must have a MessageInput widget as ancestor of your widget tree',
|
|
||||||
);
|
|
||||||
return messageInputState!;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State of [MessageInput]
|
/// State of [MessageInput]
|
||||||
@@ -520,11 +509,11 @@ class MessageInputState extends State<MessageInput>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (_ogAttachment != null)
|
else if (_effectiveController.ogAttachment != null)
|
||||||
OGAttachmentPreview(
|
OGAttachmentPreview(
|
||||||
attachment: _ogAttachment!,
|
attachment: _effectiveController.ogAttachment!,
|
||||||
onDismissPreviewPressed: () {
|
onDismissPreviewPressed: () {
|
||||||
setState(() => _ogAttachment = null);
|
_effectiveController.clearOGAttachment();
|
||||||
_focusNode.unfocus();
|
_focusNode.unfocus();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -929,7 +918,6 @@ class MessageInputState extends State<MessageInput>
|
|||||||
return context.translations.writeAMessageLabel;
|
return context.translations.writeAMessageLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
Attachment? _ogAttachment;
|
|
||||||
String? _lastSearchedContainsUrlText;
|
String? _lastSearchedContainsUrlText;
|
||||||
CancelableOperation? _enrichUrlOperation;
|
CancelableOperation? _enrichUrlOperation;
|
||||||
|
|
||||||
@@ -948,14 +936,16 @@ class MessageInputState extends State<MessageInput>
|
|||||||
|
|
||||||
// Reset the og attachment if the text doesn't contain any url
|
// Reset the og attachment if the text doesn't contain any url
|
||||||
if (matchedUrls.isEmpty) {
|
if (matchedUrls.isEmpty) {
|
||||||
setState(() => _ogAttachment = null);
|
_effectiveController.clearOGAttachment();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final firstMatchedUrl = matchedUrls.first.group(0)!;
|
final firstMatchedUrl = matchedUrls.first.group(0)!;
|
||||||
|
|
||||||
// If the parsed url matches the ogAttachment url, don't do anything
|
// 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;
|
final client = StreamChat.of(context).client;
|
||||||
|
|
||||||
@@ -964,11 +954,11 @@ class MessageInputState extends State<MessageInput>
|
|||||||
).then(
|
).then(
|
||||||
(ogAttachment) {
|
(ogAttachment) {
|
||||||
final attachment = Attachment.fromOGAttachment(ogAttachment);
|
final attachment = Attachment.fromOGAttachment(ogAttachment);
|
||||||
setState(() => _ogAttachment = attachment);
|
_effectiveController.setOGAttachment(attachment);
|
||||||
},
|
},
|
||||||
onError: (error, stackTrace) {
|
onError: (error, stackTrace) {
|
||||||
// Reset the ogAttachment if there was an error
|
// Reset the ogAttachment if there was an error
|
||||||
setState(() => _ogAttachment = null);
|
_effectiveController.clearOGAttachment();
|
||||||
widget.onError?.call(error, stackTrace);
|
widget.onError?.call(error, stackTrace);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1470,14 +1460,6 @@ class MessageInputState extends State<MessageInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
/// Adds an attachment to the [messageInputController.attachments] map
|
||||||
void _addAttachments(Iterable<Attachment> attachments) {
|
void _addAttachments(Iterable<Attachment> attachments) {
|
||||||
final limit = widget.attachmentLimit;
|
final limit = widget.attachmentLimit;
|
||||||
@@ -1612,21 +1594,14 @@ class MessageInputState extends State<MessageInput>
|
|||||||
|
|
||||||
/// Sends the current message
|
/// Sends the current message
|
||||||
Future<void> sendMessage() async {
|
Future<void> sendMessage() async {
|
||||||
var message = _effectiveController.value;
|
final skipEnrichUrl = _effectiveController.ogAttachment == null;
|
||||||
|
|
||||||
// Add ogAttachment if present
|
var message = _effectiveController.value;
|
||||||
final skipEnrichUrl = _ogAttachment == null;
|
|
||||||
if (!skipEnrichUrl) {
|
|
||||||
message = message.copyWith(
|
|
||||||
attachments: [...message.attachments, _ogAttachment!],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var shouldKeepFocus = widget.shouldKeepFocusAfterMessage;
|
var shouldKeepFocus = widget.shouldKeepFocusAfterMessage;
|
||||||
|
|
||||||
shouldKeepFocus ??= !_commandEnabled;
|
shouldKeepFocus ??= !_commandEnabled;
|
||||||
|
|
||||||
_ogAttachment = null;
|
|
||||||
_effectiveController.reset();
|
_effectiveController.reset();
|
||||||
|
|
||||||
if (widget.preMessageSending != null) {
|
if (widget.preMessageSending != null) {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:stream_chat/stream_chat.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].
|
/// A value listenable builder related to a [Message].
|
||||||
///
|
///
|
||||||
@@ -17,33 +18,46 @@ class MessageInputController extends ValueNotifier<Message> {
|
|||||||
/// message.
|
/// message.
|
||||||
factory MessageInputController({
|
factory MessageInputController({
|
||||||
Message? message,
|
Message? message,
|
||||||
|
Map<RegExp, TextStyle>? textPatternStyle,
|
||||||
}) =>
|
}) =>
|
||||||
MessageInputController._(
|
MessageInputController._(
|
||||||
initialMessage: message ?? Message(),
|
initialMessage: message ?? Message(),
|
||||||
|
textPatternStyle: textPatternStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates a controller for an editable text field from an initial [text].
|
/// Creates a controller for an editable text field from an initial [text].
|
||||||
factory MessageInputController.fromText(String? text) =>
|
factory MessageInputController.fromText(
|
||||||
|
String? text, {
|
||||||
|
Map<RegExp, TextStyle>? textPatternStyle,
|
||||||
|
}) =>
|
||||||
MessageInputController._(
|
MessageInputController._(
|
||||||
initialMessage: Message(text: text),
|
initialMessage: Message(text: text),
|
||||||
|
textPatternStyle: textPatternStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates a controller for an editable text field from initial
|
/// Creates a controller for an editable text field from initial
|
||||||
/// [attachments].
|
/// [attachments].
|
||||||
factory MessageInputController.fromAttachments(
|
factory MessageInputController.fromAttachments(
|
||||||
List<Attachment> attachments,
|
List<Attachment> attachments, {
|
||||||
) =>
|
Map<RegExp, TextStyle>? textPatternStyle,
|
||||||
|
}) =>
|
||||||
MessageInputController._(
|
MessageInputController._(
|
||||||
initialMessage: Message(attachments: attachments),
|
initialMessage: Message(attachments: attachments),
|
||||||
|
textPatternStyle: textPatternStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
MessageInputController._({
|
MessageInputController._({
|
||||||
required Message initialMessage,
|
required Message initialMessage,
|
||||||
}) : _textEditingController =
|
Map<RegExp, TextStyle>? textPatternStyle,
|
||||||
TextEditingController.fromValue(TextEditingValue(
|
}) : _textEditingController = MessageTextFieldController.fromValue(
|
||||||
text: initialMessage.text ?? '',
|
initialMessage.text == null
|
||||||
composing: TextRange.collapsed(initialMessage.text?.length ?? 0),
|
? const TextEditingValue()
|
||||||
)),
|
: TextEditingValue(
|
||||||
|
text: initialMessage.text!,
|
||||||
|
composing: TextRange.collapsed(initialMessage.text!.length),
|
||||||
|
),
|
||||||
|
textPatternStyle: textPatternStyle,
|
||||||
|
),
|
||||||
_initialMessage = initialMessage,
|
_initialMessage = initialMessage,
|
||||||
super(initialMessage) {
|
super(initialMessage) {
|
||||||
addListener(_textEditingSyncer);
|
addListener(_textEditingSyncer);
|
||||||
@@ -73,8 +87,9 @@ class MessageInputController extends ValueNotifier<Message> {
|
|||||||
Message get message => value;
|
Message get message => value;
|
||||||
|
|
||||||
/// Returns the controller of the text field linked to this controller.
|
/// Returns the controller of the text field linked to this controller.
|
||||||
TextEditingController get textEditingController => _textEditingController;
|
MessageTextFieldController get textEditingController =>
|
||||||
final TextEditingController _textEditingController;
|
_textEditingController;
|
||||||
|
final MessageTextFieldController _textEditingController;
|
||||||
|
|
||||||
/// Returns the text of the message.
|
/// Returns the text of the message.
|
||||||
String get text => _textEditingController.text;
|
String get text => _textEditingController.text;
|
||||||
@@ -174,6 +189,28 @@ class MessageInputController extends ValueNotifier<Message> {
|
|||||||
attachments = [];
|
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.
|
/// Returns the list of mentioned users in the message.
|
||||||
List<User> get mentionedUsers => value.mentionedUsers;
|
List<User> get mentionedUsers => value.mentionedUsers;
|
||||||
|
|
||||||
@@ -220,9 +257,8 @@ class MessageInputController extends ValueNotifier<Message> {
|
|||||||
/// Sets the [value] to the initial [Message] value.
|
/// Sets the [value] to the initial [Message] value.
|
||||||
void reset({bool resetId = true}) {
|
void reset({bool resetId = true}) {
|
||||||
if (resetId) {
|
if (resetId) {
|
||||||
_initialMessage = _initialMessage.copyWith(
|
final newId = const Uuid().v4();
|
||||||
id: const Uuid().v4(),
|
_initialMessage = _initialMessage.copyWith(id: newId);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
value = _initialMessage;
|
value = _initialMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<RegExp, TextStyle>? 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 = <TextSpan>[];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user