fix(mobile): 新的同事页合并发出/收到申请并按方向渲染(视觉审查 🔴1/🔴2)

- 合并 getFriendApplicationListAsApplicant(我发出的)+ AsRecipient(我收到的),
  按 fromUserID 是否等于自己判断方向
- 我发出的待处理 → 灰字「等待对方通过」且无操作按钮;我收到的待处理才渲染
  「接受/拒绝」;已同意→已添加、已拒绝→已拒绝(视觉规范 5.1)
- 接受/拒绝按钮 shape 复用 theme.dart 新增的统一 8px Token(AppRadius.button),
  不再用 Material3 默认胶囊圆角,页面内零魔法数值
- 窄屏(<360)按钮组/状态字换第二行右对齐不挤压姓名;非窄屏状态区固定 132px

双机实测:A(t2001)发→A 侧 AsApplicant 显示 handleResult=0(等待对方通过、无按钮);
B(t2002)收→AsRecipient 显示接受/拒绝按钮,点接受后双方 handleResult=1(已添加)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-16 03:03:11 +08:00
co-authored by multica-agent
parent c5516b8eb8
commit b50ee2464c
2 changed files with 73 additions and 12 deletions
+59 -12
View File
@@ -1,10 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import '../services/im_service.dart';
import '../theme.dart';
import '../widgets/avatar.dart';
/// 「新的同事」:收到的好友申请列表,可接受 / 拒绝
/// 「新的同事」:收到的 + 我发出的好友申请列表。
/// 按申请方向渲染(视觉规范 v1 §5.1):
/// - 我发出的待处理 → 灰字「等待对方通过」,无操作按钮;
/// - 我收到的待处理 → 「接受 / 拒绝」按钮组;
/// - 已同意 → 「已添加」;已拒绝 → 「已拒绝」;失败 → toast 且行保持可重试。
class NewFriendsScreen extends StatefulWidget {
const NewFriendsScreen({super.key});
@@ -25,10 +30,23 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
Future<void> _load() async {
try {
final list = await OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient();
// 合并两类申请:我发出的 + 我收到的,按申请时间倒序,待处理在前
final results = await Future.wait([
OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient(),
OpenIM.iMManager.friendshipManager.getFriendApplicationListAsApplicant(),
]);
final merged = <FriendApplicationInfo>[
...(results[0] ?? <FriendApplicationInfo>[]),
...(results[1] ?? <FriendApplicationInfo>[]),
];
merged.sort((a, b) {
if ((a.handleResult ?? 0) == 0 && (b.handleResult ?? 0) != 0) return -1;
if ((b.handleResult ?? 0) == 0 && (a.handleResult ?? 0) != 0) return 1;
return (b.createTime ?? 0).compareTo(a.createTime ?? 0);
});
if (!mounted) return;
setState(() {
_list = list;
_list = merged;
_loading = false;
_failed = false;
});
@@ -41,6 +59,7 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
}
}
/// 处理收到的申请:接受 / 拒绝。成功才改状态,失败 toast 保持待处理可重试。
Future<void> _handle(FriendApplicationInfo a, bool accept) async {
try {
if (accept) {
@@ -79,7 +98,13 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
children: [
const Text('申请列表没加载出来', style: TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary)),
const SizedBox(height: AppGap.x4),
OutlinedButton(onPressed: _load, child: const Text('重试')),
OutlinedButton(
onPressed: _load,
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.button)),
),
child: const Text('重试'),
),
],
),
);
@@ -96,10 +121,21 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
);
}
/// 我发出的申请(fromUserID 是我自己);否则是我收到的。
bool _isOutgoing(FriendApplicationInfo a) {
final myID = IMService.instance.currentUserID;
return myID != null && a.fromUserID == myID;
}
Widget _tile(FriendApplicationInfo a) {
final name = a.fromNickname?.isNotEmpty == true ? a.fromNickname! : (a.fromUserID ?? '');
final isPending = a.handleResult == 0;
final showButtons = isPending;
final outgoing = _isOutgoing(a);
// 我发出的显示对方(to),我收到的显示申请人(from)
final name = outgoing
? (a.toNickname?.isNotEmpty == true ? a.toNickname! : (a.toUserID ?? ''))
: (a.fromNickname?.isNotEmpty == true ? a.fromNickname! : (a.fromUserID ?? ''));
final isPending = (a.handleResult ?? 0) == 0;
// 只有「我收到的待处理」才显示操作按钮;我发出的待处理只显示状态字
final showButtons = isPending && !outgoing;
// 窄屏(<360)时按钮组换到第二行右对齐,避免挤压姓名区
final narrow = MediaQuery.sizeOf(context).width < 360;
@@ -113,6 +149,7 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
backgroundColor: AppColors.primary,
minimumSize: const Size(60, 32),
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.button)),
),
child: const Text('接受', style: TextStyle(fontSize: AppFont.sub)),
),
@@ -122,6 +159,7 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
style: OutlinedButton.styleFrom(
minimumSize: const Size(60, 32),
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.button)),
),
child: const Text('拒绝', style: TextStyle(fontSize: AppFont.sub)),
),
@@ -129,11 +167,12 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
);
Widget statusText;
if (a.handleResult == 1) {
if ((a.handleResult ?? 0) == 1) {
statusText = const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
} else if (a.handleResult == -1) {
} else if ((a.handleResult ?? 0) == -1) {
statusText = const Text('已拒绝', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
} else {
// 待处理:我发出的显示「等待对方通过」,我收到的走按钮组(showButtons
statusText = const Text('等待对方通过', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
}
@@ -164,15 +203,23 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary),
),
],
if (narrow && showButtons) ...[
// 窄屏:按钮组 / 状态字换到第二行右对齐,不挤压姓名区
if (narrow) ...[
const SizedBox(height: AppGap.x2),
Align(alignment: Alignment.centerRight, child: buttonGroup),
Align(
alignment: Alignment.centerRight,
child: showButtons ? buttonGroup : statusText,
),
],
],
),
),
// 非窄屏:状态区固定 132px 不压缩
if (!narrow)
SizedBox(width: 132, child: Align(alignment: Alignment.centerRight, child: showButtons ? buttonGroup : statusText)),
SizedBox(
width: 132,
child: Align(alignment: Alignment.centerRight, child: showButtons ? buttonGroup : statusText),
),
],
),
);
+14
View File
@@ -75,6 +75,20 @@ class AppGap {
static const double x12 = 48;
}
/// 圆角常量(视觉规范 v1 §2:按钮/输入框/列表项 = 8,弹窗 = 12,气泡 = 8)
class AppRadius {
AppRadius._();
/// 按钮 / 输入框 / 列表项
static const double button = 8;
/// 弹窗
static const double modal = 12;
/// 气泡
static const double bubble = 8;
}
/// 统一的主题
ThemeData buildAppTheme() {
return ThemeData(