fix(mobile-next): 把加载、失败、断网接到真实 SDK 与目录请求

会话列表、通讯录、好友申请、添加同事页覆盖 CompanyLoadingView/CompanyFailView;
主页断网横幅跟随 IMSdkStatus.connectionFailed。不伪造状态。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-20 02:52:24 +08:00
co-authored by Cursor multica-agent
parent 092f513708
commit 2b2c2d28a4
9 changed files with 432 additions and 186 deletions
@@ -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<void> 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<void> 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)),
),
],
);
@@ -14,10 +14,14 @@ class ContactsLogic extends GetxController
final homeLogic = Get.find<HomeLogic>();
final friendApplicationList = <UserInfo>[];
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<void> 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);
}
@@ -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),
],
@@ -11,6 +11,8 @@ class FriendRequestsLogic extends GetxController {
final imLogic = Get.find<IMController>();
final homeLogic = Get.find<HomeLogic>();
final applicationList = <FriendApplicationInfo>[].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 = <FriendApplicationInfo>[];
allList
..addAll(list[0])
..addAll(list[1]);
final allList = <FriendApplicationInfo>[];
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 ??= <String>[];
for (var e in list[0]) {
var id = IMUtils.buildFriendApplicationID(e);
if (!haveReadList.contains(id)) {
haveReadList.add(id);
var haveReadList = DataSp.getHaveReadUnHandleFriendApplication();
haveReadList ??= <String>[];
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();
@@ -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)),
],
),
);
@@ -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 = <ConversationInfo>[];
@@ -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<ConversationInfo> 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<List<ConversationInfo>> 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<void> 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 = <ConversationInfo>[];
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();
}
@@ -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)),
],
),
],
+10 -2
View File
@@ -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 ??= <String>[];
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 ??= <String>[];
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();
+32 -16
View File
@@ -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<HomeLogic>();
@@ -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(),
],
),
),
);