Files
tongxunruanjian/mobile/lib/screens/new_friends_screen.dart
T

228 lines
8.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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),
),
],
),
);
}
}