From 2b2c2d28a42f57d1cd8e635153ce398685a0bc89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BC=96=E7=A0=81=E5=B7=A5=E7=A8=8B=E5=B8=88?= Date: Thu, 20 Aug 2026 02:52:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(mobile-next):=20=E6=8A=8A=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E3=80=81=E5=A4=B1=E8=B4=A5=E3=80=81=E6=96=AD=E7=BD=91=E6=8E=A5?= =?UTF-8?q?=E5=88=B0=E7=9C=9F=E5=AE=9E=20SDK=20=E4=B8=8E=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 会话列表、通讯录、好友申请、添加同事页覆盖 CompanyLoadingView/CompanyFailView; 主页断网横幅跟随 IMSdkStatus.connectionFailed。不伪造状态。 Co-authored-by: Cursor Co-authored-by: multica-agent --- .../lib/company/ui/add_colleague_page.dart | 178 +++++++++++------- .../lib/pages/contacts/contacts_logic.dart | 44 ++++- .../lib/pages/contacts/contacts_view.dart | 25 ++- .../friend_requests_logic.dart | 72 ++++--- .../friend_requests/friend_requests_view.dart | 53 ++++-- .../conversation/conversation_logic.dart | 109 ++++++++--- .../pages/conversation/conversation_view.dart | 77 +++++--- mobile-next/lib/pages/home/home_logic.dart | 12 +- mobile-next/lib/pages/home/home_view.dart | 48 +++-- 9 files changed, 432 insertions(+), 186 deletions(-) diff --git a/mobile-next/lib/company/ui/add_colleague_page.dart b/mobile-next/lib/company/ui/add_colleague_page.dart index b01cd23..255cce4 100644 --- a/mobile-next/lib/company/ui/add_colleague_page.dart +++ b/mobile-next/lib/company/ui/add_colleague_page.dart @@ -6,6 +6,7 @@ import 'package:openim_common/openim_common.dart'; import '../auth/auth_api.dart'; import '../brand/app_tokens.dart'; import '../directory/directory_api.dart'; +import 'state_views.dart'; class AddColleagueLogic extends GetxController { final staffCtrl = TextEditingController(); @@ -13,9 +14,17 @@ class AddColleagueLogic extends GetxController { final sending = false.obs; final staffError = RxnString(); final directoryHint = RxnString(); + final loading = true.obs; + final loadFailed = false.obs; final _directory = DirectoryApi(); + @override + void onReady() { + reload(); + super.onReady(); + } + @override void onClose() { staffCtrl.dispose(); @@ -23,6 +32,18 @@ class AddColleagueLogic extends GetxController { super.onClose(); } + Future reload() async { + loading.value = true; + loadFailed.value = false; + try { + await _directory.search(keyword: '', limit: 1); + } catch (e) { + loadFailed.value = true; + } finally { + loading.value = false; + } + } + Future lookupDirectory() async { final id = staffCtrl.text.trim(); if (id.isEmpty) { @@ -35,7 +56,8 @@ class AddColleagueLogic extends GetxController { if (hit.isNotEmpty) { directoryHint.value = hit.first.nickname; } else if (items.isNotEmpty) { - directoryHint.value = items.map((e) => '${e.nickname}(${e.userID})').join('、'); + directoryHint.value = + items.map((e) => '${e.nickname}(${e.userID})').join('、'); } else { directoryHint.value = null; } @@ -56,7 +78,8 @@ class AddColleagueLogic extends GetxController { try { await OpenIM.iMManager.friendshipManager.addFriend( userID: id, - reason: msgCtrl.text.trim().isEmpty ? '你好,我想加你为同事' : msgCtrl.text.trim(), + reason: + msgCtrl.text.trim().isEmpty ? '你好,我想加你为同事' : msgCtrl.text.trim(), ); IMViews.showToast('申请已发送,等对方通过'); Get.back(); @@ -85,67 +108,84 @@ class AddColleaguePage extends StatelessWidget { onPressed: Get.back, ), ), - body: Padding( - padding: const EdgeInsets.all(AppGap.x4), - child: Column( - children: [ - Obx(() => _box( - controller: logic.staffCtrl, - hint: '请输入对方工号', - height: 48, - error: logic.staffError.value, - enabled: !logic.sending.value, - onChanged: (_) { - logic.staffError.value = null; - logic.lookupDirectory(); - }, - )), - Obx(() { - final hint = logic.directoryHint.value; - if (hint == null) return const SizedBox.shrink(); - return Padding( - padding: const EdgeInsets.only(top: AppGap.x2), - child: Align( - alignment: Alignment.centerLeft, - child: Text(hint, style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), - ), - ); - }), - const SizedBox(height: AppGap.x3), - _box( - controller: logic.msgCtrl, - hint: '你好,我想加你为同事', - height: 96, - maxLines: 4, - enabled: !logic.sending.value, - ), - const Spacer(), - Obx(() { - final busy = logic.sending.value; - return SizedBox( - width: double.infinity, + body: Obx(() { + if (logic.loading.value) { + return const CompanyLoadingView(); + } + if (logic.loadFailed.value) { + return CompanyFailView(onRetry: logic.reload); + } + return Padding( + padding: const EdgeInsets.all(AppGap.x4), + child: Column( + children: [ + _box( + controller: logic.staffCtrl, + hint: '请输入对方工号', height: 48, - child: FilledButton( - onPressed: busy ? null : logic.send, - style: FilledButton.styleFrom( - backgroundColor: AppColors.primary, - disabledBackgroundColor: AppColors.primary.withOpacity(0.4), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.control)), + error: logic.staffError.value, + enabled: !logic.sending.value, + onChanged: (_) { + logic.staffError.value = null; + logic.lookupDirectory(); + }, + ), + Builder(builder: (_) { + final hint = logic.directoryHint.value; + if (hint == null) return const SizedBox.shrink(); + return Padding( + padding: const EdgeInsets.only(top: AppGap.x2), + child: Align( + alignment: Alignment.centerLeft, + child: Text(hint, + style: const TextStyle( + fontSize: AppFont.sub, + color: AppColors.textSecondary)), ), - child: busy - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), - ) - : const Text('发送申请', style: TextStyle(fontSize: AppFont.body, color: Colors.white)), - ), - ); - }), - const SizedBox(height: AppGap.x4), - ], - ), - ), + ); + }), + const SizedBox(height: AppGap.x3), + _box( + controller: logic.msgCtrl, + hint: '你好,我想加你为同事', + height: 96, + maxLines: 4, + enabled: !logic.sending.value, + ), + const Spacer(), + Builder(builder: (_) { + final busy = logic.sending.value; + return SizedBox( + width: double.infinity, + height: 48, + child: FilledButton( + onPressed: busy ? null : logic.send, + style: FilledButton.styleFrom( + backgroundColor: AppColors.primary, + disabledBackgroundColor: + AppColors.primary.withOpacity(0.4), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(AppRadius.control)), + ), + child: busy + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, color: Colors.white), + ) + : const Text('发送申请', + style: TextStyle( + fontSize: AppFont.body, color: Colors.white)), + ), + ); + }), + const SizedBox(height: AppGap.x4), + ], + ), + ); + }), ); } @@ -163,30 +203,36 @@ class AddColleaguePage extends StatelessWidget { children: [ Container( height: height, - padding: const EdgeInsets.symmetric(horizontal: AppGap.x3, vertical: AppGap.x2), + padding: const EdgeInsets.symmetric( + horizontal: AppGap.x3, vertical: AppGap.x2), decoration: BoxDecoration( color: AppColors.searchBg, borderRadius: BorderRadius.circular(AppRadius.control), - border: Border.all(color: error != null ? AppColors.danger : Colors.transparent), + border: Border.all( + color: error != null ? AppColors.danger : Colors.transparent), ), child: TextField( controller: controller, maxLines: maxLines, enabled: enabled, onChanged: onChanged, - style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary), + style: const TextStyle( + fontSize: AppFont.body, color: AppColors.textPrimary), decoration: InputDecoration( isDense: true, border: InputBorder.none, hintText: hint, - hintStyle: const TextStyle(fontSize: AppFont.body, color: AppColors.textSecondary), + hintStyle: const TextStyle( + fontSize: AppFont.body, color: AppColors.textSecondary), ), ), ), if (error != null) Padding( padding: const EdgeInsets.only(top: AppGap.x1), - child: Text(error, style: const TextStyle(fontSize: AppFont.small, color: AppColors.danger)), + child: Text(error, + style: const TextStyle( + fontSize: AppFont.small, color: AppColors.danger)), ), ], ); diff --git a/mobile-next/lib/pages/contacts/contacts_logic.dart b/mobile-next/lib/pages/contacts/contacts_logic.dart index f717f42..6a68c33 100644 --- a/mobile-next/lib/pages/contacts/contacts_logic.dart +++ b/mobile-next/lib/pages/contacts/contacts_logic.dart @@ -14,10 +14,14 @@ class ContactsLogic extends GetxController final homeLogic = Get.find(); final friendApplicationList = []; + final loading = true.obs; + final loadFailed = false.obs; - int get friendApplicationCount => homeLogic.unhandledFriendApplicationCount.value; + int get friendApplicationCount => + homeLogic.unhandledFriendApplicationCount.value; - int get groupApplicationCount => homeLogic.unhandledGroupApplicationCount.value; + int get groupApplicationCount => + homeLogic.unhandledGroupApplicationCount.value; @override void onInit() { @@ -28,6 +32,36 @@ class ContactsLogic extends GetxController super.onInit(); } + @override + void onReady() { + reload(); + super.onReady(); + } + + Future reload() async { + loading.value = true; + loadFailed.value = false; + try { + await Future.wait([ + OpenIM.iMManager.friendshipManager + .getFriendApplicationListAsRecipient(), + OpenIM.iMManager.groupManager.getGroupApplicationListAsRecipient(), + OpenIM.iMManager.friendshipManager.getFriendListPage( + offset: 0, + count: 1, + filterBlack: true, + ), + ]); + homeLogic.getUnhandledFriendApplicationCount(); + homeLogic.getUnhandledGroupApplicationCount(); + } catch (e, s) { + Logger.print('contacts reload e: $e $s'); + loadFailed.value = true; + } finally { + loading.value = false; + } + } + @override void onClose() { PackageBridge.selectContactsBridge = null; @@ -69,7 +103,8 @@ class ContactsLogic extends GetxController ); @override - viewUserProfile(String userID, String? nickname, String? faceURL, [String? groupID]) => + viewUserProfile(String userID, String? nickname, String? faceURL, + [String? groupID]) => AppNavigator.startUserProfilePane( userID: userID, nickname: nickname, @@ -85,5 +120,6 @@ class ContactsLogic extends GetxController ); @override - scanOutUserID(String userID) => AppNavigator.startUserProfilePane(userID: userID, offAndToNamed: true); + scanOutUserID(String userID) => + AppNavigator.startUserProfilePane(userID: userID, offAndToNamed: true); } diff --git a/mobile-next/lib/pages/contacts/contacts_view.dart b/mobile-next/lib/pages/contacts/contacts_view.dart index 296723a..445de25 100644 --- a/mobile-next/lib/pages/contacts/contacts_view.dart +++ b/mobile-next/lib/pages/contacts/contacts_view.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:openim_common/openim_common.dart'; import '../../company/brand/app_tokens.dart'; +import '../../company/ui/state_views.dart'; import 'contacts_logic.dart'; class ContactsPage extends StatelessWidget { @@ -19,12 +20,19 @@ class ContactsPage extends StatelessWidget { actions: [ IconButton( onPressed: logic.addContacts, - icon: const Icon(Icons.person_add_alt, size: AppIconSize.md, color: AppColors.textPrimary), + icon: const Icon(Icons.person_add_alt, + size: AppIconSize.md, color: AppColors.textPrimary), ), ], ), - body: Obx( - () => Column( + body: Obx(() { + if (logic.loading.value) { + return const CompanyLoadingView(); + } + if (logic.loadFailed.value) { + return CompanyFailView(onRetry: logic.reload); + } + return Column( children: [ _entry( icon: Icons.person_add_alt, @@ -44,8 +52,8 @@ class ContactsPage extends StatelessWidget { onTap: logic.myFriend, ), ], - ), - ), + ); + }), ); } @@ -71,10 +79,13 @@ class ContactsPage extends StatelessWidget { borderRadius: BorderRadius.circular(AppRadius.control), ), alignment: Alignment.center, - child: Icon(icon, size: AppIconSize.sm, color: AppColors.primary), + child: + Icon(icon, size: AppIconSize.sm, color: AppColors.primary), ), const SizedBox(width: AppGap.x3), - Text(label, style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary)), + Text(label, + style: const TextStyle( + fontSize: AppFont.body, color: AppColors.textPrimary)), const Spacer(), if (count > 0) UnreadCountView(count: count), ], diff --git a/mobile-next/lib/pages/contacts/friend_requests/friend_requests_logic.dart b/mobile-next/lib/pages/contacts/friend_requests/friend_requests_logic.dart index f8d4268..91c09b4 100644 --- a/mobile-next/lib/pages/contacts/friend_requests/friend_requests_logic.dart +++ b/mobile-next/lib/pages/contacts/friend_requests/friend_requests_logic.dart @@ -11,6 +11,8 @@ class FriendRequestsLogic extends GetxController { final imLogic = Get.find(); final homeLogic = Get.find(); final applicationList = [].obs; + final loading = true.obs; + final loadFailed = false.obs; late StreamSubscription faSub; @override @@ -27,6 +29,8 @@ class FriendRequestsLogic extends GetxController { super.onReady(); } + void retry() => _getFriendRequestsList(); + @override void onClose() { faSub.cancel(); @@ -35,43 +39,56 @@ class FriendRequestsLogic extends GetxController { } void _getFriendRequestsList() async { - final list = await Future.wait([ - OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient(), - OpenIM.iMManager.friendshipManager.getFriendApplicationListAsApplicant(), - ]); + loading.value = true; + loadFailed.value = false; + try { + final list = await Future.wait([ + OpenIM.iMManager.friendshipManager + .getFriendApplicationListAsRecipient(), + OpenIM.iMManager.friendshipManager + .getFriendApplicationListAsApplicant(), + ]); - final allList = []; - allList - ..addAll(list[0]) - ..addAll(list[1]); + final allList = []; + allList + ..addAll(list[0]) + ..addAll(list[1]); - allList.sort((a, b) { - if (a.createTime! > b.createTime!) { - return -1; - } else if (a.createTime! < b.createTime!) { - return 1; - } - return 0; - }); + allList.sort((a, b) { + if (a.createTime! > b.createTime!) { + return -1; + } else if (a.createTime! < b.createTime!) { + return 1; + } + return 0; + }); - var haveReadList = DataSp.getHaveReadUnHandleFriendApplication(); - haveReadList ??= []; - for (var e in list[0]) { - var id = IMUtils.buildFriendApplicationID(e); - if (!haveReadList.contains(id)) { - haveReadList.add(id); + var haveReadList = DataSp.getHaveReadUnHandleFriendApplication(); + haveReadList ??= []; + for (var e in list[0]) { + var id = IMUtils.buildFriendApplicationID(e); + if (!haveReadList.contains(id)) { + haveReadList.add(id); + } } + DataSp.putHaveReadUnHandleFriendApplication(haveReadList); + applicationList.assignAll(allList); + } catch (e, s) { + Logger.print('friend requests e: $e $s'); + loadFailed.value = true; + } finally { + loading.value = false; } - DataSp.putHaveReadUnHandleFriendApplication(haveReadList); - applicationList.assignAll(allList); } - bool isISendRequest(FriendApplicationInfo info) => info.fromUserID == OpenIM.iMManager.userID; + bool isISendRequest(FriendApplicationInfo info) => + info.fromUserID == OpenIM.iMManager.userID; void acceptFriendApplication(FriendApplicationInfo info) async { try { await LoadingView.singleton.wrap( - asyncFunction: () => OpenIM.iMManager.friendshipManager.acceptFriendApplication(userID: info.fromUserID!), + asyncFunction: () => OpenIM.iMManager.friendshipManager + .acceptFriendApplication(userID: info.fromUserID!), ); IMViews.showToast(StrRes.addSuccessfully); _getFriendRequestsList(); @@ -83,7 +100,8 @@ class FriendRequestsLogic extends GetxController { void refuseFriendApplication(FriendApplicationInfo info) async { try { await LoadingView.singleton.wrap( - asyncFunction: () => OpenIM.iMManager.friendshipManager.refuseFriendApplication(userID: info.fromUserID!), + asyncFunction: () => OpenIM.iMManager.friendshipManager + .refuseFriendApplication(userID: info.fromUserID!), ); IMViews.showToast(StrRes.rejectSuccessfully); _getFriendRequestsList(); diff --git a/mobile-next/lib/pages/contacts/friend_requests/friend_requests_view.dart b/mobile-next/lib/pages/contacts/friend_requests/friend_requests_view.dart index e206fe7..38dcd79 100644 --- a/mobile-next/lib/pages/contacts/friend_requests/friend_requests_view.dart +++ b/mobile-next/lib/pages/contacts/friend_requests/friend_requests_view.dart @@ -18,6 +18,12 @@ class FriendRequestsPage extends StatelessWidget { appBar: AppBar(title: const Text('新的同事')), backgroundColor: AppColors.pageBg, body: Obx(() { + if (logic.loading.value) { + return const CompanyLoadingView(); + } + if (logic.loadFailed.value) { + return CompanyFailView(onRetry: logic.retry); + } if (logic.applicationList.isEmpty) { return const CompanyEmptyView( icon: Icons.person_outline, @@ -27,7 +33,8 @@ class FriendRequestsPage extends StatelessWidget { } return ListView.builder( itemCount: logic.applicationList.length, - itemBuilder: (_, index) => _buildItemView(logic.applicationList[index]), + itemBuilder: (_, index) => + _buildItemView(logic.applicationList[index]), ); }), ); @@ -43,10 +50,12 @@ class FriendRequestsPage extends StatelessWidget { return Container( constraints: const BoxConstraints(minHeight: 72), - padding: const EdgeInsets.symmetric(horizontal: AppGap.x4, vertical: AppGap.x3), + padding: const EdgeInsets.symmetric( + horizontal: AppGap.x4, vertical: AppGap.x3), decoration: const BoxDecoration( color: AppColors.pageBg, - border: Border(bottom: BorderSide(color: AppColors.divider, width: 0.5)), + border: + Border(bottom: BorderSide(color: AppColors.divider, width: 0.5)), ), child: Row( children: [ @@ -61,21 +70,25 @@ class FriendRequestsPage extends StatelessWidget { name ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary), + style: const TextStyle( + fontSize: AppFont.body, color: AppColors.textPrimary), ), if (IMUtils.isNotNullEmptyStr(info.reqMsg)) Text( info.reqMsg ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary), + style: const TextStyle( + fontSize: AppFont.sub, color: AppColors.textSecondary), ), ], ), ), const SizedBox(width: AppGap.x2), if (waiting && isISendRequest) - const Text('等待对方通过', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), + const Text('等待对方通过', + style: TextStyle( + fontSize: AppFont.sub, color: AppColors.textSecondary)), if (waiting && !isISendRequest) SizedBox( width: 132, @@ -87,11 +100,15 @@ class FriendRequestsPage extends StatelessWidget { style: OutlinedButton.styleFrom( minimumSize: const Size(0, 32), padding: EdgeInsets.zero, - side: const BorderSide(color: AppColors.textSecondary, width: 0.5), + side: const BorderSide( + color: AppColors.textSecondary, width: 0.5), foregroundColor: AppColors.textPrimary, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.control)), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(AppRadius.control)), ), - child: const Text('拒绝', style: TextStyle(fontSize: AppFont.sub)), + child: const Text('拒绝', + style: TextStyle(fontSize: AppFont.sub)), ), ), const SizedBox(width: AppGap.x2), @@ -102,16 +119,26 @@ class FriendRequestsPage extends StatelessWidget { minimumSize: const Size(0, 32), padding: EdgeInsets.zero, backgroundColor: AppColors.primary, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.control)), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(AppRadius.control)), ), - child: const Text('接受', style: TextStyle(fontSize: AppFont.sub, color: Colors.white)), + child: const Text('接受', + style: TextStyle( + fontSize: AppFont.sub, color: Colors.white)), ), ), ], ), ), - if (agreed) const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), - if (rejected) const Text('已拒绝', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), + if (agreed) + const Text('已添加', + style: TextStyle( + fontSize: AppFont.sub, color: AppColors.textSecondary)), + if (rejected) + const Text('已拒绝', + style: TextStyle( + fontSize: AppFont.sub, color: AppColors.textSecondary)), ], ), ); diff --git a/mobile-next/lib/pages/conversation/conversation_logic.dart b/mobile-next/lib/pages/conversation/conversation_logic.dart index 3125997..9336c19 100644 --- a/mobile-next/lib/pages/conversation/conversation_logic.dart +++ b/mobile-next/lib/pages/conversation/conversation_logic.dart @@ -25,10 +25,13 @@ class ConversationLogic extends GetxController { final pageSize = 400; final imStatus = IMSdkStatus.connectionSucceeded.obs; + final loading = true.obs; + final loadFailed = false.obs; bool reInstall = false; final ItemScrollController itemScrollController = ItemScrollController(); - final ItemPositionsListener itemPositionsListener = ItemPositionsListener.create(); + final ItemPositionsListener itemPositionsListener = + ItemPositionsListener.create(); int scrollIndex = -1; final onChangeConversations = []; @@ -52,13 +55,16 @@ class ConversationLogic extends GetxController { } } - Logger.print('IM SDK Status: $status, reinstall: $reInstall, progress: $progress'); + 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.showProgress(p, + status: '${StrRes.synchronizing}(${(p * 100.0).truncate()}%)'); + } else if (status == IMSdkStatus.syncEnded || + status == IMSdkStatus.syncFailed) { EasyLoading.dismiss(); if (reInstall) { onRefresh(); @@ -81,7 +87,8 @@ class ConversationLogic extends GetxController { onChangeConversations.addAll(newList); } for (var newValue in newList) { - Logger.print('======== conversation changed: ${newValue.toJson()} ========'); + Logger.print( + '======== conversation changed: ${newValue.toJson()} ========'); list.removeWhere((e) => e.conversationID == newValue.conversationID); } @@ -132,7 +139,8 @@ class ConversationLogic extends GetxController { } void deleteConversation(ConversationInfo info) async { - await OpenIM.iMManager.conversationManager.deleteConversationAndDeleteAllMsg( + await OpenIM.iMManager.conversationManager + .deleteConversationAndDeleteAllMsg( conversationID: info.conversationID, ); list.remove(info); @@ -171,7 +179,8 @@ class ConversationLogic extends GetxController { final text = IMUtils.parseNtf(info.latestMsg!, isConversation: true); if (text != null) return text; - if (info.isSingleChat || info.latestMsg!.sendID == OpenIM.iMManager.userID) + 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)} "; @@ -264,16 +273,31 @@ class ConversationLogic extends GetxController { } bool get isFailedSdkStatus => - imStatus.value == IMSdkStatus.connectionFailed || imStatus.value == IMSdkStatus.syncFailed; + imStatus.value == IMSdkStatus.connectionFailed || + imStatus.value == IMSdkStatus.syncFailed; - void _sortConversationList() => OpenIM.iMManager.conversationManager.simpleSort(list); + bool get showLoading { + if (loading.value) return true; + if (list.isNotEmpty) return false; + switch (imStatus.value) { + case IMSdkStatus.connecting: + case IMSdkStatus.syncStart: + case IMSdkStatus.synchronizing: + case IMSdkStatus.syncProgress: + return true; + default: + return false; + } + } + + bool get showFail => list.isEmpty && (loadFailed.value || isFailedSdkStatus); + + void _sortConversationList() => + OpenIM.iMManager.conversationManager.simpleSort(list); void onRefresh() async { - late List list; try { - list = await _request(); - this.list.assignAll(list); - + await reloadConversations(); if (list.isEmpty || list.length < pageSize) { refreshController.loadNoData(); } else { @@ -285,16 +309,46 @@ class ConversationLogic extends GetxController { } static Future> getConversationFirstPage() async { - final result = await OpenIM.iMManager.conversationManager.getConversationListSplit(offset: 0, count: 400); + final result = await OpenIM.iMManager.conversationManager + .getConversationListSplit(offset: 0, count: 400); return result; } void getFirstPage() async { - final result = homeLogic.conversationsAtFirstPage; + loading.value = true; + loadFailed.value = false; + try { + final cached = homeLogic.conversationsAtFirstPage; + if (cached.isNotEmpty) { + list.assignAll(cached); + _sortConversationList(); + return; + } + final result = await _request(); + list.assignAll(result); + _sortConversationList(); + } catch (e, s) { + Logger.print('conversation first page e: $e $s'); + loadFailed.value = true; + } finally { + loading.value = false; + } + } - list.assignAll(result); - _sortConversationList(); + Future reloadConversations() async { + loading.value = true; + loadFailed.value = false; + try { + final result = await _request(); + list.assignAll(result); + _sortConversationList(); + } catch (e, s) { + Logger.print('conversation reload e: $e $s'); + loadFailed.value = true; + } finally { + loading.value = false; + } } void clearConversations() { @@ -305,20 +359,23 @@ class ConversationLogic extends GetxController { final temp = []; while (true) { - var result = await OpenIM.iMManager.conversationManager.getConversationListSplit( + 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'); + 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)]; + result[i] = + onChangeConversations[onChangeConversations.indexOf(info)]; } } } @@ -367,7 +424,8 @@ class ConversationLogic extends GetxController { if (scrollIndex == 0) { itemScrollController.jumpTo(index: 0); } else { - itemScrollController.scrollTo(index: scrollIndex, duration: const Duration(milliseconds: 300)); + itemScrollController.scrollTo( + index: scrollIndex, duration: const Duration(milliseconds: 300)); } } @@ -376,7 +434,8 @@ class ConversationLogic extends GetxController { required int sessionType, }) => LoadingView.singleton.wrap( - asyncFunction: () => OpenIM.iMManager.conversationManager.getOneConversation( + asyncFunction: () => + OpenIM.iMManager.conversationManager.getOneConversation( sourceID: sourceID, sessionType: sessionType, )); @@ -438,9 +497,11 @@ class ConversationLogic extends GetxController { addFriend() => AppNavigator.startAddColleague(); - createGroup() => AppNavigator.startCreateGroup(defaultCheckedList: [OpenIM.iMManager.userInfo]); + createGroup() => AppNavigator.startCreateGroup( + defaultCheckedList: [OpenIM.iMManager.userInfo]); - addGroup() => AppNavigator.startAddContactsBySearch(searchType: SearchType.group); + addGroup() => + AppNavigator.startAddContactsBySearch(searchType: SearchType.group); void globalSearch() => AppNavigator.startGlobalSearch(); } diff --git a/mobile-next/lib/pages/conversation/conversation_view.dart b/mobile-next/lib/pages/conversation/conversation_view.dart index a1d3100..7fb4fd6 100644 --- a/mobile-next/lib/pages/conversation/conversation_view.dart +++ b/mobile-next/lib/pages/conversation/conversation_view.dart @@ -46,7 +46,8 @@ class ConversationPage extends StatelessWidget { body: Column( children: [ Padding( - padding: const EdgeInsets.fromLTRB(AppGap.x4, 0, AppGap.x4, AppGap.x2), + padding: const EdgeInsets.fromLTRB( + AppGap.x4, 0, AppGap.x4, AppGap.x2), child: Container( height: 36, decoration: BoxDecoration( @@ -57,47 +58,64 @@ class ConversationPage extends StatelessWidget { alignment: Alignment.centerLeft, child: const Row( children: [ - Icon(Icons.search, size: AppIconSize.sm, color: AppColors.textSecondary), + Icon(Icons.search, + size: AppIconSize.sm, color: AppColors.textSecondary), SizedBox(width: AppGap.x2), - Text('搜索', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), + Text('搜索', + style: TextStyle( + fontSize: AppFont.sub, + color: AppColors.textSecondary)), ], ), ), ), - Expanded( - child: logic.list.isEmpty - ? const CompanyEmptyView( - icon: Icons.chat_bubble_outline, - title: '暂时没有会话', - subtitle: '去通讯录找个同事聊聊吧', - ) - : SlidableAutoCloseBehavior( - child: ScrollablePositionedList.builder( - itemScrollController: logic.itemScrollController, - itemBuilder: (_, index) => _buildConversationItemView( - logic.list.elementAt(index), - ), - itemCount: logic.list.length, - itemPositionsListener: logic.itemPositionsListener, - ), - ), - ), + Expanded(child: _conversationBody()), ], ), )); } + Widget _conversationBody() { + if (logic.showLoading) { + return const CompanyLoadingView(); + } + if (logic.showFail) { + return CompanyFailView(onRetry: logic.reloadConversations); + } + if (logic.list.isEmpty) { + return const CompanyEmptyView( + icon: Icons.chat_bubble_outline, + title: '暂时没有会话', + subtitle: '去通讯录找个同事聊聊吧', + ); + } + return SlidableAutoCloseBehavior( + child: ScrollablePositionedList.builder( + itemScrollController: logic.itemScrollController, + itemBuilder: (_, index) => _buildConversationItemView( + logic.list.elementAt(index), + ), + itemCount: logic.list.length, + itemPositionsListener: logic.itemPositionsListener, + ), + ); + } + Widget _buildConversationItemView(ConversationInfo info) => Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), - extentRatio: logic.existUnreadMsg(info) ? 0.7 : (logic.isPinned(info) ? 0.5 : 0.4), + extentRatio: logic.existUnreadMsg(info) + ? 0.7 + : (logic.isPinned(info) ? 0.5 : 0.4), children: [ CustomSlidableAction( onPressed: (_) => logic.pinConversation(info), flex: logic.isPinned(info) ? 3 : 2, backgroundColor: Styles.c_0089FF, padding: const EdgeInsets.all(1), - child: (logic.isPinned(info) ? StrRes.cancelTop : StrRes.top).toText..style = Styles.ts_FFFFFF_14sp, + child: + (logic.isPinned(info) ? StrRes.cancelTop : StrRes.top).toText + ..style = Styles.ts_FFFFFF_14sp, ), if (logic.existUnreadMsg(info)) CustomSlidableAction( @@ -163,7 +181,8 @@ class ConversationPage extends StatelessWidget { ), ), const Spacer(), - logic.getTime(info).toText..style = Styles.ts_8E9AB0_12sp, + logic.getTime(info).toText + ..style = Styles.ts_8E9AB0_12sp, ], ), 3.verticalSpace, @@ -175,9 +194,12 @@ class ConversationPage extends StatelessWidget { prefixSpan: TextSpan( text: '', children: [ - if (logic.isNotDisturb(info) && logic.getUnreadCount(info) > 0) + if (logic.isNotDisturb(info) && + logic.getUnreadCount(info) > 0) TextSpan( - text: '[${sprintf(StrRes.nPieces, [logic.getUnreadCount(info)])}] ', + text: '[${sprintf(StrRes.nPieces, [ + logic.getUnreadCount(info) + ])}] ', style: Styles.ts_8E9AB0_14sp, ), TextSpan( @@ -195,7 +217,8 @@ class ConversationPage extends StatelessWidget { ..width = 13.63.w ..height = 14.07.h else - UnreadCountView(count: logic.getUnreadCount(info)), + UnreadCountView( + count: logic.getUnreadCount(info)), ], ), ], diff --git a/mobile-next/lib/pages/home/home_logic.dart b/mobile-next/lib/pages/home/home_logic.dart index 51db304..ab1cb26 100644 --- a/mobile-next/lib/pages/home/home_logic.dart +++ b/mobile-next/lib/pages/home/home_logic.dart @@ -24,6 +24,7 @@ class HomeLogic extends SuperController { final unhandledFriendApplicationCount = 0.obs; final unhandledGroupApplicationCount = 0.obs; final unhandledCount = 0.obs; + final isOffline = false.obs; String? _lockScreenPwd; bool _isShowScreenLock = false; bool? _isAutoLogin; @@ -50,7 +51,8 @@ class HomeLogic extends SuperController { void getUnhandledFriendApplicationCount() async { var i = 0; - var list = await OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient(); + var list = await OpenIM.iMManager.friendshipManager + .getFriendApplicationListAsRecipient(); var haveReadList = DataSp.getHaveReadUnHandleFriendApplication(); haveReadList ??= []; for (var info in list) { @@ -65,7 +67,8 @@ class HomeLogic extends SuperController { void getUnhandledGroupApplicationCount() async { var i = 0; - var list = await OpenIM.iMManager.groupManager.getGroupApplicationListAsRecipient(); + var list = await OpenIM.iMManager.groupManager + .getGroupApplicationListAsRecipient(); var haveReadList = DataSp.getHaveReadUnHandleGroupApplication(); haveReadList ??= []; for (var info in list) { @@ -101,7 +104,12 @@ class HomeLogic extends SuperController { if (value.status == IMSdkStatus.syncStart) { _getRTCInvitationStart(); } + isOffline.value = value.status == IMSdkStatus.connectionFailed; }); + final lastStatus = imLogic.imSdkStatusSubject.values.lastOrNull; + if (lastStatus != null) { + isOffline.value = lastStatus.status == IMSdkStatus.connectionFailed; + } Apis.kickoffController.stream.listen((event) { DataSp.removeLoginCertificate(); diff --git a/mobile-next/lib/pages/home/home_view.dart b/mobile-next/lib/pages/home/home_view.dart index 8bfac49..a431e18 100644 --- a/mobile-next/lib/pages/home/home_view.dart +++ b/mobile-next/lib/pages/home/home_view.dart @@ -9,6 +9,7 @@ import '../mine/mine_view.dart'; import 'home_logic.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'; import '../../company/feature_flags.dart'; +import '../../company/ui/state_views.dart'; class HomePage extends StatelessWidget { final logic = Get.find(); @@ -22,9 +23,11 @@ class HomePage extends StatelessWidget { onDoubleTap: () { logic.scrollToUnreadMessage(); }, - child: _setupIcon(ImageRes.homeTab1Sel.toImage, logic.unreadMsgCount.value), + child: _setupIcon( + ImageRes.homeTab1Sel.toImage, logic.unreadMsgCount.value), ), - inactiveIcon: _setupIcon(ImageRes.homeTab1Nor.toImage, logic.unreadMsgCount.value), + inactiveIcon: _setupIcon( + ImageRes.homeTab1Nor.toImage, logic.unreadMsgCount.value), title: StrRes.home, textStyle: Styles.ts_0089FF_10sp_semibold, ), @@ -32,8 +35,10 @@ class HomePage extends StatelessWidget { PersistentTabConfig( screen: ContactsPage(), item: ItemConfig( - icon: _setupIcon(ImageRes.homeTab2Sel.toImage, logic.unhandledCount.value), - inactiveIcon: _setupIcon(ImageRes.homeTab2Nor.toImage, logic.unhandledCount.value), + icon: _setupIcon( + ImageRes.homeTab2Sel.toImage, logic.unhandledCount.value), + inactiveIcon: _setupIcon( + ImageRes.homeTab2Nor.toImage, logic.unhandledCount.value), title: StrRes.contacts, textStyle: Styles.ts_0089FF_10sp_semibold, ), @@ -81,19 +86,30 @@ class HomePage extends StatelessWidget { return Scaffold( backgroundColor: Styles.c_FFFFFF, body: Obx( - () => PersistentTabView( - tabs: _tabs(), - navBarBuilder: (navBarConfig) => Style1BottomNavBar( - navBarConfig: navBarConfig, - navBarDecoration: const NavBarDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow(color: Colors.black12, blurRadius: 0.5, spreadRadius: 0.5), - ], + () => Column( + children: [ + if (logic.isOffline.value) const CompanyOfflineBanner(), + Expanded( + child: PersistentTabView( + tabs: _tabs(), + navBarBuilder: (navBarConfig) => Style1BottomNavBar( + navBarConfig: navBarConfig, + navBarDecoration: const NavBarDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 0.5, + spreadRadius: 0.5), + ], + ), + ), + navBarOverlap: const NavBarOverlap.none(), + screenTransitionAnimation: + const ScreenTransitionAnimation.none(), + ), ), - ), - navBarOverlap: const NavBarOverlap.none(), - screenTransitionAnimation: const ScreenTransitionAnimation.none(), + ], ), ), );