feat(ui): add support for OG Attachment preview.
Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
@@ -493,6 +493,7 @@ class Channel {
|
|||||||
Future<SendMessageResponse> sendMessage(
|
Future<SendMessageResponse> sendMessage(
|
||||||
Message message, {
|
Message message, {
|
||||||
bool skipPush = false,
|
bool skipPush = false,
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
}) async {
|
}) async {
|
||||||
_checkInitialized();
|
_checkInitialized();
|
||||||
// Cancelling previous completer in case it's called again in the process
|
// Cancelling previous completer in case it's called again in the process
|
||||||
@@ -540,6 +541,7 @@ class Channel {
|
|||||||
id!,
|
id!,
|
||||||
type,
|
type,
|
||||||
skipPush: skipPush,
|
skipPush: skipPush,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
);
|
);
|
||||||
state!.addMessage(response.message);
|
state!.addMessage(response.message);
|
||||||
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
||||||
@@ -556,7 +558,10 @@ class Channel {
|
|||||||
///
|
///
|
||||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||||
/// before actually updating the message.
|
/// before actually updating the message.
|
||||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
Future<UpdateMessageResponse> updateMessage(
|
||||||
|
Message message, {
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
|
}) async {
|
||||||
final originalMessage = message;
|
final originalMessage = message;
|
||||||
|
|
||||||
// Cancelling previous completer in case it's called again in the process
|
// Cancelling previous completer in case it's called again in the process
|
||||||
@@ -594,7 +599,10 @@ class Channel {
|
|||||||
message = await attachmentsUploadCompleter.future;
|
message = await attachmentsUploadCompleter.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
final response = await _client.updateMessage(message);
|
final response = await _client.updateMessage(
|
||||||
|
message,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
);
|
||||||
|
|
||||||
final m = response.message.copyWith(
|
final m = response.message.copyWith(
|
||||||
ownReactions: message.ownReactions,
|
ownReactions: message.ownReactions,
|
||||||
@@ -624,12 +632,14 @@ class Channel {
|
|||||||
Message message, {
|
Message message, {
|
||||||
Map<String, Object?>? set,
|
Map<String, Object?>? set,
|
||||||
List<String>? unset,
|
List<String>? unset,
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _client.partialUpdateMessage(
|
final response = await _client.partialUpdateMessage(
|
||||||
message.id,
|
message.id,
|
||||||
set: set,
|
set: set,
|
||||||
unset: unset,
|
unset: unset,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
final updatedMessage = response.message.copyWith(
|
final updatedMessage = response.message.copyWith(
|
||||||
|
|||||||
@@ -1169,12 +1169,14 @@ class StreamChatClient {
|
|||||||
String channelId,
|
String channelId,
|
||||||
String channelType, {
|
String channelType, {
|
||||||
bool skipPush = false,
|
bool skipPush = false,
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
}) =>
|
}) =>
|
||||||
_chatApi.message.sendMessage(
|
_chatApi.message.sendMessage(
|
||||||
channelId,
|
channelId,
|
||||||
channelType,
|
channelType,
|
||||||
message,
|
message,
|
||||||
skipPush: skipPush,
|
skipPush: skipPush,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Lists all the message replies for the [parentId]
|
/// Lists all the message replies for the [parentId]
|
||||||
@@ -1198,8 +1200,14 @@ class StreamChatClient {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// Update the given message
|
/// Update the given message
|
||||||
Future<UpdateMessageResponse> updateMessage(Message message) =>
|
Future<UpdateMessageResponse> updateMessage(
|
||||||
_chatApi.message.updateMessage(message);
|
Message message, {
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
|
}) =>
|
||||||
|
_chatApi.message.updateMessage(
|
||||||
|
message,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
);
|
||||||
|
|
||||||
/// Partially update the given [messageId]
|
/// Partially update the given [messageId]
|
||||||
/// Use [set] to define values to be set
|
/// Use [set] to define values to be set
|
||||||
@@ -1208,11 +1216,13 @@ class StreamChatClient {
|
|||||||
String messageId, {
|
String messageId, {
|
||||||
Map<String, Object?>? set,
|
Map<String, Object?>? set,
|
||||||
List<String>? unset,
|
List<String>? unset,
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
}) =>
|
}) =>
|
||||||
_chatApi.message.partialUpdateMessage(
|
_chatApi.message.partialUpdateMessage(
|
||||||
messageId,
|
messageId,
|
||||||
set: set,
|
set: set,
|
||||||
unset: unset,
|
unset: unset,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Deletes the given message
|
/// Deletes the given message
|
||||||
|
|||||||
@@ -16,12 +16,14 @@ class MessageApi {
|
|||||||
String channelType,
|
String channelType,
|
||||||
Message message, {
|
Message message, {
|
||||||
bool skipPush = false,
|
bool skipPush = false,
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'/channels/$channelType/$channelId/message',
|
'/channels/$channelType/$channelId/message',
|
||||||
data: {
|
data: {
|
||||||
'message': message,
|
'message': message,
|
||||||
'skip_push': skipPush,
|
'skip_push': skipPush,
|
||||||
|
'skip_enrich_url': skipEnrichUrl,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return SendMessageResponse.fromJson(response.data);
|
return SendMessageResponse.fromJson(response.data);
|
||||||
@@ -51,11 +53,15 @@ class MessageApi {
|
|||||||
|
|
||||||
/// Updates the given [message]
|
/// Updates the given [message]
|
||||||
Future<UpdateMessageResponse> updateMessage(
|
Future<UpdateMessageResponse> updateMessage(
|
||||||
Message message,
|
Message message, {
|
||||||
) async {
|
bool skipEnrichUrl = false,
|
||||||
|
}) async {
|
||||||
final response = await _client.post(
|
final response = await _client.post(
|
||||||
'/messages/${message.id}',
|
'/messages/${message.id}',
|
||||||
data: {'message': message},
|
data: {
|
||||||
|
'message': message,
|
||||||
|
'skip_enrich_url': skipEnrichUrl,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return UpdateMessageResponse.fromJson(response.data);
|
return UpdateMessageResponse.fromJson(response.data);
|
||||||
}
|
}
|
||||||
@@ -67,12 +73,14 @@ class MessageApi {
|
|||||||
String messageId, {
|
String messageId, {
|
||||||
Map<String, Object?>? set,
|
Map<String, Object?>? set,
|
||||||
List<String>? unset,
|
List<String>? unset,
|
||||||
|
bool skipEnrichUrl = false,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await _client.put(
|
final response = await _client.put(
|
||||||
'/messages/$messageId',
|
'/messages/$messageId',
|
||||||
data: {
|
data: {
|
||||||
if (set != null) 'set': set,
|
if (set != null) 'set': set,
|
||||||
if (unset != null) 'unset': unset,
|
if (unset != null) 'unset': unset,
|
||||||
|
'skip_enrich_url': skipEnrichUrl,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return UpdateMessageResponse.fromJson(response.data);
|
return UpdateMessageResponse.fromJson(response.data);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:json_annotation/json_annotation.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/action.dart';
|
||||||
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||||
import 'package:stream_chat/src/core/util/serializer.dart';
|
import 'package:stream_chat/src/core/util/serializer.dart';
|
||||||
@@ -66,6 +67,21 @@ class Attachment extends Equatable {
|
|||||||
topLevelFields + dbSpecificTopLevelFields,
|
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,
|
///The attachment type based on the URL resource. This can be: audio,
|
||||||
///image or video
|
///image or video
|
||||||
final String? type;
|
final String? type;
|
||||||
@@ -229,6 +245,33 @@ class Attachment extends Equatable {
|
|||||||
extraData: extraData ?? this.extraData,
|
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
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -354,6 +354,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
late final _focusNode = widget.focusNode ?? FocusNode();
|
late final _focusNode = widget.focusNode ?? FocusNode();
|
||||||
bool _inputEnabled = true;
|
bool _inputEnabled = true;
|
||||||
|
|
||||||
bool get _commandEnabled => _effectiveController.value.command != null;
|
bool get _commandEnabled => _effectiveController.value.command != null;
|
||||||
bool _showCommandsOverlay = false;
|
bool _showCommandsOverlay = false;
|
||||||
bool _showMentionsOverlay = false;
|
bool _showMentionsOverlay = false;
|
||||||
@@ -371,6 +372,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
_effectiveController.value.status != MessageSendingStatus.sending;
|
_effectiveController.value.status != MessageSendingStatus.sending;
|
||||||
|
|
||||||
RestorableMessageInputController? _controller;
|
RestorableMessageInputController? _controller;
|
||||||
|
|
||||||
MessageInputController get _effectiveController =>
|
MessageInputController get _effectiveController =>
|
||||||
widget.messageInputController ?? _controller!.value;
|
widget.messageInputController ?? _controller!.value;
|
||||||
|
|
||||||
@@ -517,6 +519,14 @@ class MessageInputState extends State<MessageInput>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
else if (_ogAttachment != null)
|
||||||
|
OGAttachmentPreview(
|
||||||
|
attachment: _ogAttachment!,
|
||||||
|
onDismissPreviewPressed: () {
|
||||||
|
setState(() => _ogAttachment = null);
|
||||||
|
_focusNode.unfocus();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
@@ -538,7 +548,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (_isEditing) {
|
if (!_isEditing) {
|
||||||
child = Material(
|
child = Material(
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
child: child,
|
child: child,
|
||||||
@@ -896,6 +906,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
_actionsShrunk = value.isNotEmpty && actionsLength > 1;
|
_actionsShrunk = value.isNotEmpty && actionsLength > 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_checkContainsUrlDebounced.call([value, context]);
|
||||||
_checkCommands(value, context);
|
_checkCommands(value, context);
|
||||||
_checkMentions(value, context);
|
_checkMentions(value, context);
|
||||||
_checkEmoji(value, context);
|
_checkEmoji(value, context);
|
||||||
@@ -918,14 +929,52 @@ class MessageInputState extends State<MessageInput>
|
|||||||
return context.translations.writeAMessageLabel;
|
return context.translations.writeAMessageLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkEmoji(String s, BuildContext context) {
|
Attachment? _ogAttachment;
|
||||||
if (s.isNotEmpty &&
|
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.baseOffset > 0 &&
|
||||||
_effectiveController.text
|
_effectiveController.text
|
||||||
.substring(
|
.substring(0, _effectiveController.baseOffset)
|
||||||
0,
|
|
||||||
_effectiveController.baseOffset,
|
|
||||||
)
|
|
||||||
.contains(':')) {
|
.contains(':')) {
|
||||||
final textToSelection = _effectiveController.text.substring(
|
final textToSelection = _effectiveController.text.substring(
|
||||||
0,
|
0,
|
||||||
@@ -941,14 +990,11 @@ class MessageInputState extends State<MessageInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkMentions(String s, BuildContext context) {
|
void _checkMentions(String value, BuildContext context) {
|
||||||
if (s.isNotEmpty &&
|
if (value.isNotEmpty &&
|
||||||
_effectiveController.baseOffset > 0 &&
|
_effectiveController.baseOffset > 0 &&
|
||||||
_effectiveController.text
|
_effectiveController.text
|
||||||
.substring(
|
.substring(0, _effectiveController.baseOffset)
|
||||||
0,
|
|
||||||
_effectiveController.baseOffset,
|
|
||||||
)
|
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.last
|
.last
|
||||||
.contains('@')) {
|
.contains('@')) {
|
||||||
@@ -964,11 +1010,11 @@ class MessageInputState extends State<MessageInput>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _checkCommands(String s, BuildContext context) {
|
void _checkCommands(String value, BuildContext context) {
|
||||||
if (s.startsWith('/')) {
|
if (value.startsWith('/')) {
|
||||||
final allCommands = StreamChannel.of(context).channel.config?.commands;
|
final allCommands = StreamChannel.of(context).channel.config?.commands;
|
||||||
final command =
|
final command =
|
||||||
allCommands?.firstWhereOrNull((it) => it.name == s.substring(1));
|
allCommands?.firstWhereOrNull((it) => it.name == value.substring(1));
|
||||||
if (command != null) {
|
if (command != null) {
|
||||||
return _setCommand(command);
|
return _setCommand(command);
|
||||||
} else if (!_showCommandsOverlay) {
|
} else if (!_showCommandsOverlay) {
|
||||||
@@ -1030,10 +1076,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final splits = _effectiveController.text
|
final splits = _effectiveController.text
|
||||||
.substring(
|
.substring(0, _effectiveController.selectionStart)
|
||||||
0,
|
|
||||||
_effectiveController.selectionStart,
|
|
||||||
)
|
|
||||||
.split('@');
|
.split('@');
|
||||||
final query = splits.last.toLowerCase();
|
final query = splits.last.toLowerCase();
|
||||||
|
|
||||||
@@ -1082,10 +1125,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
}
|
}
|
||||||
|
|
||||||
final splits = _effectiveController.text
|
final splits = _effectiveController.text
|
||||||
.substring(
|
.substring(0, _effectiveController.baseOffset)
|
||||||
0,
|
|
||||||
_effectiveController.baseOffset,
|
|
||||||
)
|
|
||||||
.split(':');
|
.split(':');
|
||||||
|
|
||||||
final query = splits.last.toLowerCase();
|
final query = splits.last.toLowerCase();
|
||||||
@@ -1222,11 +1262,7 @@ class MessageInputState extends State<MessageInput>
|
|||||||
focusElevation: 0,
|
focusElevation: 0,
|
||||||
hoverElevation: 0,
|
hoverElevation: 0,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_effectiveController.value = _effectiveController.value.copyWith(
|
_effectiveController.removeAttachmentById(attachment.id);
|
||||||
attachments: _effectiveController.attachments
|
|
||||||
.where((it) => it.id != attachment.id)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
fillColor:
|
fillColor:
|
||||||
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5),
|
_streamChatTheme.colorTheme.textHighEmphasis.withOpacity(0.5),
|
||||||
@@ -1572,10 +1608,19 @@ class MessageInputState extends State<MessageInput>
|
|||||||
Future<void> sendMessage() async {
|
Future<void> sendMessage() async {
|
||||||
var message = _effectiveController.value;
|
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;
|
var shouldKeepFocus = widget.shouldKeepFocusAfterMessage;
|
||||||
|
|
||||||
shouldKeepFocus ??= !_commandEnabled;
|
shouldKeepFocus ??= !_commandEnabled;
|
||||||
|
|
||||||
|
_ogAttachment = null;
|
||||||
_effectiveController.reset();
|
_effectiveController.reset();
|
||||||
|
|
||||||
if (widget.preMessageSending != null) {
|
if (widget.preMessageSending != null) {
|
||||||
@@ -1590,10 +1635,16 @@ class MessageInputState extends State<MessageInput>
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Future sendingFuture;
|
Future sendingFuture;
|
||||||
if (!_isEditing) {
|
if (_isEditing) {
|
||||||
sendingFuture = channel.sendMessage(message);
|
sendingFuture = channel.updateMessage(
|
||||||
|
message,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
sendingFuture = channel.updateMessage(message);
|
sendingFuture = channel.sendMessage(
|
||||||
|
message,
|
||||||
|
skipEnrichUrl: skipEnrichUrl,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldKeepFocus) {
|
if (shouldKeepFocus) {
|
||||||
@@ -1702,3 +1753,73 @@ class MessageInputState extends State<MessageInput>
|
|||||||
super.didChangeDependencies();
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user