diff --git a/mobile/lib/screens/new_friends_screen.dart b/mobile/lib/screens/new_friends_screen.dart index a1bcd69..70380f5 100644 --- a/mobile/lib/screens/new_friends_screen.dart +++ b/mobile/lib/screens/new_friends_screen.dart @@ -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 { Future _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 = [ + ...(results[0] ?? []), + ...(results[1] ?? []), + ]; + 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 { } } + /// 处理收到的申请:接受 / 拒绝。成功才改状态,失败 toast 保持待处理可重试。 Future _handle(FriendApplicationInfo a, bool accept) async { try { if (accept) { @@ -79,7 +98,13 @@ class _NewFriendsScreenState extends State { 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 { ); } + /// 我发出的申请(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 { 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 { 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 { ); 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 { 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), + ), ], ), ); diff --git a/mobile/lib/theme.dart b/mobile/lib/theme.dart index fbb5bed..3e576a7 100644 --- a/mobile/lib/theme.dart +++ b/mobile/lib/theme.dart @@ -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(