fix(mobile-next): 清理悬浮窗、语音文案与位置发送

按 B-273 / B-269 收口 Android 客户端:去掉悬浮窗申请与旧浮窗服务,通话入口统一为语音通话,删除位置发送与定位权限,保留历史位置只读展示。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-21 10:04:39 +08:00
co-authored by Cursor multica-agent
parent 092039bc1a
commit 694891c393
20 changed files with 662 additions and 726 deletions
+127 -87
View File
@@ -126,10 +126,13 @@ class ChatLogic extends SuperController {
String get memberStr => isSingleChat ? "" : "($memberCount)";
String? get senderName => isSingleChat ? OpenIM.iMManager.userInfo.nickname : groupMembersInfo?.nickname;
String? get senderName => isSingleChat
? OpenIM.iMManager.userInfo.nickname
: groupMembersInfo?.nickname;
bool get isAdminOrOwner =>
groupMemberRoleLevel.value == GroupRoleLevel.admin || groupMemberRoleLevel.value == GroupRoleLevel.owner;
groupMemberRoleLevel.value == GroupRoleLevel.admin ||
groupMemberRoleLevel.value == GroupRoleLevel.owner;
final directionalUsers = <GroupMembersInfo>[].obs;
@@ -140,8 +143,10 @@ class ChatLogic extends SuperController {
var isCurSingleChat = message.isSingleChat &&
isSingleChat &&
(senderId == userID || senderId == OpenIM.iMManager.userID && receiverId == userID);
var isCurGroupChat = message.isGroupChat && isGroupChat && groupID == groupId;
(senderId == userID ||
senderId == OpenIM.iMManager.userID && receiverId == userID);
var isCurGroupChat =
message.isGroupChat && isGroupChat && groupID == groupId;
return isCurSingleChat || isCurGroupChat;
}
@@ -152,11 +157,14 @@ class ChatLogic extends SuperController {
}
Future<List<Message>> searchMediaMessage() async {
final messageList = await OpenIM.iMManager.messageManager.searchLocalMessages(
conversationID: conversationInfo.conversationID,
messageTypeList: [MessageType.picture, MessageType.video],
count: 500);
return messageList.searchResultItems?.first.messageList?.reversed.toList() ?? [];
final messageList = await OpenIM.iMManager.messageManager
.searchLocalMessages(
conversationID: conversationInfo.conversationID,
messageTypeList: [MessageType.picture, MessageType.video],
count: 500);
return messageList.searchResultItems?.first.messageList?.reversed
.toList() ??
[];
}
@override
@@ -185,7 +193,8 @@ class ChatLogic extends SuperController {
_setSdkSyncDataListener();
conversationSub = imLogic.conversationChangedSubject.listen((value) {
final obj = value.firstWhereOrNull((e) => e.conversationID == conversationInfo.conversationID);
final obj = value.firstWhereOrNull(
(e) => e.conversationID == conversationInfo.conversationID);
if (obj != null) {
conversationInfo = obj;
@@ -196,7 +205,8 @@ class ChatLogic extends SuperController {
if (isCurrentChat(message)) {
if (message.contentType == MessageType.typing) {
} else {
if (!messageList.contains(message) && !scrollingCacheMessageList.contains(message)) {
if (!messageList.contains(message) &&
!scrollingCacheMessageList.contains(message)) {
_isReceivedMessageWhenSyncing = true;
if (isShowPopMenu.value || scrollController.offset != 0) {
scrollingCacheMessageList.add(message);
@@ -210,7 +220,8 @@ class ChatLogic extends SuperController {
};
imLogic.onRecvMessageRevoked = (RevokedInfo info) {
var message = messageList.firstWhereOrNull((e) => e.clientMsgID == info.clientMsgID);
var message = messageList
.firstWhereOrNull((e) => e.clientMsgID == info.clientMsgID);
message?.notificationElem = NotificationElem(detail: jsonEncode(info));
message?.contentType = MessageType.revokeMessageNotification;
@@ -273,12 +284,14 @@ class ChatLogic extends SuperController {
}
_putMemberInfo([info]);
final index = ownerAndAdmin.indexWhere((element) => element.userID == info.userID);
final index = ownerAndAdmin
.indexWhere((element) => element.userID == info.userID);
if (info.roleLevel == GroupRoleLevel.member) {
if (index > -1) {
ownerAndAdmin.removeAt(index);
}
} else if (info.roleLevel == GroupRoleLevel.admin || info.roleLevel == GroupRoleLevel.owner) {
} else if (info.roleLevel == GroupRoleLevel.admin ||
info.roleLevel == GroupRoleLevel.owner) {
if (index == -1) {
ownerAndAdmin.add(info);
} else {
@@ -364,7 +377,8 @@ class ChatLogic extends SuperController {
};
imLogic.inputStateChangedSubject.listen((value) {
if (value.conversationID == conversationInfo.conversationID && value.userID == userID) {
if (value.conversationID == conversationInfo.conversationID &&
value.userID == userID) {
typing.value = value.platformIDs?.isNotEmpty == true;
}
});
@@ -396,7 +410,8 @@ class ChatLogic extends SuperController {
Future sendPicture({required String path, bool sendNow = true}) async {
final file = await IMUtils.compressImageAndGetFile(File(path));
var message = await OpenIM.iMManager.messageManager.createImageMessageFromFullPath(
var message =
await OpenIM.iMManager.messageManager.createImageMessageFromFullPath(
imagePath: file!.path,
);
@@ -409,7 +424,8 @@ class ChatLogic extends SuperController {
}
void sendVoice(int duration, String path) async {
var message = await OpenIM.iMManager.messageManager.createSoundMessageFromFullPath(
var message =
await OpenIM.iMManager.messageManager.createSoundMessageFromFullPath(
soundPath: path,
duration: duration,
);
@@ -423,7 +439,8 @@ class ChatLogic extends SuperController {
required String thumbnailPath,
bool sendNow = true}) async {
var d = duration > 1000.0 ? duration / 1000.0 : duration;
var message = await OpenIM.iMManager.messageManager.createVideoMessageFromFullPath(
var message =
await OpenIM.iMManager.messageManager.createVideoMessageFromFullPath(
videoPath: videoPath,
videoType: mimeType,
duration: d.toInt(),
@@ -439,24 +456,14 @@ class ChatLogic extends SuperController {
}
void sendFile({required String filePath, required String fileName}) async {
var message = await OpenIM.iMManager.messageManager.createFileMessageFromFullPath(
var message =
await OpenIM.iMManager.messageManager.createFileMessageFromFullPath(
filePath: filePath,
fileName: fileName,
);
_sendMessage(message);
}
void sendLocation({
required dynamic location,
}) async {
var message = await OpenIM.iMManager.messageManager.createLocationMessage(
latitude: location['latitude'],
longitude: location['longitude'],
description: location['description'],
);
_sendMessage(message);
}
sendForwardRemarkMsg(
String content, {
String? userId,
@@ -481,8 +488,8 @@ class ChatLogic extends SuperController {
void sendTypingMsg({bool focus = false}) async {
if (isSingleChat) {
OpenIM.iMManager.conversationManager
.changeInputStates(conversationID: conversationInfo.conversationID, focus: focus);
OpenIM.iMManager.conversationManager.changeInputStates(
conversationID: conversationInfo.conversationID, focus: focus);
}
}
@@ -544,7 +551,8 @@ class ChatLogic extends SuperController {
offlinePushInfo: Config.offlinePushInfo,
)
.then((value) => _sendSucceeded(message, value))
.catchError((error, _) => _senFailed(message, groupId, userId, error, _))
.catchError(
(error, _) => _senFailed(message, groupId, userId, error, _))
.whenComplete(() => _completed());
}
@@ -557,8 +565,10 @@ class ChatLogic extends SuperController {
));
}
void _senFailed(Message message, String? groupId, String? userId, error, stack) async {
Logger.print('message send failed userID: $userId groupId:$groupId, catch error :$error $stack');
void _senFailed(
Message message, String? groupId, String? userId, error, stack) async {
Logger.print(
'message send failed userID: $userId groupId:$groupId, catch error :$error $stack');
message.status = MessageStatus.failed;
sendStatusSub.addSafely(MsgStreamEv<bool>(
id: message.clientMsgID!,
@@ -574,7 +584,8 @@ class ChatLogic extends SuperController {
customType = CustomMessageType.deletedByFriend;
}
if (null != customType) {
final hintMessage = (await OpenIM.iMManager.messageManager.createFailedHintMessage(type: customType))
final hintMessage = (await OpenIM.iMManager.messageManager
.createFailedHintMessage(type: customType))
..status = 2
..isRead = true;
if (userId != null) {
@@ -591,10 +602,15 @@ class ChatLogic extends SuperController {
);
}
} else {
if ((code == SDKErrorCode.userIsNotInGroup || code == SDKErrorCode.groupDisbanded) && null == groupId) {
if ((code == SDKErrorCode.userIsNotInGroup ||
code == SDKErrorCode.groupDisbanded) &&
null == groupId) {
final status = groupInfo?.status;
final hintMessage = (await OpenIM.iMManager.messageManager.createFailedHintMessage(
type: status == 2 ? CustomMessageType.groupDisbanded : CustomMessageType.removedFromGroup))
final hintMessage = (await OpenIM.iMManager.messageManager
.createFailedHintMessage(
type: status == 2
? CustomMessageType.groupDisbanded
: CustomMessageType.removedFromGroup))
..status = 2
..isRead = true;
messageList.add(hintMessage);
@@ -662,7 +678,9 @@ class ChatLogic extends SuperController {
void markMessageAsRead(Message message, bool visible) async {
Logger.print('markMessageAsRead: ${message.textElem?.content}, $visible');
if (visible && message.contentType! < 1000 && message.contentType! != MessageType.voice) {
if (visible &&
message.contentType! < 1000 &&
message.contentType! != MessageType.voice) {
var data = IMUtils.parseCustomMessage(message);
if (null != data && data['viewType'] == CustomMessageType.call) {
Logger.print('markMessageAsRead: call message $data');
@@ -675,11 +693,14 @@ class ChatLogic extends SuperController {
_markMessageAsRead(Message message) async {
if (!message.isRead! && message.sendID != OpenIM.iMManager.userID) {
try {
Logger.print('mark conversation message as read${message.clientMsgID!} ${message.isRead}');
Logger.print(
'mark conversation message as read${message.clientMsgID!} ${message.isRead}');
await OpenIM.iMManager.conversationManager
.markConversationMessageAsRead(conversationID: conversationInfo.conversationID);
.markConversationMessageAsRead(
conversationID: conversationInfo.conversationID);
} catch (e) {
Logger.print('failed to send group message read receipt ${message.clientMsgID} ${message.isRead}');
Logger.print(
'failed to send group message read receipt ${message.clientMsgID} ${message.isRead}');
} finally {
message.isRead = true;
message.hasReadTime = _timestamp;
@@ -690,23 +711,23 @@ class ChatLogic extends SuperController {
_clearUnreadCount() {
if (conversationInfo.unreadCount > 0) {
OpenIM.iMManager.conversationManager
.markConversationMessageAsRead(conversationID: conversationInfo.conversationID);
OpenIM.iMManager.conversationManager.markConversationMessageAsRead(
conversationID: conversationInfo.conversationID);
}
}
void _getInputState() async {
if (conversationInfo.isSingleChat) {
final result =
await OpenIM.iMManager.conversationManager.getInputStates(conversationInfo.conversationID, userID!);
final result = await OpenIM.iMManager.conversationManager
.getInputStates(conversationInfo.conversationID, userID!);
typing.value = result?.isNotEmpty == true;
}
}
void _changeInputStatus(bool focus) async {
if (conversationInfo.isSingleChat) {
await OpenIM.iMManager.conversationManager
.changeInputStates(conversationID: conversationInfo.conversationID, focus: focus);
await OpenIM.iMManager.conversationManager.changeInputStates(
conversationID: conversationInfo.conversationID, focus: focus);
}
}
@@ -714,18 +735,6 @@ class ChatLogic extends SuperController {
forceCloseToolbox.addSafely(true);
}
void onTapLocation() async {
var location = await Get.to(
const ChatWebViewMap(host: Config.locationHost, webKey: Config.webKey, webServerKey: Config.webServerKey),
transition: Transition.cupertino,
popGesture: true,
);
if (null != location) {
Logger.print(location);
sendLocation(location: location);
}
}
void onTapAlbum() async {
final List<AssetEntity>? assets = await AssetPicker.pickAssets(Get.context!,
pickerConfig: AssetPickerConfig(
@@ -743,7 +752,8 @@ class ChatLogic extends SuperController {
}
if (entity.videoDuration > const Duration(seconds: 5 * 60)) {
IMViews.showToast(sprintf(StrRes.selectVideoLimit, [5]) + StrRes.minute);
IMViews.showToast(
sprintf(StrRes.selectVideoLimit, [5]) + StrRes.minute);
return false;
}
return true;
@@ -837,10 +847,13 @@ class ChatLogic extends SuperController {
Future _handleAssets(AssetEntity? asset, {bool sendNow = true}) async {
if (null != asset) {
Logger.print('--------assets type-----${asset.type} create time: ${asset.createDateTime}');
Logger.print(
'--------assets type-----${asset.type} create time: ${asset.createDateTime}');
final originalFile = await asset.file;
final originalPath = originalFile!.path;
var path = originalPath.toLowerCase().endsWith('.gif') ? originalPath : originalFile.path;
var path = originalPath.toLowerCase().endsWith('.gif')
? originalPath
: originalFile.path;
Logger.print('--------assets path-----$path');
switch (asset.type) {
case AssetType.image:
@@ -1041,14 +1054,16 @@ class ChatLogic extends SuperController {
}
void copy(Message message) {
final content = copyTextMap[message.clientMsgID] ?? message.textElem?.content;
final content =
copyTextMap[message.clientMsgID] ?? message.textElem?.content;
if (null != content) {
IMUtils.copy(text: content.replaceAll('\u200B', ''));
}
}
Message indexOfMessage(int index, {bool calculate = true}) => IMUtils.calChatTimeInterval(
Message indexOfMessage(int index, {bool calculate = true}) =>
IMUtils.calChatTimeInterval(
messageList,
calculate: calculate,
).reversed.elementAt(index);
@@ -1111,7 +1126,11 @@ class ChatLogic extends SuperController {
void onDeleteEmoji() {
final input = inputCtrl.text;
final regexEmoji = emojiFaces.keys.toList().join('|').replaceAll('[', '\\[').replaceAll(']', '\\]');
final regexEmoji = emojiFaces.keys
.toList()
.join('|')
.replaceAll('[', '\\[')
.replaceAll(']', '\\]');
final list = [regexEmoji];
final pattern = '(${list.toList().join('|')})';
final emojiReg = RegExp(regexEmoji);
@@ -1186,7 +1205,8 @@ class ChatLogic extends SuperController {
void sendFavoritePic(int index, String url) async {
var emoji = cacheLogic.favoriteList.elementAt(index);
var message = await OpenIM.iMManager.messageManager.createFaceMessage(
data: json.encode({'url': emoji.url, 'width': emoji.width, 'height': emoji.height}),
data: json.encode(
{'url': emoji.url, 'width': emoji.width, 'height': emoji.height}),
);
_sendMessage(message);
}
@@ -1227,7 +1247,8 @@ class ChatLogic extends SuperController {
var diff = (end - _timestamp) ~/ 1000;
if (diff > 0) {
privateMessageList.addIf(() => !privateMessageList.contains(message), message);
privateMessageList.addIf(
() => !privateMessageList.contains(message), message);
}
return diff < 0 ? 0 : diff;
}
@@ -1255,7 +1276,8 @@ class ChatLogic extends SuperController {
userIDList: [OpenIM.iMManager.userID],
);
groupMembersInfo = list.firstOrNull;
groupMemberRoleLevel.value = groupMembersInfo?.roleLevel ?? GroupRoleLevel.member;
groupMemberRoleLevel.value =
groupMembersInfo?.roleLevel ?? GroupRoleLevel.member;
muteEndTime.value = groupMembersInfo?.muteEndTime ?? 0;
if (null != groupMembersInfo) {
memberUpdateInfoMap[OpenIM.iMManager.userID] = groupMembersInfo!;
@@ -1266,7 +1288,8 @@ class ChatLogic extends SuperController {
Future _queryOwnerAndAdmin() async {
if (isGroupChat) {
ownerAndAdmin = await OpenIM.iMManager.groupManager.getGroupMemberList(groupID: groupID!, filter: 5, count: 20);
ownerAndAdmin = await OpenIM.iMManager.groupManager
.getGroupMemberList(groupID: groupID!, filter: 5, count: 20);
}
return;
}
@@ -1303,7 +1326,9 @@ class ChatLogic extends SuperController {
bool get havePermissionMute =>
isGroupChat &&
(groupInfo?.ownerUserID == OpenIM.iMManager.userID /*||
(groupInfo?.ownerUserID ==
OpenIM.iMManager
.userID /*||
groupMembersInfo?.roleLevel == 2*/
);
@@ -1315,8 +1340,10 @@ class ChatLogic extends SuperController {
void _queryUserOnlineStatus() {
if (isSingleChat) {
OpenIM.iMManager.userManager.subscribeUsersStatus([userID!]).then((value) {
final status = value.firstWhereOrNull((element) => element.userID == userID);
OpenIM.iMManager.userManager
.subscribeUsersStatus([userID!]).then((value) {
final status =
value.firstWhereOrNull((element) => element.userID == userID);
_configUserStatusChanged(status);
});
userStatusChangedSub = imLogic.userStatusChangedSubject.listen((value) {
@@ -1336,8 +1363,9 @@ class ChatLogic extends SuperController {
void _configUserStatusChanged(UserStatusInfo? status) {
if (status != null) {
onlineStatus.value = status.status == 1;
onlineStatusDesc.value =
status.status == 0 ? StrRes.offline : _onlineStatusDes(status.platformIDs!) + StrRes.online;
onlineStatusDesc.value = status.status == 0
? StrRes.offline
: _onlineStatusDes(status.platformIDs!) + StrRes.online;
}
}
@@ -1496,10 +1524,12 @@ class ChatLogic extends SuperController {
if (message.sendID == OpenIM.iMManager.userID) {
canRevoke = true;
} else {
var list = await LoadingView.singleton
.wrap(asyncFunction: () => OpenIM.iMManager.groupManager.getGroupOwnerAndAdmin(groupID: groupID!));
var list = await LoadingView.singleton.wrap(
asyncFunction: () => OpenIM.iMManager.groupManager
.getGroupOwnerAndAdmin(groupID: groupID!));
var sender = list.firstWhereOrNull((e) => e.userID == message.sendID);
var revoker = list.firstWhereOrNull((e) => e.userID == OpenIM.iMManager.userID);
var revoker =
list.firstWhereOrNull((e) => e.userID == OpenIM.iMManager.userID);
if (revoker != null && sender == null) {
canRevoke = true;
@@ -1531,7 +1561,8 @@ class ChatLogic extends SuperController {
),
);
message.contentType = MessageType.revokeMessageNotification;
message.notificationElem = NotificationElem(detail: jsonEncode(_buildRevokeInfo(message)));
message.notificationElem =
NotificationElem(detail: jsonEncode(_buildRevokeInfo(message)));
messageList.refresh();
} catch (e) {
IMViews.showToast(e.toString());
@@ -1595,12 +1626,15 @@ class ChatLogic extends SuperController {
if (isGroupChat) {
if (groupMemberRoleLevel.value == GroupRoleLevel.owner ||
(groupMemberRoleLevel.value == GroupRoleLevel.admin &&
ownerAndAdmin.firstWhereOrNull((element) => element.userID == message.sendID) == null)) {
ownerAndAdmin.firstWhereOrNull(
(element) => element.userID == message.sendID) ==
null)) {
return true;
}
}
if (message.sendID == OpenIM.iMManager.userID) {
if (DateTime.now().millisecondsSinceEpoch - (message.sendTime ??= 0) < (1000 * 60 * 5)) {
if (DateTime.now().millisecondsSinceEpoch - (message.sendTime ??= 0) <
(1000 * 60 * 5)) {
return true;
}
}
@@ -1611,7 +1645,8 @@ class ChatLogic extends SuperController {
if (message.status != MessageStatus.succeeded) {
return false;
}
return message.contentType == MessageType.picture || message.contentType == MessageType.customFace;
return message.contentType == MessageType.picture ||
message.contentType == MessageType.customFace;
}
WillPopCallback? willPop() {
@@ -1658,12 +1693,14 @@ class ChatLogic extends SuperController {
var data = message.customElem!.data;
var map = json.decode(data!);
var customType = map['customType'];
return customType == CustomMessageType.deletedByFriend || customType == CustomMessageType.blockedByFriend;
return customType == CustomMessageType.deletedByFriend ||
customType == CustomMessageType.blockedByFriend;
}
return false;
}
void sendFriendVerification() => AppNavigator.startSendVerificationApplication(userID: userID);
void sendFriendVerification() =>
AppNavigator.startSendVerificationApplication(userID: userID);
void _setSdkSyncDataListener() {
connectionSub = imLogic.imSdkStatusPublishSubject.listen((value) {
@@ -1699,7 +1736,9 @@ class ChatLogic extends SuperController {
}
bool showBubbleBg(Message message) {
return !isNotificationType(message) && !isFailedHintMessage(message) && !isRevokeMessage(message);
return !isNotificationType(message) &&
!isFailedHintMessage(message) &&
!isRevokeMessage(message);
}
bool isRevokeMessage(Message message) {
@@ -1748,7 +1787,8 @@ class ChatLogic extends SuperController {
}
Future<void> _loadHistoryForSyncEnd() async {
final result = await OpenIM.iMManager.messageManager.getAdvancedHistoryMessageList(
final result =
await OpenIM.iMManager.messageManager.getAdvancedHistoryMessageList(
conversationID: conversationInfo.conversationID,
count: messageList.length < _pageSize ? _pageSize : messageList.length,
startMsg: null,
+20 -7
View File
@@ -86,13 +86,23 @@ class ChatPage extends StatelessWidget {
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);
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) {
if (message.contentType != MessageType.picture &&
message.contentType != MessageType.video) {
return null;
}
@@ -125,7 +135,8 @@ class ChatPage extends StatelessWidget {
child: Hero(
tag: message.clientMsgID!,
child: _buildMediaContent(message),
placeholderBuilder: (BuildContext context, Size heroSize, Widget child) => child,
placeholderBuilder:
(BuildContext context, Size heroSize, Widget child) => child,
),
);
}
@@ -155,7 +166,8 @@ class ChatPage extends StatelessWidget {
final content = data['content'];
final view = ChatCallItemView(type: type, content: content);
return CustomTypeInfo(view);
} else if (viewType == CustomMessageType.deletedByFriend || viewType == CustomMessageType.blockedByFriend) {
} else if (viewType == CustomMessageType.deletedByFriend ||
viewType == CustomMessageType.blockedByFriend) {
final view = ChatFriendRelationshipAbnormalHintView(
name: logic.nickname.value,
onTap: logic.sendFriendVerification,
@@ -244,10 +256,11 @@ class ChatPage extends StatelessWidget {
toolbox: ChatToolBox(
onTapAlbum: logic.onTapAlbum,
onTapCamera: logic.onTapCamera,
onTapCall: FeatureFlags.livekitCall && logic.isSingleChat ? logic.call : null,
onTapCall: FeatureFlags.livekitCall && logic.isSingleChat
? logic.call
: null,
onTapCard: logic.onTapCarte,
onTapFile: logic.onTapFile,
onTapLocation: logic.onTapLocation,
),
voiceRecordBar: bar,
emojiView: ChatEmojiView(