import 'package:flutter/material.dart'; import 'package:flutter_openim_sdk/flutter_openim_sdk.dart'; import '../services/im_service.dart'; import '../theme.dart'; import '../utils/format.dart'; import '../widgets/avatar.dart'; import '../widgets/search_box.dart'; import '../widgets/state_views.dart'; import 'add_friend_screen.dart'; import 'chat_screen.dart'; /// 消息列表页(效果图 m1): /// 标题「畅联」+ 圆形加号、搜索框、会话行(头像/名称/预览/时间/未读角标/免打扰铃铛)、 /// 置顶置灰,空/加载/失败/断网四态按 states.png。 class ConversationListScreen extends StatefulWidget { /// 空态「去找同事」按钮:切到通讯录 Tab final VoidCallback? onFindContacts; const ConversationListScreen({super.key, this.onFindContacts}); @override State createState() => _ConversationListScreenState(); } class _ConversationListScreenState extends State { String _keyword = ''; bool get _offline => IMService.instance.connectStatus != ConnectStatus.success; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('畅联'), actions: [ IconButton( icon: const Icon(Icons.add_circle_outline, size: 26), onPressed: () { // 加号:从简只保留「添加同事」入口 showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: ListTile( leading: const Icon(Icons.person_add_alt), title: const Text('添加同事'), onTap: () { Navigator.of(ctx).pop(); Navigator.of(context).push(MaterialPageRoute(builder: (_) => const AddFriendScreen())); }, ), ), ); }, ), ], ), body: AnimatedBuilder( animation: IMService.instance, builder: (context, _) { return Column( children: [ if (_offline) const OfflineBanner(), SearchBox(onChanged: (v) => setState(() => _keyword = v)), Expanded(child: _buildBody()), ], ); }, ), ); } Widget _buildBody() { final im = IMService.instance; // 四态:加载中 / 加载失败 / 空 / 列表 if (!im.conversationsLoaded && im.syncing && im.conversations.isEmpty) { return const LoadingConversations(); } if (im.syncFailed && im.conversations.isEmpty) { return ErrorConversations(onRetry: () => im.refreshConversations()); } final list = _keyword.isEmpty ? im.conversations : im.conversations .where((c) => (c.showName ?? '').toLowerCase().contains(_keyword.toLowerCase())) .toList(); if (list.isEmpty) { if (_keyword.isNotEmpty) { return const Center( child: Text('没有找到相关会话', style: TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary)), ); } return EmptyConversations(onFindContacts: widget.onFindContacts); } return ListView.separated( itemCount: list.length + (_offline ? 1 : 0), separatorBuilder: (_, i) => const Divider(indent: 80), itemBuilder: (context, i) { if (i >= list.length) return const OfflineFooter(); return _conversationTile(list[i]); }, ); } Widget _conversationTile(ConversationInfo c) { final isGroup = c.conversationType == ConversationType.superGroup || c.conversationType == ConversationType.group; final muted = (c.recvMsgOpt ?? 0) != 0; return Material( color: c.isPinned == true ? AppColors.pinnedBg : AppColors.pageBg, child: InkWell( onTap: () { Navigator.of(context) .push(MaterialPageRoute(builder: (_) => ChatScreen(conversation: c))) .then((_) => IMService.instance.refreshConversations()); }, child: Padding( padding: const EdgeInsets.symmetric(horizontal: AppGap.x3, vertical: AppGap.x3), child: Row( children: [ _avatarWithBadge(c, isGroup, muted), const SizedBox(width: AppGap.x3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( c.showName ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: AppFont.body, color: AppColors.textPrimary), ), const SizedBox(height: AppGap.x1), Row( children: [ Expanded( child: Text( FormatUtils.messagePreview(c.latestMsg, isGroup: isGroup), maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: AppFont.sub, color: AppColors.textSecondary), ), ), if (muted) const Icon(Icons.notifications_off_outlined, size: 16, color: AppColors.textSecondary), ], ), ], ), ), const SizedBox(width: AppGap.x2), Text( FormatUtils.conversationTime(c.latestMsgSendTime), style: const TextStyle(fontSize: AppFont.small, color: AppColors.textSecondary), ), ], ), ), ), ); } Widget _avatarWithBadge(ConversationInfo c, bool isGroup, bool muted) { final unread = c.unreadCount; return Stack( clipBehavior: Clip.none, children: [ NameAvatar(name: c.showName ?? '', faceURL: c.faceURL, isGroup: isGroup, size: 56), if (unread > 0) Positioned( right: -6, top: -6, child: Container( constraints: const BoxConstraints(minWidth: 18), height: 18, padding: const EdgeInsets.symmetric(horizontal: 4), alignment: Alignment.center, decoration: BoxDecoration( // 免打扰会话的角标弱化(参考微信习惯) color: muted ? AppColors.textSecondary : AppColors.danger, borderRadius: BorderRadius.circular(9), ), child: Text( unread > 99 ? '99+' : '$unread', style: const TextStyle(fontSize: 11, color: Colors.white), ), ), ), ], ); } }