update flutter uikit to 1.4.0

This commit is contained in:
anonymous 2023-01-14 09:23:46 +08:00
parent e0bb3127c8
commit 121e8c295f
25 changed files with 480 additions and 91 deletions

View File

@ -1,3 +1,9 @@
## 1.4.0
* Add: Text translation. Long press the text messages and choose `Translate`. This function can be turn off by `showTranslation` from `ToolTipsConfig`.
* Optimize: The long press pop-up location.
* Optimize: keyboard pop-up event.
## 1.3.2
* Fix: Text input field height, after choosing to mention someone.

View File

@ -963,7 +963,7 @@ packages:
name: tencent_cloud_chat_sdk
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.7"
version: "5.0.8"
tencent_cloud_chat_uikit:
dependency: "direct main"
description:
@ -998,14 +998,14 @@ packages:
name: tencent_im_base
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.17"
version: "1.0.19"
tencent_im_sdk_plugin_platform_interface:
dependency: transitive
description:
name: tencent_im_sdk_plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.10"
version: "0.3.11"
tencent_im_sdk_plugin_web:
dependency: "direct main"
description:

BIN
images/translate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -3,10 +3,13 @@ import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
// ignore: unnecessary_import
import 'package:flutter/foundation.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/models/link_preview_content.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/life_cycle/chat_life_cycle.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_model_tools.dart';
@ -53,6 +56,7 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
V2TimGroupInfo? _groupInfo;
String groupMemberListSeq = "0";
List<V2TimGroupMemberFullInfo?>? groupMemberList = [];
V2TimGroupInfo? get groupInfo => _groupInfo;
set groupInfo(V2TimGroupInfo? value) {
@ -1214,6 +1218,26 @@ class TUIChatSeparateViewModel extends ChangeNotifier {
}
}
translateText(V2TimMessage message) async {
final String originText = message.textElem?.text ?? "";
final String deviceLocale =
WidgetsBinding.instance?.window.locale.toLanguageTag() ?? "en";
final String targetMessage = deviceLocale.split("-")[0];
final translatedText =
await _messageService.translateText(originText, targetMessage);
final LocalCustomDataModel localCustomData = LocalCustomDataModel.fromMap(
json.decode(TencentUtils.checkString(message.localCustomData) ?? "{}"));
localCustomData.translatedText = translatedText;
final result = await TencentImSDKPlugin.v2TIMManager.v2TIMMessageManager
.setLocalCustomData(
msgID: message.msgID!,
localCustomData: json.encode(localCustomData.toMap()));
if (result.code == 0 && TencentUtils.checkString(message.msgID) != null) {
updateMessageFromController(msgID: message.msgID!);
}
}
updateMultiSelectStatus(bool isSelect) {
_isMultiSelect = isSelect;
if (!isSelect) {

View File

@ -798,4 +798,18 @@ class MessageServiceImpl extends MessageService {
}
return result;
}
@override
Future<String> translateText(String text, String target) async {
final result = await TencentImSDKPlugin.v2TIMManager
.getMessageManager()
.translateText(texts: [text], targetLanguage: target);
if (result.code != 0) {
_coreService.callOnCallback(TIMCallback(
type: TIMCallbackType.API_ERROR,
errorMsg: result.desc,
errorCode: result.code));
}
return result.data?[text] ?? "";
}
}

View File

@ -204,4 +204,6 @@ abstract class MessageService {
required int imageType, // messageType为图片消息是有效
required bool isSnapshot, // messageType为视频消息是有效
});
Future<String> translateText(String text, String target);
}

File diff suppressed because one or more lines are too long

View File

@ -656,5 +656,9 @@
"k_0td1p3f": "保存中…",
"k_1klqdh1": "仅限汉字、英文、数字和下划线",
"k_03el5lp": "未填写",
"k_1ui0gai": "搜索指定内容"
"k_1ui0gai": "搜索指定内容",
"k_003nvk2": "消息",
"k_03agld7": "群提示",
"k_002wkr3": "翻译",
"k_13g4hxv": "翻译完成"
}

File diff suppressed because one or more lines are too long

