update flutter uikit to 1.4.0
This commit is contained in:
parent
e0bb3127c8
commit
121e8c295f
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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] ?? "";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 message、sound message、reply 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,45 +1043,65 @@ class _TIMUIKItHistoryMessageListItemState
|
|||
)),
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
// maxWidth: getMaxWidth(false),
|
||||
// maxWidth: getMaxWidth(false),
|
||||
maxWidth: constraints.maxWidth * 0.8,
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
return GestureDetector(
|
||||
child: IgnorePointer(
|
||||
ignoring: model.isMultiSelect,
|
||||
child: _getMessageItemBuilder(
|
||||
message, message.status, model)),
|
||||
onSecondaryTapDown: (details) {
|
||||
if (PlatformUtils().isWeb) {
|
||||
if (widget.allowLongPress) {
|
||||
_onLongPress(
|
||||
context,
|
||||
message,
|
||||
model,
|
||||
theme,
|
||||
details,
|
||||
);
|
||||
}
|
||||
if (widget.onLongPress != null) {
|
||||
widget.onLongPress!(context, message);
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (widget.allowLongPress) {
|
||||
_onLongPress(
|
||||
context,
|
||||
message,
|
||||
model,
|
||||
theme,
|
||||
null,
|
||||
);
|
||||
}
|
||||
if (widget.onLongPress != null) {
|
||||
widget.onLongPress!(context, message);
|
||||
}
|
||||
},
|
||||
return Column(
|
||||
crossAxisAlignment:
|
||||
(message.isSelf ?? false)
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
child: IgnorePointer(
|
||||
ignoring: model.isMultiSelect,
|
||||
child: _getMessageItemBuilder(
|
||||
message, message.status, model)),
|
||||
onSecondaryTapDown: (details) {
|
||||
if (PlatformUtils().isWeb) {
|
||||
if (widget.allowLongPress) {
|
||||
_onLongPress(
|
||||
context,
|
||||
message,
|
||||
model,
|
||||
theme,
|
||||
details,
|
||||
);
|
||||
}
|
||||
if (widget.onLongPress != null) {
|
||||
widget.onLongPress!(context, message);
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongPress: () {
|
||||
if (widget.allowLongPress) {
|
||||
_onLongPress(
|
||||
context,
|
||||
message,
|
||||
model,
|
||||
theme,
|
||||
null,
|
||||
);
|
||||
}
|
||||
if (widget.onLongPress != null) {
|
||||
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),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -337,6 +337,7 @@ class _TUIChatState extends TIMUIKitState<TIMUIKitChat> {
|
|||
child: Listener(
|
||||
onPointerMove: closePanel,
|
||||
child: TIMUIKitHistoryMessageListContainer(
|
||||
textFieldController: textFieldController,
|
||||
customEmojiStickerList:
|
||||
widget.customEmojiStickerList,
|
||||
isUseDefaultEmoji:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue