- 上游: 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>
447 lines
12 KiB
Dart
447 lines
12 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
|
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:openim_common/openim_common.dart';
|
|
import 'package:pull_to_refresh_new/pull_to_refresh.dart';
|
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
|
|
|
import '../../core/controller/app_controller.dart';
|
|
import '../../core/controller/im_controller.dart';
|
|
import '../../core/im_callback.dart';
|
|
import '../../routes/app_navigator.dart';
|
|
import '../contacts/add_by_search/add_by_search_logic.dart';
|
|
import '../home/home_logic.dart';
|
|
|
|
class ConversationLogic extends GetxController {
|
|
final popCtrl = CustomPopupMenuController();
|
|
final list = <ConversationInfo>[].obs;
|
|
final imLogic = Get.find<IMController>();
|
|
final homeLogic = Get.find<HomeLogic>();
|
|
final appLogic = Get.find<AppController>();
|
|
final refreshController = RefreshController();
|
|
final tempDraftText = <String, String>{};
|
|
final pageSize = 400;
|
|
|
|
final imStatus = IMSdkStatus.connectionSucceeded.obs;
|
|
bool reInstall = false;
|
|
|
|
final ItemScrollController itemScrollController = ItemScrollController();
|
|
final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create();
|
|
|
|
int scrollIndex = -1;
|
|
final onChangeConversations = <ConversationInfo>[];
|
|
|
|
@override
|
|
void onInit() {
|
|
getFirstPage();
|
|
imLogic.conversationAddedSubject.listen(onChanged);
|
|
imLogic.conversationChangedSubject.listen(onChanged);
|
|
homeLogic.onScrollToUnreadMessage = scrollTo;
|
|
imLogic.imSdkStatusSubject.listen((value) async {
|
|
final status = value.status;
|
|
final appReInstall = value.reInstall;
|
|
final progress = value.progress;
|
|
imStatus.value = status;
|
|
|
|
if (status == IMSdkStatus.syncStart) {
|
|
reInstall = appReInstall;
|
|
if (reInstall) {
|
|
EasyLoading.showProgress(0, status: StrRes.synchronizing);
|
|
}
|
|
}
|
|
|
|
Logger.print('IM SDK Status: $status, reinstall: $reInstall, progress: $progress');
|
|
|
|
if (status == IMSdkStatus.syncProgress && reInstall) {
|
|
final p = (progress!).toDouble() / 100.0;
|
|
|
|
EasyLoading.showProgress(p, status: '${StrRes.synchronizing}(${(p * 100.0).truncate()}%)');
|
|
} else if (status == IMSdkStatus.syncEnded || status == IMSdkStatus.syncFailed) {
|
|
EasyLoading.dismiss();
|
|
if (reInstall) {
|
|
onRefresh();
|
|
reInstall = false;
|
|
}
|
|
}
|
|
});
|
|
super.onInit();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
list.clear();
|
|
reInstall = false;
|
|
super.onClose();
|
|
}
|
|
|
|
void onChanged(List<ConversationInfo> newList) {
|
|
if (reInstall) {
|
|
onChangeConversations.addAll(newList);
|
|
}
|
|
for (var newValue in newList) {
|
|
Logger.print('======== conversation changed: ${newValue.toJson()} ========');
|
|
list.removeWhere((e) => e.conversationID == newValue.conversationID);
|
|
}
|
|
|
|
if (newList.length > pageSize) {
|
|
final tempList = newList;
|
|
|
|
while (true) {
|
|
final temp = tempList.sublist(0, pageSize);
|
|
list.insertAll(0, temp);
|
|
_sortConversationList();
|
|
|
|
if (tempList.length <= pageSize) {
|
|
break;
|
|
}
|
|
|
|
tempList.removeRange(0, pageSize);
|
|
}
|
|
} else {
|
|
list.insertAll(0, newList);
|
|
_sortConversationList();
|
|
Logger.print(
|
|
'======== conversation sort result: ${list.where((e) => e.unreadCount > 0).toList().map((e) => '${e.showName} [${e.conversationID}]: ${e.unreadCount}')} ========');
|
|
}
|
|
}
|
|
|
|
void promptSoundOrNotification(ConversationInfo info) {
|
|
if (imLogic.userInfo.value.globalRecvMsgOpt == 0 &&
|
|
info.recvMsgOpt == 0 &&
|
|
info.unreadCount > 0 &&
|
|
info.latestMsg?.sendID != OpenIM.iMManager.userID) {
|
|
appLogic.promptSoundOrNotification(info.latestMsg!.seq!);
|
|
}
|
|
}
|
|
|
|
String getConversationID(ConversationInfo info) {
|
|
return info.conversationID;
|
|
}
|
|
|
|
void markMessageHasRead(ConversationInfo info) {
|
|
_markMessageHasRead(info);
|
|
}
|
|
|
|
void pinConversation(ConversationInfo info) async {
|
|
OpenIM.iMManager.conversationManager.pinConversation(
|
|
conversationID: info.conversationID,
|
|
isPinned: !info.isPinned!,
|
|
);
|
|
}
|
|
|
|
void deleteConversation(ConversationInfo info) async {
|
|
await OpenIM.iMManager.conversationManager.deleteConversationAndDeleteAllMsg(
|
|
conversationID: info.conversationID,
|
|
);
|
|
list.remove(info);
|
|
}
|
|
|
|
void removeConversation(String id) {
|
|
list.removeWhere((e) => e.conversationID == id);
|
|
}
|
|
|
|
void setConversationDraft({required String cid, required String draftText}) {
|
|
OpenIM.iMManager.conversationManager.setConversationDraft(
|
|
conversationID: cid,
|
|
draftText: draftText,
|
|
);
|
|
}
|
|
|
|
String? getPrefixTag(ConversationInfo info) {
|
|
if (info.groupAtType == GroupAtType.groupNotification) {
|
|
return '[${StrRes.groupAc}]';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
String getContent(ConversationInfo info) {
|
|
try {
|
|
if (null != info.draftText && '' != info.draftText) {
|
|
var map = json.decode(info.draftText!);
|
|
String text = map['text'];
|
|
if (text.isNotEmpty) {
|
|
return text;
|
|
}
|
|
}
|
|
|
|
if (null == info.latestMsg) return "";
|
|
|
|
final text = IMUtils.parseNtf(info.latestMsg!, isConversation: true);
|
|
if (text != null) return text;
|
|
if (info.isSingleChat || info.latestMsg!.sendID == OpenIM.iMManager.userID)
|
|
return IMUtils.parseMsg(info.latestMsg!, isConversation: true);
|
|
|
|
return "${info.latestMsg!.senderNickname}: ${IMUtils.parseMsg(info.latestMsg!, isConversation: true)} ";
|
|
} catch (e, s) {
|
|
Logger.print('------e:$e s:$s');
|
|
}
|
|
return '[${StrRes.unsupportedMessage}]';
|
|
}
|
|
|
|
String? getAvatar(ConversationInfo info) {
|
|
return info.faceURL;
|
|
}
|
|
|
|
bool isGroupChat(ConversationInfo info) {
|
|
return info.isGroupChat;
|
|
}
|
|
|
|
String getShowName(ConversationInfo info) {
|
|
if (info.showName == null || info.showName.isBlank!) {
|
|
return info.userID!;
|
|
}
|
|
return info.showName!;
|
|
}
|
|
|
|
String getTime(ConversationInfo info) {
|
|
return IMUtils.getChatTimeline(info.latestMsgSendTime!);
|
|
}
|
|
|
|
int getUnreadCount(ConversationInfo info) {
|
|
return info.unreadCount;
|
|
}
|
|
|
|
bool existUnreadMsg(ConversationInfo info) {
|
|
return getUnreadCount(info) > 0;
|
|
}
|
|
|
|
bool isPinned(ConversationInfo info) {
|
|
return info.isPinned!;
|
|
}
|
|
|
|
bool isNotDisturb(ConversationInfo info) {
|
|
return info.recvMsgOpt != 0;
|
|
}
|
|
|
|
bool isUserGroup(int index) => list.elementAt(index).isGroupChat;
|
|
|
|
void _markMessageHasRead(
|
|
ConversationInfo conversation,
|
|
) {
|
|
if (conversation.unreadCount == 0) {
|
|
return;
|
|
}
|
|
OpenIM.iMManager.conversationManager.markConversationMessageAsRead(
|
|
conversationID: conversation.conversationID,
|
|
);
|
|
}
|
|
|
|
void _setupDraftText({
|
|
required String conversationID,
|
|
required String oldDraftText,
|
|
required String newDraftText,
|
|
}) {
|
|
if (oldDraftText.isEmpty && newDraftText.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
Logger.print('draftText:$newDraftText');
|
|
OpenIM.iMManager.conversationManager.setConversationDraft(
|
|
conversationID: conversationID,
|
|
draftText: newDraftText,
|
|
);
|
|
}
|
|
|
|
String? get imSdkStatus {
|
|
switch (imStatus.value) {
|
|
case IMSdkStatus.syncStart:
|
|
case IMSdkStatus.synchronizing:
|
|
case IMSdkStatus.syncProgress:
|
|
return StrRes.synchronizing;
|
|
case IMSdkStatus.syncFailed:
|
|
return StrRes.syncFailed;
|
|
case IMSdkStatus.connecting:
|
|
return StrRes.connecting;
|
|
case IMSdkStatus.connectionFailed:
|
|
return StrRes.connectionFailed;
|
|
case IMSdkStatus.connectionSucceeded:
|
|
case IMSdkStatus.syncEnded:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
bool get isFailedSdkStatus =>
|
|
imStatus.value == IMSdkStatus.connectionFailed || imStatus.value == IMSdkStatus.syncFailed;
|
|
|
|
void _sortConversationList() => OpenIM.iMManager.conversationManager.simpleSort(list);
|
|
|
|
void onRefresh() async {
|
|
late List<ConversationInfo> list;
|
|
try {
|
|
list = await _request();
|
|
this.list.assignAll(list);
|
|
|
|
if (list.isEmpty || list.length < pageSize) {
|
|
refreshController.loadNoData();
|
|
} else {
|
|
refreshController.loadComplete();
|
|
}
|
|
} finally {
|
|
refreshController.refreshCompleted();
|
|
}
|
|
}
|
|
|
|
static Future<List<ConversationInfo>> getConversationFirstPage() async {
|
|
final result = await OpenIM.iMManager.conversationManager.getConversationListSplit(offset: 0, count: 400);
|
|
|
|
return result;
|
|
}
|
|
|
|
void getFirstPage() async {
|
|
final result = homeLogic.conversationsAtFirstPage;
|
|
|
|
list.assignAll(result);
|
|
_sortConversationList();
|
|
}
|
|
|
|
void clearConversations() {
|
|
list.clear();
|
|
}
|
|
|
|
_request() async {
|
|
final temp = <ConversationInfo>[];
|
|
|
|
while (true) {
|
|
var result = await OpenIM.iMManager.conversationManager.getConversationListSplit(
|
|
offset: temp.length,
|
|
count: pageSize,
|
|
);
|
|
if (onChangeConversations.isNotEmpty) {
|
|
final bSet = Set.from(onChangeConversations);
|
|
|
|
Logger.print('replace conversation: [${onChangeConversations.length}], $bSet');
|
|
|
|
for (int i = 0; i < result.length; i++) {
|
|
final info = result[i];
|
|
|
|
if (bSet.contains(info)) {
|
|
result[i] = onChangeConversations[onChangeConversations.indexOf(info)];
|
|
}
|
|
}
|
|
}
|
|
temp.addAll(result);
|
|
|
|
if (result.length < pageSize) {
|
|
break;
|
|
}
|
|
}
|
|
onChangeConversations.clear();
|
|
|
|
return temp;
|
|
}
|
|
|
|
bool isValidConversation(ConversationInfo info) {
|
|
return info.isValid;
|
|
}
|
|
|
|
void scrollTo() {
|
|
if (list.isEmpty) return;
|
|
|
|
final positions = itemPositionsListener.itemPositions.value;
|
|
|
|
int min = 0;
|
|
if (positions.isNotEmpty) {
|
|
min = (positions
|
|
.where((ItemPosition position) => position.itemTrailingEdge > 0)
|
|
.reduce((ItemPosition min, ItemPosition position) =>
|
|
position.itemTrailingEdge < min.itemTrailingEdge ? position : min)
|
|
.index);
|
|
}
|
|
|
|
var currentIndex = 0;
|
|
|
|
for (var i = min + 1; i < list.length; i++) {
|
|
final item = list[i];
|
|
|
|
if (item.unreadCount > 0) {
|
|
currentIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
scrollIndex = currentIndex;
|
|
|
|
if (scrollIndex == 0) {
|
|
itemScrollController.jumpTo(index: 0);
|
|
} else {
|
|
itemScrollController.scrollTo(index: scrollIndex, duration: const Duration(milliseconds: 300));
|
|
}
|
|
}
|
|
|
|
static Future<ConversationInfo> _createConversation({
|
|
required String sourceID,
|
|
required int sessionType,
|
|
}) =>
|
|
LoadingView.singleton.wrap(
|
|
asyncFunction: () => OpenIM.iMManager.conversationManager.getOneConversation(
|
|
sourceID: sourceID,
|
|
sessionType: sessionType,
|
|
));
|
|
|
|
Future<bool> _jumpOANtf(ConversationInfo info) async {
|
|
if (info.conversationType == ConversationType.notification) {
|
|
await AppNavigator.startOANtfList(info: info);
|
|
_markMessageHasRead(info);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void toChat({
|
|
bool offUntilHome = true,
|
|
String? userID,
|
|
String? groupID,
|
|
String? nickname,
|
|
String? faceURL,
|
|
int? sessionType,
|
|
ConversationInfo? conversationInfo,
|
|
Message? searchMessage,
|
|
}) async {
|
|
conversationInfo ??= await _createConversation(
|
|
sourceID: userID ?? groupID!,
|
|
sessionType: userID == null ? sessionType! : ConversationType.single,
|
|
);
|
|
|
|
if (await _jumpOANtf(conversationInfo)) return;
|
|
|
|
await AppNavigator.startChat(
|
|
offUntilHome: offUntilHome,
|
|
draftText: conversationInfo.draftText,
|
|
conversationInfo: conversationInfo,
|
|
searchMessage: searchMessage,
|
|
);
|
|
|
|
var newDraftText = tempDraftText[conversationInfo.conversationID];
|
|
|
|
_markMessageHasRead(conversationInfo);
|
|
|
|
_setupDraftText(
|
|
conversationID: conversationInfo.conversationID,
|
|
oldDraftText: conversationInfo.draftText ?? '',
|
|
newDraftText: newDraftText!,
|
|
);
|
|
|
|
bool equal(e) => e.conversationID == conversationInfo?.conversationID;
|
|
|
|
var groupAtType = list.firstWhereOrNull(equal)?.groupAtType;
|
|
if (groupAtType != GroupAtType.atNormal) {
|
|
OpenIM.iMManager.conversationManager.resetConversationGroupAtType(
|
|
conversationID: conversationInfo.conversationID,
|
|
);
|
|
}
|
|
}
|
|
|
|
scan() => AppNavigator.startScan();
|
|
|
|
addFriend() => AppNavigator.startAddContactsBySearch(searchType: SearchType.user);
|
|
|
|
createGroup() => AppNavigator.startCreateGroup(defaultCheckedList: [OpenIM.iMManager.userInfo]);
|
|
|
|
addGroup() => AppNavigator.startAddContactsBySearch(searchType: SearchType.group);
|
|
|
|
void globalSearch() => AppNavigator.startGlobalSearch();
|
|
}
|