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:
co-authored by
Cursor
multica-agent
parent
092f513708
commit
2b2c2d28a4
@@ -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)),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user