新增 mobile/:畅联手机端 Flutter 工程(B-58)
- 登录:工号+密码走 account-service /api/login,自动登录、被踢下线回登录页 - 消息:会话列表(未读角标/免打扰/时间)、单聊群聊、文字/语音/图片/文件消息、失败重发、历史分页 - 通讯录:新的同事/我的群聊入口、按部门分组(好友 ex 字段)、搜索 - 通话:一对一语音通话(信令走 OpenIM 自定义消息 + LiveKit,token 走 /api/rtc_token) - 界面按确认效果图 m1/m2/m3 实现,主色 #3B87F5,四态视图齐全,无英文残留
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
import '../widgets/avatar.dart';
|
||||
|
||||
/// 「新的同事」:收到的好友申请列表,可接受 / 拒绝
|
||||
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 list = await OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_list = list;
|
||||
_loading = false;
|
||||
_failed = false;
|
||||
});
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_loading = false;
|
||||
_failed = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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, 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]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tile(FriendApplicationInfo a) {
|
||||
final name = a.fromNickname?.isNotEmpty == true ? a.fromNickname! : (a.fromUserID ?? '');
|
||||
Widget trailing;
|
||||
if (a.handleResult == 1) {
|
||||
trailing = const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
|
||||
} else if (a.handleResult == -1) {
|
||||
trailing = const Text('已拒绝', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
|
||||
} else {
|
||||
trailing = Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FilledButton(
|
||||
onPressed: () => _handle(a, true),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: AppColors.primary,
|
||||
minimumSize: const Size(0, 32),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
|
||||
),
|
||||
child: const Text('接受', style: TextStyle(fontSize: AppFont.sub)),
|
||||
),
|
||||
const SizedBox(width: AppGap.x2),
|
||||
OutlinedButton(
|
||||
onPressed: () => _handle(a, false),
|
||||
style: OutlinedButton.styleFrom(
|
||||
minimumSize: const Size(0, 32),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3),
|
||||
),
|
||||
child: const Text('拒绝', style: TextStyle(fontSize: AppFont.sub)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return ListTile(
|
||||
leading: NameAvatar(name: name),
|
||||
title: Text(name, style: const TextStyle(fontSize: AppFont.body)),
|
||||
subtitle: a.reqMsg?.isNotEmpty == true
|
||||
? Text(a.reqMsg!, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary))
|
||||
: null,
|
||||
trailing: trailing,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user