View File

@ -245,6 +245,37 @@ class MessageUtils {
margin: const EdgeInsets.symmetric(vertical: 10), child: child);
}
static String getAbstractMessageAsync(V2TimMessage message,
List<V2TimGroupMemberFullInfo?> groupMemberList) {
final msgType = message.elemType;
switch (msgType) {
case MessageElemType.V2TIM_ELEM_TYPE_CUSTOM:
return handleCustomMessageString(message);
case MessageElemType.V2TIM_ELEM_TYPE_SOUND:
return TIM_t("[语音]");
case MessageElemType.V2TIM_ELEM_TYPE_TEXT:
return message.textElem!.text as String;
case MessageElemType.V2TIM_ELEM_TYPE_FACE:
return TIM_t("[表情]");
case MessageElemType.V2TIM_ELEM_TYPE_FILE:
final String? option2 = message.fileElem!.fileName ?? "";
return TIM_t_para("[文件] {{option2}}", "[文件] $option2")(
option2: option2);
case MessageElemType.V2TIM_ELEM_TYPE_GROUP_TIPS:
return TIM_t("群提示");
case MessageElemType.V2TIM_ELEM_TYPE_IMAGE:
return TIM_t("[图片]");
case MessageElemType.V2TIM_ELEM_TYPE_VIDEO:
return TIM_t("[视频]");
case MessageElemType.V2TIM_ELEM_TYPE_LOCATION:
return TIM_t("[位置]");
case MessageElemType.V2TIM_ELEM_TYPE_MERGER:
return TIM_t("[聊天记录]");
default:
return TIM_t("未知消息");
}
}
static Future<String> getAbstractMessage(V2TimMessage message,
List<V2TimGroupMemberFullInfo?> groupMemberList) async {
final msgType = message.elemType;

View File

@ -7,6 +7,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:provider/provider.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitMessageItem/tim_uikit_chat_text_translate_elem.dart';
import 'package:tencent_super_tooltip/tencent_super_tooltip.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_statelesswidget.dart';
@ -131,6 +132,7 @@ class ToolTipsConfig {
final bool showRecallMessage;
final bool showCopyMessage;
final bool showForwardMessage;
final bool showTranslation;
final Widget? Function(V2TimMessage message, Function() closeTooltip,
[Key? key, BuildContext? context])? additionalItemBuilder;
@ -139,6 +141,7 @@ class ToolTipsConfig {
this.showMultipleChoiceMessage = true,
this.showRecallMessage = true,
this.showReplyMessage = true,
this.showTranslation = true,
this.showCopyMessage = true,
this.showForwardMessage = true,
this.additionalItemBuilder});
@ -200,6 +203,9 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
/// padding for each message item
final EdgeInsetsGeometry? padding;
/// The controller for text field.
final TIMUIKitInputTextFieldController? textFieldController;
/// padding for text messagesound messagereply message
final EdgeInsetsGeometry? textPadding;
@ -253,7 +259,8 @@ class TIMUIKitHistoryMessageListItem extends StatefulWidget {
this.isUseMessageReaction,
this.bottomRowBuilder,
this.isUseDefaultEmoji = false,
this.customEmojiStickerList = const []})
this.customEmojiStickerList = const [],
this.textFieldController})
: super(key: key);
@override
@ -682,6 +689,7 @@ class _TIMUIKItHistoryMessageListItemState
} else {
if (box != null) {
double screenWidth = MediaQuery.of(context).size.width;
double viewInsetsBottom = MediaQuery.of(context).viewInsets.bottom;
Offset offset = box.localToGlobal(Offset.zero);
double boxWidth = box.size.width;
if (isSelf) {
@ -689,7 +697,10 @@ class _TIMUIKItHistoryMessageListItemState
} else {
left = offset.dx;
}
if (offset.dy < 300 && !isLongMessage) {
if (offset.dy < 300 && !isLongMessage && viewInsetsBottom == 0) {
selectEmojiPanelPosition = SelectEmojiPanelPosition.up;
popupDirection = TooltipDirection.down;
} else if(viewInsetsBottom != 0 && offset.dy < 220){
selectEmojiPanelPosition = SelectEmojiPanelPosition.up;
popupDirection = TooltipDirection.down;
}
@ -1032,11 +1043,18 @@ class _TIMUIKItHistoryMessageListItemState
)),
Container(
constraints: BoxConstraints(
// maxWidth: getMaxWidth(false),
// maxWidth: getMaxWidth(false),
maxWidth: constraints.maxWidth * 0.8,
),
child: Builder(builder: (context) {
return GestureDetector(
return Column(
crossAxisAlignment:
(message.isSelf ?? false)
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
GestureDetector(
child: IgnorePointer(
ignoring: model.isMultiSelect,
child: _getMessageItemBuilder(
@ -1071,6 +1089,19 @@ class _TIMUIKItHistoryMessageListItemState
widget.onLongPress!(context, message);
}
},
),
Container(
color: theme.white,
child: TIMUIKitTextTranslationElem(
message: message,
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
isFromSelf: message.isSelf ?? false,
isShowJump: false,
clearJump: () {},
chatModel: model),
)
],
);
}),
),

View File

@ -73,15 +73,15 @@ class TIMUIKitMessageTooltipState
Widget ItemInkWell({
Widget? child,
GestureTapCallback? onTap,
GestureTapCallback? onTap
}) {
return SizedBox(
width: 50,
width: 40,
child: InkWell(
onTap: onTap,
splashColor: Colors.white,
child: Container(
padding: const EdgeInsets.only(bottom: 8, top: 8),
padding: const EdgeInsets.only(bottom: 6, top: 6),
child: child,
),
),
@ -127,6 +127,11 @@ class TIMUIKitMessageTooltipState
"id": "delete",
"icon": "images/delete_message.png"
},
{
"label": TIM_t("翻译"),
"id": "translate",
"icon": "images/translate.png"
},
if (shouldShowRevokeAction)
{
"label": TIM_t("撤回"),
@ -134,15 +139,12 @@ class TIMUIKitMessageTooltipState
"icon": "images/revoke_message.png"
}
];
if (widget.message.elemType != MessageElemType.V2TIM_ELEM_TYPE_TEXT) {
defaultTipsList.removeAt(0);
}
List formatedTipsList = defaultTipsList;
if (tooltipsConfig != null) {
formatedTipsList = defaultTipsList.where((element) {
final type = element["id"];
if (type == "copyMessage") {
return tooltipsConfig.showCopyMessage;
return tooltipsConfig.showCopyMessage && widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT;
}
if (type == "forwardMessage") {
return tooltipsConfig.showForwardMessage;
@ -156,7 +158,9 @@ class TIMUIKitMessageTooltipState
if (type == "multiSelect") {
return tooltipsConfig.showMultipleChoiceMessage;
}
if (type == "translate") {
return tooltipsConfig.showTranslation && widget.message.elemType == MessageElemType.V2TIM_ELEM_TYPE_TEXT;
}
if (type == "revoke") {
return tooltipsConfig.showRecallMessage;
}
@ -212,6 +216,9 @@ class TIMUIKitMessageTooltipState
model.updateMultiSelectStatus(true);
model.addToMultiSelectedMessageList(widget.message);
break;
case 'translate':
model.translateText(widget.message);
break;
case "forwardMessage":
model.addToMultiSelectedMessageList(widget.message);
Navigator.push(
@ -313,10 +320,10 @@ class TIMUIKitMessageTooltipState
direction: Axis.horizontal,
alignment: ScreenUtils.getFormFactor(context) ==
ScreenType.Handset
? WrapAlignment.spaceAround
? WrapAlignment.spaceBetween
: WrapAlignment.start,
spacing: 4,
runSpacing: 16,
runSpacing: 8,
children: [
..._buildLongPressTipItem(theme, model),
if (extraTipsActionItem != null)

View File

@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:scroll_to_index/scroll_to_index.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/tim_uikit_text_field_controller.dart';
import 'package:tencent_im_base/tencent_im_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
@ -61,6 +62,9 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
final List customEmojiStickerList;
/// The controller for text field.
final TIMUIKitInputTextFieldController? textFieldController;
final bool isAllowScroll;
const TIMUIKitHistoryMessageListContainer({
@ -84,6 +88,7 @@ class TIMUIKitHistoryMessageListContainer extends StatefulWidget {
this.toolTipsConfig,
this.isUseDefaultEmoji = false,
this.customEmojiStickerList = const [],
this.textFieldController,
}) : super(key: key);
@override
@ -155,6 +160,7 @@ class _TIMUIKitHistoryMessageListContainerState
message: message!,
showAvatar: chatConfig.isShowAvatar,
onTapForOthersPortrait: widget.onTapAvatar,
textFieldController: widget.textFieldController,
messageItemBuilder: widget.messageItemBuilder,
onLongPressForOthersHeadPortrait:
widget.onLongPressForOthersHeadPortrait,

View File

@ -226,9 +226,9 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
if (widget.message.localCustomData != null &&
widget.message.localCustomData!.isNotEmpty) {
final String localJSON = widget.message.localCustomData!;
final LinkPreviewModel? localPreviewInfo =
LinkPreviewModel.fromMap(json.decode(localJSON));
if (localPreviewInfo != null && !localPreviewInfo.isEmpty()) {
final LocalCustomDataModel? localPreviewInfo =
LocalCustomDataModel.fromMap(json.decode(localJSON));
if (localPreviewInfo != null && !localPreviewInfo.isLinkPreviewEmpty()) {
return Container(
margin: const EdgeInsets.only(top: 8),
child:
@ -290,7 +290,7 @@ class _TIMUIKitReplyElemState extends TIMUIKitState<TIMUIKitReplyElem> {
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10));
final textWithLink = LinkPreviewEntry.getHyperlinksText(
widget.message,
widget.message.textElem?.text ?? "",
widget.chatModel.chatConfig.isSupportMarkdownForTextMessage,
widget.chatModel.chatConfig.onTapLink);
return Container(

View File

@ -102,10 +102,10 @@ class _TIMUIKitTextElemState extends TIMUIKitState<TIMUIKitTextElem> {
if (widget.message.localCustomData != null &&
widget.message.localCustomData!.isNotEmpty) {
final String localJSON = widget.message.localCustomData!;
final LinkPreviewModel? localPreviewInfo =
LinkPreviewModel.fromMap(json.decode(localJSON));
final LocalCustomDataModel? localPreviewInfo =
LocalCustomDataModel.fromMap(json.decode(localJSON));
// If [localCustomData] is not empty, check if the link preview info exists
if (localPreviewInfo == null || localPreviewInfo.isEmpty()) {
if (localPreviewInfo == null || localPreviewInfo.isLinkPreviewEmpty()) {
// If not exists, get it
_initLinkPreview();
}
@ -136,9 +136,9 @@ class _TIMUIKitTextElemState extends TIMUIKitState<TIMUIKitTextElem> {
widget.message.localCustomData!.isNotEmpty) {
try {
final String localJSON = widget.message.localCustomData!;
final LinkPreviewModel? localPreviewInfo =
LinkPreviewModel.fromMap(json.decode(localJSON));
if (localPreviewInfo != null && !localPreviewInfo.isEmpty()) {
final LocalCustomDataModel? localPreviewInfo =
LocalCustomDataModel.fromMap(json.decode(localJSON));
if (localPreviewInfo != null && !localPreviewInfo.isLinkPreviewEmpty()) {
return Container(
margin: const EdgeInsets.only(top: 8),
child:
@ -160,7 +160,7 @@ class _TIMUIKitTextElemState extends TIMUIKitState<TIMUIKitTextElem> {
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
final textWithLink = LinkPreviewEntry.getHyperlinksText(
widget.message,
widget.message.textElem?.text ?? "",
widget.chatModel.chatConfig.isSupportMarkdownForTextMessage,
widget.chatModel.chatConfig.onTapLink,
widget.isUseDefaultEmoji,

View File

@ -0,0 +1,259 @@
import 'dart:async';
import 'dart:convert';
import 'package:tencent_extended_text/extended_text.dart';
import 'package:flutter/material.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_base.dart';
import 'package:tencent_cloud_chat_uikit/base_widgets/tim_ui_kit_state.dart';
import 'package:tencent_cloud_chat_uikit/business_logic/separate_models/tui_chat_separate_view_model.dart';
import 'package:tencent_cloud_chat_uikit/tencent_cloud_chat_uikit.dart';
import 'package:tencent_cloud_chat_uikit/ui/views/TIMUIKitChat/TIMUIKitTextField/special_text/DefaultSpecialTextSpanBuilder.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/link_preview_entry.dart';
import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/widgets/link_preview.dart';
import 'TIMUIKitMessageReaction/tim_uikit_message_reaction_show_panel.dart';
class TIMUIKitTextTranslationElem extends StatefulWidget {
final V2TimMessage message;
final bool isFromSelf;
final bool isShowJump;
final VoidCallback clearJump;
final TextStyle? fontStyle;
final BorderRadius? borderRadius;
final Color? backgroundColor;
final EdgeInsetsGeometry? textPadding;
final TUIChatSeparateViewModel chatModel;
final bool? isShowMessageReaction;
final bool isUseDefaultEmoji;
final List customEmojiStickerList;
const TIMUIKitTextTranslationElem(
{Key? key,
required this.message,
required this.isFromSelf,
required this.isShowJump,
required this.clearJump,
this.fontStyle,
this.borderRadius,
this.isShowMessageReaction,
this.backgroundColor,
this.textPadding,
required this.chatModel,
this.isUseDefaultEmoji = false,
this.customEmojiStickerList = const []})
: super(key: key);
@override
State<StatefulWidget> createState() => _TIMUIKitTextTranslationElemState();
}
class _TIMUIKitTextTranslationElemState extends TIMUIKitState<TIMUIKitTextTranslationElem> {
bool isShowJumpState = false;
bool isShining = false;
@override
void initState() {
super.initState();
// get the link preview info
_getLinkPreview();
}
@override
void didUpdateWidget(TIMUIKitTextTranslationElem oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.message.msgID == null && widget.message.msgID != null) {
_getLinkPreview();
}
}
_showJumpColor() {
if ((widget.chatModel.jumpMsgID != widget.message.msgID) &&
(widget.message.msgID?.isNotEmpty ?? true)) {
return;
}
isShining = true;
int shineAmount = 6;
setState(() {
isShowJumpState = true;
});
Timer.periodic(const Duration(milliseconds: 300), (timer) {
if (mounted) {
setState(() {
isShowJumpState = shineAmount.isOdd ? true : false;
});
}
if (shineAmount == 0 || !mounted) {
isShining = false;
timer.cancel();
}
shineAmount--;
});
Future.delayed(const Duration(milliseconds: 100), () {
widget.clearJump();
});
}
// get the link preview info
_getLinkPreview() {
if (widget.chatModel.chatConfig.urlPreviewType !=
UrlPreviewType.previewCardAndHyperlink) {
return;
}
try {
if (widget.message.localCustomData != null &&
widget.message.localCustomData!.isNotEmpty) {
final String localJSON = widget.message.localCustomData!;
final LocalCustomDataModel? localPreviewInfo =
LocalCustomDataModel.fromMap(json.decode(localJSON));
// If [localCustomData] is not empty, check if the link preview info exists
if (localPreviewInfo == null || localPreviewInfo.isLinkPreviewEmpty()) {
// If not exists, get it
_initLinkPreview();
}
} else {
// It [localCustomData] is empty, get the link info
_initLinkPreview();
}
} catch (e) {
return null;
}
}
_initLinkPreview() async {
// Get the link preview info from extension, let it update the message UI automatically by providing a [onUpdateMessage].
// The `onUpdateMessage` can use the `updateMessage()` from the [TIMUIKitChatController] directly.
LinkPreviewEntry.getFirstLinkPreviewContent(
message: widget.message,
onUpdateMessage: () {
widget.chatModel
.updateMessageFromController(msgID: widget.message.msgID!);
});
}
Widget? _renderPreviewWidget() {
// If the link preview info from [localCustomData] is available, use it to render the preview card.
// Otherwise, it will returns null.
if (widget.message.localCustomData != null &&
widget.message.localCustomData!.isNotEmpty) {
try {
final String localJSON = widget.message.localCustomData!;
final LocalCustomDataModel? localPreviewInfo =
LocalCustomDataModel.fromMap(json.decode(localJSON));
if (localPreviewInfo != null && !localPreviewInfo.isLinkPreviewEmpty()) {
return Container(
margin: const EdgeInsets.only(top: 8),
child:
// You can use this default widget [LinkPreviewWidget] to render preview card, or you can use custom widget.
LinkPreviewWidget(linkPreview: localPreviewInfo),
);
} else {
return null;
}
} catch (e) {
return null;
}
} else {
return null;
}
}
@override
Widget tuiBuild(BuildContext context, TUIKitBuildValue value) {
final theme = value.theme;
final borderRadius = widget.isFromSelf
? const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(2),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10))
: const BorderRadius.only(
topLeft: Radius.circular(2),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10));
if ((widget.chatModel.jumpMsgID == widget.message.msgID)) {}
if (widget.isShowJump) {
if (!isShining) {
Future.delayed(Duration.zero, () {
_showJumpColor();
});
} else {
if ((widget.chatModel.jumpMsgID == widget.message.msgID) &&
(widget.message.msgID?.isNotEmpty ?? false)) {
widget.clearJump();
}
}
}
final defaultStyle = widget.isFromSelf
? theme.lightPrimaryMaterialColor.shade50
: theme.chatMessageItemFromOthersBgColor;
final backgroundColor = isShowJumpState
? const Color.fromRGBO(245, 166, 35, 1)
: (widget.backgroundColor ?? defaultStyle);
final LocalCustomDataModel localCustomData = LocalCustomDataModel.fromMap(
json.decode(TencentUtils.checkString(widget.message.localCustomData) ?? "{}"));
final String? translateText = localCustomData.translatedText;
final textWithLink = LinkPreviewEntry.getHyperlinksText(
translateText ?? "",
widget.chatModel.chatConfig.isSupportMarkdownForTextMessage,
widget.chatModel.chatConfig.onTapLink,
widget.isUseDefaultEmoji,
widget.customEmojiStickerList,
);
return TencentUtils.checkString(translateText) != null ? Container(
margin: const EdgeInsets.only(top: 6),
padding: widget.textPadding ?? const EdgeInsets.all(10),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: widget.borderRadius ?? borderRadius,
),
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// If the [elemType] is text message, it will not be null here.
// You can render the widget from extension directly, with a [TextStyle] optionally.
widget.chatModel.chatConfig.urlPreviewType != UrlPreviewType.none
? textWithLink!(
style: widget.fontStyle ??
TextStyle(
fontSize: 16,
textBaseline: TextBaseline.ideographic,
height: widget.chatModel.chatConfig.textHight))
: ExtendedText(translateText!,
softWrap: true,
style: widget.fontStyle ??
TextStyle(
fontSize: 16,
height: widget.chatModel.chatConfig.textHight),
specialTextSpanBuilder: DefaultSpecialTextSpanBuilder(
isUseDefaultEmoji: widget.isUseDefaultEmoji,
customEmojiStickerList: widget.customEmojiStickerList,
showAtBackground: true,
)),
const SizedBox(height: 6),
Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Icon(Icons.check_circle,
color: Color(0x72282c34),
size: 12,),
const SizedBox(width: 4,),
Text(TIM_t("翻译完成"),
style: const TextStyle(
color: Color(0x72282c34),
fontSize: 10
),
)
],
)
],
),
) : const SizedBox(width: 0, height: 0);
}
}

View File

@ -279,7 +279,7 @@ class _InputTextFieldState extends TIMUIKitState<TIMUIKitInputTextField> {
final haveRepliedMessage = repliedMessage != null;
if (haveRepliedMessage) {
final text =
"${MessageUtils.getDisplayName(widget.model.repliedMessage!)}:${widget.model.abstractMessageBuilder != null ? widget.model.abstractMessageBuilder!(widget.model.repliedMessage!) : MessageUtils.getAbstractMessage(widget.model.repliedMessage!, widget.model.groupMemberList ?? [])}";
"${MessageUtils.getDisplayName(widget.model.repliedMessage!)}:${widget.model.abstractMessageBuilder != null ? widget.model.abstractMessageBuilder!(widget.model.repliedMessage!) : MessageUtils.getAbstractMessageAsync(widget.model.repliedMessage!, widget.model.groupMemberList ?? [])}";
return Container(
color: widget.backgroundColor ?? hexToColor("f5f5f6"),
alignment: Alignment.centerLeft,

View File

@ -337,6 +337,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
child: Listener(
onPointerMove: closePanel,
child: TIMUIKitHistoryMessageListContainer(
textFieldController: textFieldController,
customEmojiStickerList:
widget.customEmojiStickerList,
isUseDefaultEmoji:

View File

@ -52,7 +52,7 @@ class _ForwardMessageScreenState extends TIMUIKitState<ForwardMessageScreen> {
final sender = (e.nickName != null && e.nickName!.isNotEmpty)
? e.nickName
: e.sender;
return "$sender: ${model.abstractMessageBuilder != null ? model.abstractMessageBuilder!(e) : MessageUtils.getAbstractMessage(e, [])}";
return "$sender: ${model.abstractMessageBuilder != null ? model.abstractMessageBuilder!(e) : MessageUtils.getAbstractMessageAsync(e, [])}";
}).toList();
}

View File

@ -45,10 +45,10 @@ class LinkUtils {
}
/// Get the URL preview information
static Future<List<LinkPreviewModel>> getURLPreview(
static Future<List<LocalCustomDataModel>> getURLPreview(
List<String> urlMatches) async {
// Request for preview information for all URL links synchronously
final List<LinkPreviewModel> urlPreview =
final List<LocalCustomDataModel> urlPreview =
await Future.wait(urlMatches.map((e) async {
String url = e;
if (!e.contains("http")) {
@ -56,7 +56,7 @@ class LinkUtils {
}
final WebInfo info = await LinkPreview.scrapeFromURL(url);
return LinkPreviewModel(
return LocalCustomDataModel(
url: e,
title: info.title,
image: info.image,
@ -68,7 +68,7 @@ class LinkUtils {
/// save the link info to local and call updating the message on UI, only works with [onUpdateMessage]
static Future<void> saveToLocalAndUpdate(V2TimMessage message,
LinkPreviewModel previewItem, VoidCallback onUpdateMessage) async {
LocalCustomDataModel previewItem, VoidCallback onUpdateMessage) async {
if (message.msgID != null) {
String saveInfo = LinkPreviewEntry.linkInfoToString(previewItem);
final currentInfo = message.localCustomData;

View File

@ -9,11 +9,10 @@ import 'models/link_preview_content.dart';
class LinkPreviewEntry {
/// get the text message with hyperlinks
static LinkPreviewText? getHyperlinksText(
V2TimMessage message, bool isMarkdown,
String messageText, bool isMarkdown,
[Function(String)? onLinkTap,
bool isUseDefaultEmoji = false,
List customEmojiStickerList = const []]) {
final String? messageText = message.textElem!.text;
if (messageText == null) {
return null;
@ -46,10 +45,10 @@ class LinkPreviewEntry {
return null;
}
final List<LinkPreviewModel?> previewItemList =
final List<LocalCustomDataModel?> previewItemList =
await LinkUtils.getURLPreview([urlMatches[0]]);
if (previewItemList.isNotEmpty) {
final LinkPreviewModel previewItem = previewItemList.first!;
final LocalCustomDataModel previewItem = previewItemList.first!;
if (onUpdateMessage != null) {
LinkUtils.saveToLocalAndUpdate(message, previewItem, onUpdateMessage);
}
@ -75,7 +74,7 @@ class LinkPreviewEntry {
return [];
}
final List<LinkPreviewModel> previewItemList =
final List<LocalCustomDataModel> previewItemList =
await LinkUtils.getURLPreview([urlMatches[0]]);
if (previewItemList.isNotEmpty) {
final List<LinkPreviewContent?> resultList = previewItemList
@ -91,7 +90,7 @@ class LinkPreviewEntry {
}
}
static String linkInfoToString(LinkPreviewModel linkInfo) {
static String linkInfoToString(LocalCustomDataModel linkInfo) {
return linkInfo.toString();
}

View File

@ -4,14 +4,15 @@ import 'package:flutter/cupertino.dart';
typedef LinkPreviewText = Widget Function({TextStyle? style});
class LinkPreviewModel {
class LocalCustomDataModel {
final String? description;
final String? image;
final String url;
final String? url;
final String? title;
String? translatedText;
LinkPreviewModel(
{this.description, this.image, required this.url, this.title});
LocalCustomDataModel(
{this.description, this.image, this.url, this.title, this.translatedText});
Map<String, String?> toMap() {
final Map<String, String?> data = {};
@ -19,13 +20,15 @@ class LinkPreviewModel {
data['image'] = image;
data['title'] = title;
data['description'] = description;
data['translatedText'] = translatedText;
return data;
}
LinkPreviewModel.fromMap(Map map)
LocalCustomDataModel.fromMap(Map map)
: description = map['description'],
image = map['image'],
url = map['url'],
translatedText = map['translatedText'],
title = map['title'];
@override
@ -33,7 +36,7 @@ class LinkPreviewModel {
return json.encode(toMap());
}
bool isEmpty() {
bool isLinkPreviewEmpty() {
if ((image == null || image!.isEmpty) &&
(title == null || title!.isEmpty) &&
(description == null || description!.isEmpty)) {
@ -49,6 +52,6 @@ class LinkPreviewContent {
this.linkPreviewWidget,
});
final LinkPreviewModel? linkInfo;
final LocalCustomDataModel? linkInfo;
final Widget? linkPreviewWidget;
}

View File

@ -4,19 +4,21 @@ import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/common/utils.da
import 'package:tencent_cloud_chat_uikit/ui/widgets/link_preview/models/link_preview_content.dart';
class LinkPreviewWidget extends TIMStatelessWidget {
final LinkPreviewModel linkPreview;
final LocalCustomDataModel linkPreview;
const LinkPreviewWidget({Key? key, required this.linkPreview})
: super(key: key);
@override
Widget timBuild(BuildContext context) {
if (linkPreview.isEmpty()) {
if (linkPreview.isLinkPreviewEmpty()) {
return Container();
}
return GestureDetector(
onTap: () {
LinkUtils.launchURL(context, linkPreview.url);
if(linkPreview.url != null){
LinkUtils.launchURL(context, linkPreview.url!);
}
},
child: Container(
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 8, right: 8),

View File

@ -1063,7 +1063,7 @@ packages:
name: tencent_cloud_chat_sdk
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.7"
version: "5.0.8"
tencent_extended_text:
dependency: "direct main"
description:
@ -1091,14 +1091,14 @@ packages:
name: tencent_im_base
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.17"
version: "1.0.19"
tencent_im_sdk_plugin_platform_interface:
dependency: transitive
description:
name: tencent_im_sdk_plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.10"
version: "0.3.11"
tencent_keyboard_visibility:
dependency: "direct main"
description:

View File

@ -1,6 +1,6 @@
name: tencent_cloud_chat_uikit
description: UI components library and basic chat business logic for Tencent Cloud Chat service, helping you build In-APP Chat module easily.
version: 1.3.2
version: 1.4.0
homepage: https://www.tencentcloud.com/products/im?from=pub
repository: https://github.com/TencentCloud/tc-chat-uikit-flutter
documentation: https://comm.qq.com/im/doc/flutter/en/TUIKit/readme.html
@ -62,7 +62,7 @@ dependencies:
url_launcher: ^6.1.4
universal_html: ^2.0.8
link_preview_generator: ^1.2.0
tencent_im_base: ^1.0.17
tencent_im_base: ^1.0.19
disk_space: ^0.2.1
http: ^0.13.5
crypto: ^3.0.2