228 lines
8.4 KiB
Dart
228 lines
8.4 KiB
Dart
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<NewFriendsScreen> createState() => _NewFriendsScreenState();
|
||
}
|
||
|
||
class _NewFriendsScreenState extends State<NewFriendsScreen> {
|
||
List<FriendApplicationInfo> _list = [];
|
||
bool _loading = true;
|
||
bool _failed = false;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_load();
|
||
}
|
||
|
||
Future<void> _load() async {
|
||
try {
|
||
// 合并两类申请:我发出的 + 我收到的,按申请时间倒序,待处理在前
|
||
final results = await Future.wait([
|
||
OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient(),
|
||
OpenIM.iMManager.friendshipManager.getFriendApplicationListAsApplicant(),
|
||
]);
|
||
final merged = <FriendApplicationInfo>[
|
||
...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<void> _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),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|