import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../../core/theme/app_colors.dart'; import '../../../../core/widgets/robot_avatar.dart'; import '../../../chat/presentation/providers/chat_providers.dart'; import '../../../notifications/presentation/providers/notification_providers.dart'; import '../../../settings/presentation/providers/settings_providers.dart'; // --------------------------------------------------------------------------- // Home page — "主页" Tab // Shows active agent cards + recent activity + welcome guide // --------------------------------------------------------------------------- class HomePage extends ConsumerWidget { const HomePage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final profile = ref.watch(accountProfileProvider); final chatState = ref.watch(chatProvider); final unreadCount = ref.watch(unreadNotificationCountProvider); final greeting = _greeting(); final name = profile.displayName.isNotEmpty ? profile.displayName : '用户'; return Scaffold( backgroundColor: AppColors.background, body: CustomScrollView( slivers: [ // ── AppBar ────────────────────────────────────────────────────── SliverAppBar( expandedHeight: 120, pinned: true, backgroundColor: AppColors.background, flexibleSpace: FlexibleSpaceBar( titlePadding: const EdgeInsets.fromLTRB(20, 0, 20, 16), title: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$greeting,$name', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), const Text( '我智能体 随时为你服务', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ], ), ), actions: [ if (unreadCount > 0) Stack( children: [ const Padding( padding: EdgeInsets.all(12), child: Icon(Icons.notifications_outlined, color: AppColors.textSecondary), ), Positioned( right: 8, top: 8, child: Container( width: 16, height: 16, decoration: const BoxDecoration( color: AppColors.error, shape: BoxShape.circle, ), child: Center( child: Text( '$unreadCount', style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), ), ], ), const SizedBox(width: 8), ], ), SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 100), sliver: SliverList( delegate: SliverChildListDelegate([ // ── Active agent status card ───────────────────────────── _AgentStatusCard(chatState: chatState), const SizedBox(height: 20), // ── IT0 official agent cards ───────────────────────────── const _SectionHeader(title: 'IT0 官方智能体'), const SizedBox(height: 12), const _OfficialAgentsRow(), const SizedBox(height: 20), // ── Guide if nothing created yet ───────────────────────── const _SectionHeader(title: '我的智能体'), const SizedBox(height: 12), const _MyAgentsPlaceholder(), const SizedBox(height: 20), // ── Quick tips ─────────────────────────────────────────── const _QuickTipsCard(), ]), ), ), ], ), ); } String _greeting() { final hour = DateTime.now().hour; if (hour < 6) return '夜深了'; if (hour < 12) return '早上好'; if (hour < 14) return '中午好'; if (hour < 18) return '下午好'; return '晚上好'; } } // --------------------------------------------------------------------------- // Agent status card — shows current iAgent state // --------------------------------------------------------------------------- class _AgentStatusCard extends StatelessWidget { final ChatState chatState; const _AgentStatusCard({required this.chatState}); @override Widget build(BuildContext context) { final isActive = chatState.isStreaming; final statusText = switch (chatState.agentStatus) { AgentStatus.idle => '空闲中', AgentStatus.thinking => '正在思考...', AgentStatus.executing => '执行指令中...', AgentStatus.awaitingApproval => '等待审批', AgentStatus.error => '发生错误', }; final robotState = switch (chatState.agentStatus) { AgentStatus.idle => RobotState.idle, AgentStatus.thinking => RobotState.thinking, AgentStatus.executing => RobotState.executing, AgentStatus.awaitingApproval => RobotState.alert, AgentStatus.error => RobotState.alert, }; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(20), border: Border.all( color: isActive ? AppColors.primary.withOpacity(0.5) : AppColors.surfaceLight.withOpacity(0.3), ), gradient: isActive ? LinearGradient( colors: [ AppColors.primary.withOpacity(0.08), AppColors.surface, ], begin: Alignment.topLeft, end: Alignment.bottomRight, ) : null, ), child: Row( children: [ RobotAvatar(state: robotState, size: 64), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '我智能体', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), const SizedBox(height: 4), Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: isActive ? AppColors.success : AppColors.textMuted, ), ), const SizedBox(width: 6), Text( statusText, style: TextStyle( fontSize: 13, color: isActive ? AppColors.success : AppColors.textSecondary, ), ), ], ), if (chatState.messages.isNotEmpty) ...[ const SizedBox(height: 6), Text( '对话中 · ${chatState.messages.length} 条消息', style: const TextStyle( fontSize: 12, color: AppColors.textMuted, ), ), ], ], ), ), // Arrow hint const Icon(Icons.chevron_right, color: AppColors.textMuted), ], ), ); } } // --------------------------------------------------------------------------- // Section header // --------------------------------------------------------------------------- class _SectionHeader extends StatelessWidget { final String title; const _SectionHeader({required this.title}); @override Widget build(BuildContext context) { return Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ); } } // --------------------------------------------------------------------------- // Official agents row (horizontal scroll cards) // --------------------------------------------------------------------------- class _OfficialAgentsRow extends StatelessWidget { const _OfficialAgentsRow(); static const _agents = [ _AgentCard( name: '我智能体 运维助手', desc: '服务器管理、SSH 执行、日志分析', icon: Icons.dns_outlined, color: Color(0xFF6366F1), isOfficial: true, ), _AgentCard( name: '安全审计助手', desc: '漏洞扫描、权限审查、合规检查', icon: Icons.security_outlined, color: Color(0xFF22C55E), isOfficial: true, ), _AgentCard( name: '数据库巡检', desc: '慢查询分析、索引优化、备份验证', icon: Icons.storage_outlined, color: Color(0xFF0EA5E9), isOfficial: true, ), ]; @override Widget build(BuildContext context) { return SizedBox( height: 130, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: _agents.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) => _agents[index], ), ); } } class _AgentCard extends StatelessWidget { final String name; final String desc; final IconData icon; final Color color; final bool isOfficial; const _AgentCard({ required this.name, required this.desc, required this.icon, required this.color, this.isOfficial = false, }); @override Widget build(BuildContext context) { return Container( width: 180, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 36, height: 36, decoration: BoxDecoration( color: color.withOpacity(0.15), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: color, size: 20), ), if (isOfficial) ...[ const Spacer(), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.15), borderRadius: BorderRadius.circular(6), ), child: Text( '官方', style: TextStyle( fontSize: 10, color: color, fontWeight: FontWeight.w600, ), ), ), ], ], ), const SizedBox(height: 10), Text( name, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( desc, style: const TextStyle( fontSize: 11, color: AppColors.textMuted, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ); } } // --------------------------------------------------------------------------- // My agents placeholder — guides user to talk to iAgent // --------------------------------------------------------------------------- class _MyAgentsPlaceholder extends StatelessWidget { const _MyAgentsPlaceholder(); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all( color: AppColors.primary.withOpacity(0.2), style: BorderStyle.solid, ), ), child: Column( children: [ const Icon( Icons.add_circle_outline, size: 40, color: AppColors.primary, ), const SizedBox(height: 12), const Text( '还没有自己的智能体', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: 6), const Text( '点击下方机器人按钮,告诉 我智能体\n"帮我招募一个 OpenClaw 智能体"', style: TextStyle( fontSize: 13, color: AppColors.textSecondary, height: 1.5, ), textAlign: TextAlign.center, ), ], ), ); } } // --------------------------------------------------------------------------- // Quick tips card // --------------------------------------------------------------------------- class _QuickTipsCard extends StatelessWidget { const _QuickTipsCard(); static const _tips = [ '💬 "帮我招募一个监控 GitHub Actions 的智能体"', '🔧 "把我的 OpenClaw 配置导出为 JSON"', '📊 "分析我的服务器最近7天的负载情况"', '🛡️ "帮我设置每天凌晨2点自动备份数据库"', ]; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '你可以这样说...', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.textSecondary, ), ), const SizedBox(height: 10), ..._tips.map( (tip) => Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( tip, style: const TextStyle( fontSize: 13, color: AppColors.textMuted, height: 1.4, ), ), ), ), ], ), ); } }