import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../../core/theme/design_tokens.dart'; import '../../../chat/data/models/chat_list_item.dart'; import '../../../chat/presentation/bloc/chat_bloc.dart'; import '../../../chat/ui/widgets/ui_schema_renderer.dart'; import '../../../../shared/widgets/toast/toast.dart'; import '../../../../shared/widgets/toast/toast_type.dart'; import 'home_sheet.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { final TextEditingController _messageController = TextEditingController(); final ScrollController _scrollController = ScrollController(); bool get _hasMessage => _messageController.text.trim().isNotEmpty; @override void initState() { super.initState(); _messageController.addListener(_onMessageChanged); } @override void dispose() { _messageController.removeListener(_onMessageChanged); _messageController.dispose(); _scrollController.dispose(); super.dispose(); } void _onMessageChanged() { setState(() {}); } @override Widget build(BuildContext context) { return BlocProvider( create: (context) => ChatBloc(), child: BlocConsumer( listener: (context, state) { if (state.error != null) { Toast.show(context, state.error!, type: ToastType.error); } }, builder: (context, state) { return Scaffold( backgroundColor: const Color(0xFFF8FAFC), body: SafeArea( child: Column( children: [ _buildHeader(context), Expanded(child: _buildChatArea(context, state)), _buildInputContainer(context), ], ), ), ); }, ), ); } Widget _buildHeader(BuildContext context) { return SizedBox( height: 60, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: const Icon( LucideIcons.settings, size: 24, color: AppColors.slate900, ), onPressed: () => context.push('/settings'), ), Row( children: [ IconButton( icon: const Icon( LucideIcons.calendar, size: 24, color: AppColors.slate900, ), onPressed: () => context.push('/calendar/dayweek?from=home'), ), const SizedBox(width: 16), IconButton( icon: const Icon( LucideIcons.messageSquare, size: 24, color: AppColors.slate900, ), onPressed: () => context.push('/messages/invites'), ), ], ), ], ), ), ); } Widget _buildChatArea(BuildContext context, ChatState state) { if (state.items.isEmpty) { return const Center( child: Text( '开始对话吧', style: TextStyle(fontSize: 16, color: AppColors.slate400), ), ); } WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); return ListView.builder( controller: _scrollController, padding: const EdgeInsets.all(20), itemCount: state.items.length, itemBuilder: (context, index) { final item = state.items[index]; return Padding( padding: const EdgeInsets.only(bottom: 16), child: _buildChatItem(item), ); }, ); } Widget _buildChatItem(ChatListItem item) { switch (item.type) { case ChatItemType.message: return _buildMessageItem(item as TextMessageItem); case ChatItemType.toolCall: return _buildToolCallItem(item as ToolCallItem); case ChatItemType.toolResult: return _buildToolResultItem(item as ToolResultItem); } } Widget _buildMessageItem(TextMessageItem item) { final isUser = item.sender == MessageSender.user; return Row( mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ if (!isUser) ...[ Container( width: 32, height: 32, decoration: BoxDecoration( color: AppColors.blue100, shape: BoxShape.circle, ), child: const Icon( LucideIcons.bot, size: 18, color: AppColors.blue600, ), ), const SizedBox(width: 8), ], Flexible( child: Container( padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 9), decoration: BoxDecoration( color: isUser ? const Color(0xFFEAF1FB) : AppColors.white, borderRadius: BorderRadius.only( topLeft: const Radius.circular(12), topRight: const Radius.circular(12), bottomLeft: Radius.circular(isUser ? 12 : 0), bottomRight: Radius.circular(isUser ? 0 : 12), ), border: isUser ? null : Border.all(color: AppColors.slate300), ), child: Text( item.content, style: const TextStyle(fontSize: 14, color: AppColors.slate900), ), ), ), if (isUser) const SizedBox(width: 40), if (!isUser) const SizedBox(width: 40), ], ); } Widget _buildToolCallItem(ToolCallItem item) { String statusText; Color statusColor; IconData statusIcon; switch (item.status) { case ToolCallStatus.pending: statusText = '准备中...'; statusColor = AppColors.slate500; statusIcon = LucideIcons.clock; break; case ToolCallStatus.executing: statusText = '执行中...'; statusColor = AppColors.blue600; statusIcon = LucideIcons.loader; break; case ToolCallStatus.error: statusText = item.errorMessage ?? '执行失败'; statusColor = AppColors.red600; statusIcon = LucideIcons.alertCircle; break; case ToolCallStatus.completed: statusText = '已完成'; statusColor = AppColors.emerald600; statusIcon = LucideIcons.checkCircle; break; } return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: AppColors.blue50, borderRadius: BorderRadius.circular(8), border: Border.all(color: AppColors.slate300), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(statusIcon, size: 16, color: statusColor), const SizedBox(width: 8), Text( item.toolName, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: AppColors.slate700, ), ), const SizedBox(width: 8), Text(statusText, style: TextStyle(fontSize: 12, color: statusColor)), ], ), ); } Widget _buildToolResultItem(ToolResultItem item) { return UiSchemaRenderer.render(item.uiCard); } Widget _buildInputContainer(BuildContext context) { return Container( padding: const EdgeInsets.all(16), color: const Color(0xFFF8FAFC), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( onTap: () => _showBottomSheet(context), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: AppColors.white, shape: BoxShape.circle, border: Border.all(color: AppColors.slate300), ), child: const Icon( LucideIcons.plus, size: 20, color: AppColors.slate500, ), ), ), const SizedBox(width: 8), Expanded( child: Container( constraints: const BoxConstraints(minHeight: 48), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(24), border: Border.all(color: AppColors.slate300), ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: TextField( controller: _messageController, minLines: 1, maxLines: 3, decoration: const InputDecoration( hintText: '输入消息...', border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, disabledBorder: InputBorder.none, errorBorder: InputBorder.none, focusedErrorBorder: InputBorder.none, isDense: true, contentPadding: EdgeInsets.zero, filled: false, ), onSubmitted: (_) => _sendMessage(context), ), ), const SizedBox(width: 8), GestureDetector( onTap: _hasMessage ? () => _sendMessage(context) : null, child: Icon( _hasMessage ? LucideIcons.send : LucideIcons.mic, size: 24, color: _hasMessage ? AppColors.blue600 : AppColors.slate500, ), ), ], ), ), ), ], ), ); } Future _sendMessage(BuildContext context) async { final content = _messageController.text.trim(); if (content.isEmpty) return; _messageController.clear(); context.read().sendMessage(content); } void _showBottomSheet(BuildContext context) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (context) => const HomeSheet(), ); } }