import 'package:flutter/material.dart'; import '../../api/oa_client.dart'; import '../../im/chat_prefs.dart'; import '../../session/session.dart'; import '../desk_theme.dart'; import '../widgets/desk_widgets.dart'; import 'desk_pick_people_page.dart'; class DeskChatDetailPage extends StatefulWidget { const DeskChatDetailPage({ super.key, required this.session, required this.api, required this.conversationId, required this.peerId, required this.peerName, required this.isGroup, this.peerAvatarFileId, this.onCall, }); final SessionStore session; final OaClient api; final String conversationId; final String peerId; final String peerName; final bool isGroup; final String? peerAvatarFileId; final void Function(String kind)? onCall; @override State createState() => _DeskChatDetailPageState(); } class _DeskChatDetailPageState extends State { List> _members = []; bool _mute = false; bool _pin = false; @override void initState() { super.initState(); _mute = ChatPrefs.muted(widget.conversationId); _pin = ChatPrefs.pinned(widget.conversationId); _loadMembers(); } Future _loadMembers() async { if (widget.isGroup && widget.conversationId.isNotEmpty) { try { final data = await widget.api.get('/im/groups/${widget.conversationId}/members'); if (!mounted) return; setState(() => _members = asMaps(data)); return; } catch (_) {} } setState(() { _members = [ {'id': widget.peerId, 'name': widget.peerName, 'avatarFileId': widget.peerAvatarFileId}, {'id': widget.session.userId, 'name': widget.session.displayName, 'avatarFileId': widget.session.user['avatarFileId']}, ]; }); } Future _addMembers() async { final exclude = {widget.session.userId, ..._members.map((e) => '${e['id'] ?? e['userId'] ?? ''}')}; final picked = await Navigator.of(context).push>>( MaterialPageRoute(builder: (_) => DeskPickPeoplePage(api: widget.api, title: '选择同事', exclude: exclude)), ); if (picked == null || picked.isEmpty) return; try { if (widget.isGroup && widget.conversationId.isNotEmpty) { await widget.api.post('/im/groups/${widget.conversationId}/members', { 'memberIds': picked.map((e) => '${e['id']}').toList(), }); await _loadMembers(); } else { await widget.api.post('/im/groups', { 'name': '${widget.peerName}的群聊', 'memberIds': [widget.peerId, ...picked.map((e) => '${e['id']}')], }); if (mounted) { deskToast(context, '已创建群聊'); Navigator.pop(context); } } } catch (e) { if (mounted) deskToast(context, '$e', error: true); } } Future _clear() async { final ok = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('删除聊天记录'), content: const Text('将从本机清空当前会话的聊天记录,对方不受影响。'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('取消')), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('删除')), ], ), ); if (ok != true) return; await ChatPrefs.clearHistory(widget.conversationId); if (mounted) { deskToast(context, '已删除本机聊天记录'); Navigator.pop(context, 'cleared'); } } @override Widget build(BuildContext context) { return ColoredBox( color: kDeskBg, child: Column( children: [ DeskPaneHeader( title: '聊天详情', leading: IconButton(icon: const Icon(Icons.arrow_back, size: 20), onPressed: () => Navigator.pop(context)), ), Expanded( child: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 480), child: ListView( padding: const EdgeInsets.all(20), children: [ Material( color: Colors.white, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(16), child: Wrap( spacing: 16, runSpacing: 12, children: [ for (final m in _members) SizedBox( width: 56, child: Column( children: [ DeskAvatar( label: '${m['name'] ?? m['displayName'] ?? ''}', fileId: '${m['avatarFileId'] ?? ''}', api: widget.api, size: 44, ), const SizedBox(height: 4), Text('${m['name'] ?? m['displayName'] ?? ''}', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11)), ], ), ), InkWell( onTap: _addMembers, child: Container( width: 44, height: 44, decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), border: Border.all(color: kDeskLine)), child: const Icon(Icons.add, color: kDeskMute), ), ), ], ), ), ), const SizedBox(height: 12), _section([ _switchRow('消息免打扰', _mute, (v) async { await ChatPrefs.setMuted(widget.conversationId, v); setState(() => _mute = v); }), _switchRow('置顶聊天', _pin, (v) async { await ChatPrefs.setPinned(widget.conversationId, v); setState(() => _pin = v); }), ]), const SizedBox(height: 12), _section([ _actionRow('语音通话', () => widget.onCall?.call('voice')), _actionRow('视频通话', () => widget.onCall?.call(widget.isGroup ? 'meeting' : 'video')), _actionRow('删除聊天记录', _clear, danger: true), ]), ], ), ), ), ), ], ), ); } Widget _section(List children) { return Material( color: Colors.white, borderRadius: BorderRadius.circular(8), clipBehavior: Clip.antiAlias, child: Column(children: children), ); } Widget _switchRow(String title, bool value, ValueChanged onChanged) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( children: [ Expanded(child: Text(title, style: const TextStyle(fontSize: 14))), Switch(value: value, activeTrackColor: kDeskGreen, onChanged: onChanged), ], ), ); } Widget _actionRow(String title, VoidCallback onTap, {bool danger = false}) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ Expanded(child: Text(title, style: TextStyle(fontSize: 14, color: danger ? const Color(0xFFFA5151) : kDeskInk))), if (!danger) const Icon(Icons.chevron_right, size: 18, color: kDeskMute), ], ), ), ), ); } }