- 上游: github.com/OpenIMSDK/openim-flutter-demo tag 3.8.3-patch.3 commit b3dfdb1e8aaeaaf6f0793e10cadd20d5c184a31f - 并行目录 mobile-next/ 原样导入(含 LICENSE),不覆盖现网 mobile/ - 锁定 Flutter 3.24.5 / Dart 3.5.4 / JDK 17 / Gradle 7.6.3 / AGP 7.3.1 - 实测 Android debug 与 release 构建均成功(见 docs/mobile-next-baseline-import.md) - 本提交可单独回退:git revert 1db1229(重写前) Co-authored-by: multica-agent <github@multica.ai>
274 lines
9.9 KiB
Dart
274 lines
9.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:openim_common/openim_common.dart';
|
|
|
|
import '../../widgets/file_download_progress.dart';
|
|
import 'chat_logic.dart';
|
|
|
|
class ChatPage extends StatelessWidget {
|
|
final logic = Get.find<ChatLogic>(tag: GetTags.chat);
|
|
|
|
ChatPage({super.key});
|
|
|
|
Widget _buildItemView(Message message) => ChatItemView(
|
|
key: logic.itemKey(message),
|
|
message: message,
|
|
textScaleFactor: logic.scaleFactor.value,
|
|
allAtMap: logic.getAtMapping(message),
|
|
timelineStr: logic.getShowTime(message),
|
|
sendStatusSubject: logic.sendStatusSub,
|
|
closePopMenuSubject: logic.forceCloseMenuSub,
|
|
enabledReadStatus: logic.enabledReadStatus(message),
|
|
readingDuration: logic.readTime(message),
|
|
isPlayingSound: logic.isPlaySound(message),
|
|
showLongPressMenu: !logic.isInvalidGroup,
|
|
leftNickname: logic.getNewestNickname(message),
|
|
leftFaceUrl: logic.getNewestFaceURL(message),
|
|
rightNickname: logic.senderName,
|
|
rightFaceUrl: OpenIM.iMManager.userInfo.faceURL,
|
|
showLeftNickname: !logic.isSingleChat,
|
|
showRightNickname: !logic.isSingleChat,
|
|
enabledCopyMenu: logic.showCopyMenu(message),
|
|
enabledRevokeMenu: logic.showRevokeMenu(message),
|
|
enabledReplyMenu: logic.showReplyMenu(message),
|
|
enabledForwardMenu: logic.showForwardMenu(message),
|
|
enabledDelMenu: logic.showDelMenu(message),
|
|
enabledAddEmojiMenu: logic.showAddEmojiMenu(message),
|
|
onFailedToResend: () => logic.failedResend(message),
|
|
onPopMenuShowChanged: logic.onPopMenuShowChanged,
|
|
onClickItemView: () => logic.parseClickEvent(message),
|
|
onTapCopyMenu: () => logic.copy(message),
|
|
onTapDelMenu: () => logic.deleteMsg(message),
|
|
onTapForwardMenu: () => logic.forward(message),
|
|
onTapRevokeMenu: () {
|
|
logic.markRevokedMessage(message);
|
|
logic.revokeMsgV2(message);
|
|
},
|
|
onTapAddEmojiMenu: () => logic.addEmoji(message),
|
|
visibilityChange: (msg, visible) {
|
|
logic.markMessageAsRead(message, visible);
|
|
},
|
|
onLongPressLeftAvatar: () {
|
|
logic.onLongPressLeftAvatar(message);
|
|
},
|
|
onLongPressRightAvatar: () {},
|
|
onTapLeftAvatar: () {
|
|
logic.onTapLeftAvatar(message);
|
|
},
|
|
onVisibleTrulyText: (text) {
|
|
logic.copyTextMap[message.clientMsgID] = text;
|
|
},
|
|
customTypeBuilder: _buildCustomTypeItemView,
|
|
fileDownloadProgressView: FileDownloadProgressView(message),
|
|
patterns: <MatchPattern>[
|
|
MatchPattern(
|
|
type: PatternType.email,
|
|
onTap: logic.clickLinkText,
|
|
),
|
|
MatchPattern(
|
|
type: PatternType.url,
|
|
onTap: logic.clickLinkText,
|
|
),
|
|
MatchPattern(
|
|
type: PatternType.mobile,
|
|
onTap: logic.clickLinkText,
|
|
),
|
|
MatchPattern(
|
|
type: PatternType.tel,
|
|
onTap: logic.clickLinkText,
|
|
),
|
|
],
|
|
mediaItemBuilder: (context, message) {
|
|
return _buildMediaItem(context, message);
|
|
},
|
|
onTapUserProfile: handleUserProfileTap,
|
|
);
|
|
|
|
void handleUserProfileTap(({String userID, String name, String? faceURL, String? groupID}) userProfile) {
|
|
final userInfo = UserInfo(userID: userProfile.userID, nickname: userProfile.name, faceURL: userProfile.faceURL);
|
|
logic.viewUserInfo(userInfo);
|
|
}
|
|
|
|
Widget? _buildMediaItem(BuildContext context, Message message) {
|
|
if (message.contentType != MessageType.picture && message.contentType != MessageType.video) {
|
|
return null;
|
|
}
|
|
|
|
return GestureDetector(
|
|
onTap: () async {
|
|
try {
|
|
logic.stopVoice();
|
|
|
|
IMUtils.previewMediaFile(
|
|
context: context,
|
|
message: message,
|
|
onAutoPlay: (index) {
|
|
return !logic.playOnce;
|
|
},
|
|
muted: logic.rtcIsBusy,
|
|
onPageChanged: (index) {
|
|
logic.playOnce = true;
|
|
},
|
|
onOperate: (type) {
|
|
if (type == OperateType.forward) {
|
|
logic.forward(message);
|
|
}
|
|
}).then((value) {
|
|
logic.playOnce = false;
|
|
});
|
|
} catch (e) {
|
|
IMViews.showToast(e.toString());
|
|
}
|
|
},
|
|
child: Hero(
|
|
tag: message.clientMsgID!,
|
|
child: _buildMediaContent(message),
|
|
placeholderBuilder: (BuildContext context, Size heroSize, Widget child) => child,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMediaContent(Message message) {
|
|
final isOutgoing = message.sendID == OpenIM.iMManager.userID;
|
|
|
|
if (message.isVideoType) {
|
|
return ChatVideoView(
|
|
isISend: isOutgoing,
|
|
message: message,
|
|
);
|
|
} else {
|
|
return ChatPictureView(
|
|
isISend: isOutgoing,
|
|
message: message,
|
|
);
|
|
}
|
|
}
|
|
|
|
CustomTypeInfo? _buildCustomTypeItemView(_, Message message) {
|
|
final data = IMUtils.parseCustomMessage(message);
|
|
if (null != data) {
|
|
final viewType = data['viewType'];
|
|
if (viewType == CustomMessageType.call) {
|
|
final type = data['type'];
|
|
final content = data['content'];
|
|
final view = ChatCallItemView(type: type, content: content);
|
|
return CustomTypeInfo(view);
|
|
} else if (viewType == CustomMessageType.deletedByFriend || viewType == CustomMessageType.blockedByFriend) {
|
|
final view = ChatFriendRelationshipAbnormalHintView(
|
|
name: logic.nickname.value,
|
|
onTap: logic.sendFriendVerification,
|
|
blockedByFriend: viewType == CustomMessageType.blockedByFriend,
|
|
deletedByFriend: viewType == CustomMessageType.deletedByFriend,
|
|
);
|
|
return CustomTypeInfo(view, false, false);
|
|
} else if (viewType == CustomMessageType.removedFromGroup) {
|
|
return CustomTypeInfo(
|
|
StrRes.removedFromGroupHint.toText..style = Styles.ts_8E9AB0_12sp,
|
|
false,
|
|
false,
|
|
);
|
|
} else if (viewType == CustomMessageType.groupDisbanded) {
|
|
return CustomTypeInfo(
|
|
StrRes.groupDisbanded.toText..style = Styles.ts_8E9AB0_12sp,
|
|
false,
|
|
false,
|
|
);
|
|
} else if (viewType == CustomMessageType.tag) {
|
|
final isISend = message.sendID == OpenIM.iMManager.userID;
|
|
if (null != data['textElem']) {
|
|
final textElem = TextElem.fromJson(data['textElem']);
|
|
return CustomTypeInfo(
|
|
ChatText(
|
|
text: textElem.content ?? '',
|
|
textScaleFactor: logic.scaleFactor.value,
|
|
model: TextModel.normal,
|
|
),
|
|
);
|
|
} else if (null != data['soundElem']) {
|
|
final soundElem = SoundElem.fromJson(data['soundElem']);
|
|
return CustomTypeInfo(
|
|
ChatVoiceView(
|
|
isISend: isISend,
|
|
soundPath: soundElem.soundPath,
|
|
soundUrl: soundElem.sourceUrl,
|
|
duration: soundElem.duration,
|
|
isPlaying: logic.isPlaySound(message),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Widget? get _groupCallHintView => null;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return WillPopScope(
|
|
onWillPop: logic.willPop(),
|
|
child: ChatVoiceRecordLayout(
|
|
onCompleted: logic.sendVoice,
|
|
builder: (bar) => Obx(() {
|
|
return Scaffold(
|
|
backgroundColor: Styles.c_F0F2F6,
|
|
appBar: TitleBar.chat(
|
|
title: logic.nickname.value,
|
|
member: logic.memberStr,
|
|
subTitle: logic.subTile,
|
|
showOnlineStatus: logic.showOnlineStatus(),
|
|
isOnline: logic.onlineStatus.value,
|
|
onCloseMultiModel: logic.exit,
|
|
onClickMoreBtn: logic.chatSetup,
|
|
onClickCallBtn: logic.call,
|
|
),
|
|
body: SafeArea(
|
|
child: WaterMarkBgView(
|
|
text: '',
|
|
path: logic.background.value,
|
|
backgroundColor: Styles.c_FFFFFF,
|
|
floatView: _groupCallHintView,
|
|
bottomView: ChatInputBox(
|
|
forceCloseToolboxSub: logic.forceCloseToolbox,
|
|
controller: logic.inputCtrl,
|
|
focusNode: logic.focusNode,
|
|
isNotInGroup: logic.isInvalidGroup,
|
|
directionalText: logic.directionalText(),
|
|
onCloseDirectional: logic.onClearDirectional,
|
|
onSend: (v) => logic.sendTextMsg(),
|
|
toolbox: ChatToolBox(
|
|
onTapAlbum: logic.onTapAlbum,
|
|
onTapCamera: logic.onTapCamera,
|
|
onTapCall: logic.call,
|
|
onTapCard: logic.onTapCarte,
|
|
onTapFile: logic.onTapFile,
|
|
onTapLocation: logic.onTapLocation,
|
|
),
|
|
voiceRecordBar: bar,
|
|
emojiView: ChatEmojiView(
|
|
textEditingController: logic.inputCtrl,
|
|
favoriteList: logic.cacheLogic.urlList,
|
|
onAddFavorite: logic.favoriteManage,
|
|
onSelectedFavorite: logic.sendFavoritePic,
|
|
),
|
|
),
|
|
child: ChatListView(
|
|
onTouch: () => logic.closeToolbox(),
|
|
itemCount: logic.messageList.length,
|
|
controller: logic.scrollController,
|
|
onScrollToBottomLoad: logic.onScrollToBottomLoad,
|
|
onScrollToTop: logic.onScrollToTop,
|
|
itemBuilder: (_, index) {
|
|
final message = logic.indexOfMessage(index);
|
|
return Obx(() => _buildItemView(message));
|
|
},
|
|
),
|
|
),
|
|
));
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
}
|