import 'package:flutter/material.dart'; import '../api/oa_client.dart'; import '../im/chat_prefs.dart'; import '../session/session.dart'; import '../theme.dart'; import '../widgets/wecom.dart'; import 'pick_people_page.dart'; class ChatDetailPage extends StatefulWidget { const ChatDetailPage({ super.key, required this.session, required this.api, required this.conversationId, required this.peerId, required this.peerName, required this.isGroup, this.peerAvatarFileId, this.messages = const [], this.onCall, }); final SessionStore session; final OaClient api; final String conversationId; final String peerId; final String peerName; final bool isGroup; final String? peerAvatarFileId; final List> messages; final void Function(String kind)? onCall; @override State createState() => _ChatDetailPageState(); } class _ChatDetailPageState 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: (_) => PickPeoplePage(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) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已创建群聊'))); Navigator.pop(context); } } } catch (e) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e'))); } } Future _search() async { await Navigator.of(context).push(MaterialPageRoute( builder: (_) => _ChatSearchPage(messages: widget.messages, peerName: widget.peerName), )); } Future _pickBg() async { const colors = [ 0xFFF5F5F5, 0xFFE7F0E4, 0xFFE8EEF6, 0xFFF6EFE6, 0xFFF3E8EE, ]; final picked = await showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: Wrap( spacing: 12, runSpacing: 12, children: [ for (final c in colors) GestureDetector( onTap: () => Navigator.pop(ctx, c), child: Container( width: 56, height: 56, decoration: BoxDecoration( color: Color(c), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFDDDDDD)), ), ), ), ], ), ), ), ); if (picked == null) return; await ChatPrefs.setBg(widget.conversationId, picked); if (mounted) setState(() {}); } 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) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已删除本机聊天记录'))); Navigator.pop(context, 'cleared'); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: kPaper, appBar: AppBar(title: const Text('聊天详情')), body: ListView( children: [ const SizedBox(height: 12), Container( color: Colors.white, padding: const EdgeInsets.fromLTRB(16, 16, 16, 8), child: Wrap( spacing: 16, runSpacing: 12, children: [ for (final m in _members) SizedBox( width: 56, child: Column( children: [ SquareAvatar( label: '${m['name'] ?? m['displayName'] ?? ''}', size: 48, fileId: '${m['avatarFileId'] ?? ''}', api: widget.api, ), const SizedBox(height: 4), Text( '${m['name'] ?? m['displayName'] ?? ''}', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 12, color: kInk), ), ], ), ), SizedBox( width: 56, child: Column( children: [ InkWell( onTap: _addMembers, child: Container( width: 48, height: 48, decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), border: Border.all(color: const Color(0xFFC8C8C8), style: BorderStyle.solid), ), child: const Icon(Icons.add, color: kMute), ), ), const SizedBox(height: 4), const Text(' ', style: TextStyle(fontSize: 12)), ], ), ), ], ), ), const SizedBox(height: 12), CellGroup( children: [ Cell(title: '查找聊天内容', onTap: _search, showLine: false), ], ), CellGroup( children: [ Cell( title: '消息免打扰', trailing: Switch( value: _mute, activeTrackColor: kWeGreen, onChanged: (v) async { await ChatPrefs.setMuted(widget.conversationId, v); setState(() => _mute = v); }, ), ), Cell( title: '置顶聊天', trailing: Switch( value: _pin, activeTrackColor: kWeGreen, onChanged: (v) async { await ChatPrefs.setPinned(widget.conversationId, v); setState(() => _pin = v); }, ), showLine: false, ), ], ), CellGroup( children: [ Cell(title: '设置当前聊天背景', onTap: _pickBg), Cell(title: '语音通话', onTap: () => widget.onCall?.call('audio')), Cell( title: '视频通话', onTap: () => widget.onCall?.call(widget.isGroup ? 'meeting' : 'video'), showLine: false, ), ], ), CellGroup( children: [ Cell(title: '删除聊天记录', onTap: _clear, showLine: false), ], ), CellGroup( children: [ Cell( title: '投诉', onTap: () { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已记录投诉,我们会尽快处理'))); }, showLine: false, ), ], ), const SizedBox(height: 24), ], ), ); } } class _ChatSearchPage extends StatefulWidget { const _ChatSearchPage({required this.messages, required this.peerName}); final List> messages; final String peerName; @override State<_ChatSearchPage> createState() => _ChatSearchPageState(); } class _ChatSearchPageState extends State<_ChatSearchPage> { String _q = ''; @override Widget build(BuildContext context) { final q = _q.trim(); final hits = q.isEmpty ? const >[] : widget.messages.where((e) => '${e['body'] ?? ''}'.contains(q)).toList(); return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: const Text('查找聊天内容')), body: Column( children: [ WxSearchBar(hint: '搜索', onChanged: (v) => setState(() => _q = v)), Expanded( child: q.isEmpty ? const Center(child: Text('输入关键词查找聊天内容', style: TextStyle(color: kMute))) : hits.isEmpty ? const Center(child: Text('没有找到相关内容', style: TextStyle(color: kMute))) : ListView( children: [ for (final r in hits) Cell( title: '${r['body']}', subtitle: '${r['fromName'] ?? widget.peerName} ${r['createdAt'] ?? ''}', showLine: true, ), ], ), ), ], ), ); } }