import 'package:flutter/material.dart'; import '../../core/module/app_module.dart'; import '../../widgets/cute_icon.dart'; import '../assistant/assistant_page.dart'; /// 首页仪表盘:AI 输入入口 + 模块卡片网格。 /// /// 这个页面是「模块目录」,不是某个模块的主页 —— 卡片全部从 [appModules] /// 生成,所以将来加模块时**这个文件不需要改**。 /// /// 副作用是:打开 App 第一眼看不到账单了,要点一下「记账」卡片才进账单首页。 /// 这是刻意的 —— 记账不再是主角。 class DashboardPage extends StatelessWidget { const DashboardPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( body: Container( width: double.infinity, height: double.infinity, decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFFFFE5CC), Color(0xFFFFD4A8), Color(0xFFFFC08A)], ), ), child: SafeArea( child: ListView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), children: [ _header(), const SizedBox(height: 16), _aiEntry(context), const SizedBox(height: 22), const Text( '功能模块', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), const SizedBox(height: 10), _moduleGrid(context), const SizedBox(height: 90), // 底部导航避让 ], ), ), ), ); } // ---- 标题 + 问候 ---- Widget _header() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '强宝小助手', style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Color(0xFF5D4037), letterSpacing: 1, ), ), const SizedBox(height: 4), Text( _greeting(), style: const TextStyle(fontSize: 13, color: Color(0xFF8D6E63)), ), ], ); } String _greeting() { final hour = DateTime.now().hour; if (hour < 6) return '夜深了,注意休息'; if (hour < 12) return '早上好,今天想做点什么?'; if (hour < 18) return '下午好,今天想做点什么?'; return '晚上好,今天想做点什么?'; } // ---- AI 入口(阶段 B 接上,现在点进去是占位页)---- Widget _aiEntry(BuildContext context) { return GestureDetector( onTap: () => Navigator.of(context).push( MaterialPageRoute(builder: (_) => const AssistantPage()), ), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFC8956E), width: 1.2), boxShadow: [ BoxShadow( color: const Color(0xFF5D4037).withValues(alpha: 0.08), blurRadius: 10, offset: const Offset(0, 3), ), ], ), child: Row( children: [ const CuteIcon('assistant', size: 19), const SizedBox(width: 10), const Expanded( child: Text( '问点什么,或者说一句…', style: TextStyle(fontSize: 14, color: Color(0xFFBCAAA4)), ), ), const Icon(Icons.chevron_right, size: 20, color: Color(0xFFBCAAA4)), ], ), ), ); } // ---- 模块卡片网格 ---- // // 两列。不用 GridView + childAspectRatio:卡片高度由内容(摘要可有可无) // 决定,估错比例就会在开发时刷一屏溢出警告 —— GridView 卡片的经典坑。 // 这里让每张卡片自己撑高,居上对齐。 Widget _moduleGrid(BuildContext context) { const gap = 12.0; final rows = []; for (var i = 0; i < appModules.length; i += 2) { final end = i + 2 <= appModules.length ? i + 2 : appModules.length; final pair = appModules.sublist(i, end); if (rows.isNotEmpty) rows.add(const SizedBox(height: gap)); rows.add( Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ for (var j = 0; j < pair.length; j++) ...[ if (j > 0) const SizedBox(width: gap), Expanded(child: _moduleCard(context, pair[j])), ], // 模块数是奇数时补一个空位,否则最后一张卡片会被拉成通栏 if (pair.length == 1) ...[ const SizedBox(width: gap), const Expanded(child: SizedBox()), ], ], ), ); } return Column(children: rows); } Widget _moduleCard(BuildContext context, AppModule module) { return GestureDetector( onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: module.open)), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: const Color(0xFF5D4037).withValues(alpha: 0.08), blurRadius: 10, offset: const Offset(0, 3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CuteIcon(module.icon, size: 26), const SizedBox(height: 8), Text( module.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF5D4037), ), ), const SizedBox(height: 3), Text( module.blurb, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 11.5, color: Color(0xFF8D6E63)), ), if (module.summary != null) ...[ const SizedBox(height: 10), module.summary!(context), ], ], ), ), ); } }