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}); @override State createState() => _NewFriendsScreenState(); } class _NewFriendsScreenState extends State { List _list = []; bool _loading = true; bool _failed = false; @override void initState() { super.initState(); _load(); } Future _load() async { try { // 合并两类申请:我发出的 + 我收到的,按申请时间倒序,待处理在前 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 = merged; _loading = false; _failed = false; }); } catch (_) { if (!mounted) return; setState(() { _loading = false; _failed = true; }); } } /// 处理收到的申请:接受 / 拒绝。成功才改状态,失败 toast 保持待处理可重试。 Future _handle(FriendApplicationInfo a, bool accept) async { try { if (accept) { await OpenIM.iMManager.friendshipManager.acceptFriendApplication(userID: a.fromUserID ?? '', handleMsg: ''); } else { await OpenIM.iMManager.friendshipManager.refuseFriendApplication(userID: a.fromUserID ?? '', handleMsg: ''); } if (!mounted) return; setState(() => a.handleResult = accept ? 1 : -1); _toast(accept ? '已添加为同事' : '已拒绝'); } catch (_) { _toast('操作失败,请检查网络后重试'); } } void _toast(String text) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(text), duration: const Duration(seconds: 2))); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('新的同事')), body: _buildBody(), ); } Widget _buildBody() { if (_loading) { return const Center(child: CircularProgressIndicator(color: AppColors.primary)); } if (_failed) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('申请列表没加载出来', style: TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary)), const SizedBox(height: AppGap.x4), OutlinedButton( onPressed: _load, style: OutlinedButton.styleFrom( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(AppRadius.button)), ), child: const Text('重试'), ), ], ), ); } if (_list.isEmpty) { return const Center( child: Text('暂时没有新的申请', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), ); } return ListView.separated( itemCount: _list.length, separatorBuilder: (_, __) => const Divider(indent: 76), itemBuilder: (_, i) => _tile(_list[i]), ); } /// 我发出的申请(fromUserID 是我自己);否则是我收到的。 bool _isOutgoing(FriendApplicationInfo a) { final myID = IMService.instance.currentUserID; return myID != null && a.fromUserID == myID; } Widget _tile(FriendApplicationInfo a) { 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; // 「接受/拒绝」按钮组:总宽固定 132,任何情况下不压缩、不换行 final buttonGroup = Row( mainAxisSize: MainAxisSize.min, children: [ FilledButton( onPressed: () => _handle(a, true), style: FilledButton.styleFrom( 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)), ), const SizedBox(width: AppGap.x2), OutlinedButton( onPressed: () => _handle(a, false), 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)), ), ], ); Widget statusText; if ((a.handleResult ?? 0) == 1) { statusText = const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)); } 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)); } return Padding( padding: const EdgeInsets.symmetric(horizontal: AppGap.x4, vertical: AppGap.x3), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ NameAvatar(name: name), const SizedBox(width: AppGap.x3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( name, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary), ), if (a.reqMsg?.isNotEmpty == true) ...[ const SizedBox(height: AppGap.x1), Text( a.reqMsg!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary), ), ], // 窄屏:按钮组 / 状态字换到第二行右对齐,不挤压姓名区 if (narrow) ...[ const SizedBox(height: AppGap.x2), Align( alignment: Alignment.centerRight, child: showButtons ? buttonGroup : statusText, ), ], ], ), ), // 非窄屏:状态区固定 132px 不压缩 if (!narrow) SizedBox( width: 132, child: Align(alignment: Alignment.centerRight, child: showButtons ? buttonGroup : statusText), ), ], ), ); } }