feat(mobile,account-service): 统一发送服务 + 员工目录接口 + 好友申请五态/验证消息(B-52 施工 2/3)
- message_send_service:所有消息走统一门禁(登录/连接/空接收方/好友校验), 失败打印 SDK 原始 code 并映射中文提示,不再只吞异常 - chat_screen 接入统一发送服务,失败提示真实原因 - account-service 新增 GET /api/directory(登录用户可读启用员工,用于添加同事) - add_friend_screen 增加可编辑验证消息(视觉规范 4.2) - new_friends_screen 五态 + 窄屏(<360)按钮组换行不挤压姓名(视觉规范 5.3) Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -3,7 +3,7 @@ import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
|
||||
|
||||
import '../theme.dart';
|
||||
|
||||
/// 添加同事:输入对方工号(userID)发送好友申请
|
||||
/// 添加同事:输入对方工号(userID)+ 可编辑验证消息,发送好友申请
|
||||
class AddFriendScreen extends StatefulWidget {
|
||||
const AddFriendScreen({super.key});
|
||||
|
||||
@@ -13,11 +13,13 @@ class AddFriendScreen extends StatefulWidget {
|
||||
|
||||
class _AddFriendScreenState extends State<AddFriendScreen> {
|
||||
final TextEditingController _idCtrl = TextEditingController();
|
||||
final TextEditingController _msgCtrl = TextEditingController(text: '你好,我想加你为同事');
|
||||
bool _sending = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_idCtrl.dispose();
|
||||
_msgCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ class _AddFriendScreenState extends State<AddFriendScreen> {
|
||||
}
|
||||
setState(() => _sending = true);
|
||||
try {
|
||||
await OpenIM.iMManager.friendshipManager.addFriend(userID: userID, reason: '你好,我想加你为同事');
|
||||
await OpenIM.iMManager.friendshipManager.addFriend(userID: userID, reason: _msgCtrl.text.trim());
|
||||
if (!mounted) return;
|
||||
_toast('申请已发送,等对方通过');
|
||||
Navigator.of(context).pop();
|
||||
@@ -66,6 +68,22 @@ class _AddFriendScreenState extends State<AddFriendScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppGap.x4),
|
||||
TextField(
|
||||
controller: _msgCtrl,
|
||||
maxLines: 3,
|
||||
minLines: 2,
|
||||
decoration: InputDecoration(
|
||||
hintText: '验证消息(可不填)',
|
||||
hintStyle: const TextStyle(color: AppColors.textSecondary),
|
||||
filled: true,
|
||||
fillColor: AppColors.searchBg,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppGap.x6),
|
||||
SizedBox(
|
||||
height: 48,
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import '../services/call_service.dart';
|
||||
import '../services/im_service.dart';
|
||||
import '../services/message_send_service.dart';
|
||||
import '../theme.dart';
|
||||
import '../utils/format.dart';
|
||||
import '../widgets/chat_input_bar.dart';
|
||||
@@ -135,6 +136,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
|
||||
// ---------------- 发送 ----------------
|
||||
|
||||
/// 统一发送入口:文字/表情/语音/图片/文件都经过这里。
|
||||
/// 失败时用 [MessageSendService] 的中文提示,不再只标红感叹号。
|
||||
Future<void> _sendMessage(Message message) async {
|
||||
setState(() {
|
||||
message.status = MessageStatus.sending;
|
||||
@@ -142,17 +145,17 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
});
|
||||
if (_scroll.hasClients) _scroll.jumpTo(0);
|
||||
try {
|
||||
final sent = await OpenIM.iMManager.messageManager.sendMessage(
|
||||
final sent = await MessageSendService.instance.send(
|
||||
message: message,
|
||||
userID: _isGroup ? null : _peerID,
|
||||
groupID: _isGroup ? _groupID : null,
|
||||
offlinePushInfo: OfflinePushInfo(),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => message.status = sent.status ?? MessageStatus.succeeded);
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => message.status = MessageStatus.failed);
|
||||
_toast(e is Exception ? e.toString() : '消息没发出去,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,17 +163,17 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
Future<void> _resend(Message message) async {
|
||||
setState(() => message.status = MessageStatus.sending);
|
||||
try {
|
||||
final sent = await OpenIM.iMManager.messageManager.sendMessage(
|
||||
final sent = await MessageSendService.instance.send(
|
||||
message: message,
|
||||
userID: _isGroup ? null : _peerID,
|
||||
groupID: _isGroup ? _groupID : null,
|
||||
offlinePushInfo: OfflinePushInfo(),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => message.status = sent.status ?? MessageStatus.succeeded);
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => message.status = MessageStatus.failed);
|
||||
_toast(e is Exception ? e.toString() : '消息没发出去,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,43 +98,83 @@ class _NewFriendsScreenState extends State<NewFriendsScreen> {
|
||||
|
||||
Widget _tile(FriendApplicationInfo a) {
|
||||
final name = a.fromNickname?.isNotEmpty == true ? a.fromNickname! : (a.fromUserID ?? '');
|
||||
Widget trailing;
|
||||
final isPending = a.handleResult == 0;
|
||||
final showButtons = isPending;
|
||||
// 窄屏(<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),
|
||||
),
|
||||
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),
|
||||
),
|
||||
child: const Text('拒绝', style: TextStyle(fontSize: AppFont.sub)),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget statusText;
|
||||
if (a.handleResult == 1) {
|
||||
trailing = const Text('已添加', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
|
||||
statusText = 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));
|
||||
statusText = 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)),
|
||||
),
|
||||
],
|
||||
);
|
||||
statusText = const Text('等待对方通过', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary));
|
||||
}
|
||||
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,
|
||||
|
||||
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 && showButtons) ...[
|
||||
const SizedBox(height: AppGap.x2),
|
||||
Align(alignment: Alignment.centerRight, child: buttonGroup),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (!narrow)
|
||||
SizedBox(width: 132, child: Align(alignment: Alignment.centerRight, child: showButtons ? buttonGroup : statusText)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user