import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../core/msn_api.dart'; import '../../core/session_controller.dart'; import '../../models/context_member_model.dart'; import '../../models/room_model.dart'; import '../../theme/toss_theme.dart'; import '../profile/user_profile_sheet.dart'; class SearchPage extends ConsumerStatefulWidget { const SearchPage({super.key, required this.contextId}); final String contextId; @override ConsumerState createState() => _SearchPageState(); } class _SearchPageState extends ConsumerState { final _q = TextEditingController(); List> _messageResults = []; List _rooms = []; List _friends = []; bool _busy = false; bool _loadedLists = false; int _segment = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _ensureLists()); } @override void dispose() { _q.dispose(); super.dispose(); } Future _ensureLists() async { if (_loadedLists || widget.contextId.isEmpty) return; setState(() => _busy = true); try { final api = ref.read(msnApiProvider); final rooms = await api.listRooms(widget.contextId); final members = await api.listContextMembers(widget.contextId); if (!mounted) return; setState(() { _rooms = rooms; _friends = members; _loadedLists = true; }); } finally { if (mounted) setState(() => _busy = false); } } Future _runMessageSearch() async { if (widget.contextId.isEmpty) return; setState(() => _busy = true); try { final r = await ref.read(msnApiProvider).searchMessages(widget.contextId, _q.text); if (!mounted) return; setState(() => _messageResults = r); } finally { if (mounted) setState(() => _busy = false); } } String get _needle => _q.text.trim().toLowerCase(); List get _filteredRooms { if (_needle.isEmpty) return _rooms; return _rooms.where((r) { final name = (r.name ?? (r.isGroup ? '그룹' : '1:1')).toLowerCase(); final last = (r.lastBody ?? '').toLowerCase(); return name.contains(_needle) || last.contains(_needle); }).toList(); } List get _filteredFriends { if (_needle.isEmpty) return _friends; return _friends.where((m) { final name = m.displayName.toLowerCase(); final st = (m.statusMessage ?? '').toLowerCase(); return name.contains(_needle) || m.userId.toLowerCase().contains(_needle) || st.contains(_needle); }).toList(); } @override Widget build(BuildContext context) { final tt = Theme.of(context).textTheme; final myId = ref.watch(sessionProvider).value?.userId; return Scaffold( backgroundColor: TossColors.bg, appBar: AppBar(title: const Text('검색')), body: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), child: SegmentedButton( segments: const [ ButtonSegment(value: 0, label: Text('메시지'), icon: Icon(Icons.search, size: 18)), ButtonSegment(value: 1, label: Text('방'), icon: Icon(Icons.chat_bubble_outline, size: 18)), ButtonSegment(value: 2, label: Text('친구'), icon: Icon(Icons.people_outline, size: 18)), ], selected: {_segment}, onSelectionChanged: (s) { if (s.isEmpty) return; setState(() => _segment = s.first); }, ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: Row( children: [ Expanded( child: TextField( controller: _q, decoration: InputDecoration( hintText: _segment == 0 ? '이 맥락에서 메시지 검색' : _segment == 1 ? '방 이름 또는 미리보기로 필터' : '이름·상태로 친구 필터', prefixIcon: const Icon(Icons.search_rounded, color: TossColors.textSecondary), ), onChanged: (_) { if (_segment != 0) setState(() {}); }, onSubmitted: (_) { if (_segment == 0) unawaited(_runMessageSearch()); }, ), ), const SizedBox(width: 8), if (_segment == 0) Material( color: TossColors.blue, borderRadius: BorderRadius.circular(12), child: IconButton( onPressed: _busy ? null : () => unawaited(_runMessageSearch()), icon: _busy ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.arrow_forward_rounded, color: Colors.white), ), ), ], ), ), Expanded( child: _busy && !_loadedLists && _segment != 0 ? const Center(child: CircularProgressIndicator(color: TossColors.blue)) : _segment == 0 ? _buildMessageList(tt) : _segment == 1 ? _buildRoomList(tt) : _buildFriendList(tt, myId), ), ], ), ); } Widget _buildMessageList(TextTheme tt) { return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), itemCount: _messageResults.length, separatorBuilder: (_, __) => const SizedBox(height: 8), itemBuilder: (context, i) { final r = _messageResults[i]; return Material( color: TossColors.surface, borderRadius: BorderRadius.circular(14), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: () { final roomId = r['roomId'] as String?; if (roomId == null) return; context.push('/chat?roomId=$roomId&contextId=${widget.contextId}'); }, child: Ink( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: TossColors.line), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(r['body'] as String? ?? '', style: tt.bodyLarge), const SizedBox(height: 6), Text('방 ${r['roomId']}', style: tt.bodySmall), ], ), ), ), ), ); }, ); } Widget _buildRoomList(TextTheme tt) { final list = _filteredRooms; if (list.isEmpty) { return Center(child: Text(_emptyHint('방'))); } return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), itemCount: list.length, separatorBuilder: (_, __) => const SizedBox(height: 8), itemBuilder: (context, i) { final r = list[i]; final title = r.name ?? (r.isGroup ? '그룹 채팅' : '1:1 채팅'); final sub = r.lastBody ?? '메시지 없음'; return Material( color: TossColors.surface, borderRadius: BorderRadius.circular(14), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: () => context.push('/chat?roomId=${r.id}&contextId=${widget.contextId}'), child: Ink( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: TossColors.line), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, style: tt.titleMedium), const SizedBox(height: 4), Text(sub, style: tt.bodySmall, maxLines: 2, overflow: TextOverflow.ellipsis), ], ), ), ), ), ); }, ); } Widget _buildFriendList(TextTheme tt, String? myId) { final list = _filteredFriends; if (list.isEmpty) { return Center(child: Text(_emptyHint('친구'))); } return ListView.separated( padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), itemCount: list.length, separatorBuilder: (_, __) => const SizedBox(height: 8), itemBuilder: (context, i) { final m = list[i]; final isSelf = myId != null && m.userId == myId; return Material( color: TossColors.surface, borderRadius: BorderRadius.circular(14), child: InkWell( borderRadius: BorderRadius.circular(14), onTap: () async { if (isSelf) { await showUserProfileSheet( context, ref, contextId: widget.contextId, userId: m.userId, ); } else { final roomId = await ref.read(msnApiProvider).openDirectRoom(widget.contextId, m.userId); if (!context.mounted) return; await context.push('/chat?roomId=$roomId&contextId=${widget.contextId}'); } }, child: Ink( decoration: BoxDecoration( borderRadius: BorderRadius.circular(14), border: Border.all(color: TossColors.line), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( m.displayName.isNotEmpty ? m.displayName : m.userId, style: tt.titleMedium, ), if (m.statusMessage != null && m.statusMessage!.isNotEmpty) Text(m.statusMessage!, style: tt.bodySmall, maxLines: 1), ], ), ), IconButton( icon: const Icon(Icons.person_outline_rounded), onPressed: () async { await showUserProfileSheet( context, ref, contextId: widget.contextId, userId: m.userId, ); }, ), ], ), ), ), ), ); }, ); } String _emptyHint(String kind) { if (_needle.isEmpty) return '목록을 불러오는 중이거나 $kind가 없습니다'; return '검색어와 일치하는 $kind가 없습니다'; } }