// 发现页 — 严格对齐 spec §3.12 discover.html // // 视觉层级(从上到下): // 1. 搜索框 (pill 形状) // 2. 每日推荐卡片 inspiration-card (accent→tertiary 渐变) // 3. 热门话题 hot-topics (横向滚动 chips) // 4. 精选模板 featured-templates (2 列网格) // 5. 达人日记 expert-diaries (纵向列表) // // 注意:本页是发现/灵感浏览,区别于 /search(主动搜索) import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../core/constants/design_tokens.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_radius.dart'; import '../../../core/theme/app_shadows.dart'; import '../../../core/theme/app_typography.dart'; class DiscoverPage extends StatelessWidget { const DiscoverPage({super.key}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; final bg = isDark ? AppColors.bgDark : AppColors.bgLight; return Scaffold( backgroundColor: bg, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: DesignTokens.spacing12), _SearchBar(onTap: () => context.push('/search')), const SizedBox(height: DesignTokens.spacing20), const _InspirationCard( title: '今日推荐:图书馆的午后时光', author: '小暖 · 5月31日', emoji: '📚', ), const SizedBox(height: DesignTokens.spacing24), _SectionTitle(title: '热门话题'), const SizedBox(height: DesignTokens.spacing12), const _HotTopicsChips(), const SizedBox(height: DesignTokens.spacing24), _SectionTitle(title: '精选模板'), const SizedBox(height: DesignTokens.spacing12), const _FeaturedTemplatesGrid(), const SizedBox(height: DesignTokens.spacing24), _SectionTitle(title: '达人日记'), const SizedBox(height: DesignTokens.spacing12), const _ExpertDiariesList(), const SizedBox(height: DesignTokens.spacing24), ], ), ), ), ); } } /// 1. 搜索框(点击跳转 /search) class _SearchBar extends StatelessWidget { const _SearchBar({required this.onTap}); final VoidCallback onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); return InkWell( onTap: onTap, borderRadius: AppRadius.pillBorder, child: Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: DesignTokens.spacing16), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: AppRadius.pillBorder, border: Border.all(color: theme.colorScheme.outlineVariant), ), child: Row( children: [ Icon(Icons.search_rounded, size: 20, color: theme.colorScheme.onSurfaceVariant), const SizedBox(width: DesignTokens.spacing12), Text( '搜索日记、模板、话题...', style: TextStyle( fontSize: 14, color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), ); } } /// 2. 每日推荐卡片(渐变背景) class _InspirationCard extends StatelessWidget { const _InspirationCard({ required this.title, required this.author, required this.emoji, }); final String title; final String author; final String emoji; @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(DesignTokens.spacing20), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.accent, AppColors.tertiary], ), borderRadius: AppRadius.lgBorder, boxShadow: [ BoxShadow( color: AppColors.accent.withValues(alpha: 0.2), offset: const Offset(0, 4), blurRadius: 14, ), ], ), child: Stack( children: [ // 装饰圆 Positioned( right: -20, top: -20, child: Container( width: 120, height: 120, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withValues(alpha: 0.12), ), ), ), Positioned( left: -10, bottom: -20, child: Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white.withValues(alpha: 0.08), ), ), ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '今日推荐', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white.withValues(alpha: 0.85), letterSpacing: 0.5, ), ), const SizedBox(height: DesignTokens.spacing12), Row( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: AppRadius.mdBorder, ), alignment: Alignment.center, child: Text(emoji, style: const TextStyle(fontSize: 36)), ), const SizedBox(width: DesignTokens.spacing16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: Colors.white, height: 1.25, ), ), const SizedBox(height: 6), Text( author, style: TextStyle( fontSize: 12, color: Colors.white.withValues(alpha: 0.75), ), ), ], ), ), ], ), ], ), ], ), ); } } class _SectionTitle extends StatelessWidget { const _SectionTitle({required this.title}); final String title; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Text( title, style: TextStyle( fontFamily: AppTypography.displayFont, fontSize: 20, fontWeight: FontWeight.w700, color: theme.colorScheme.onSurface, ), ); } } /// 3. 热门话题(横向滚动 chips) class _HotTopicsChips extends StatelessWidget { const _HotTopicsChips(); static const _topics = [ '#期末备考', '#读书笔记', '#旅行手账', '#美食日记', '#校园生活', '#自我成长', '#心情日记', '#手写摘抄', ]; @override Widget build(BuildContext context) { final theme = Theme.of(context); return SizedBox( height: 44, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: _topics.length, separatorBuilder: (_, __) => const SizedBox(width: DesignTokens.spacing8), itemBuilder: (context, index) { final isHot = index < 3; return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: isHot ? theme.colorScheme.primary : theme.colorScheme.surface, borderRadius: AppRadius.pillBorder, border: isHot ? null : Border.all(color: theme.colorScheme.outlineVariant), ), alignment: Alignment.center, child: Text( _topics[index], style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: isHot ? theme.colorScheme.onPrimary : theme.colorScheme.onSurface, ), ), ); }, ), ); } } /// 4. 精选模板(2 列网格) class _FeaturedTemplatesGrid extends StatelessWidget { const _FeaturedTemplatesGrid(); static const _templates = [ ('📖', '每日心情日记', '2.3k 人使用', AppColors.secondarySoftLight), ('🎓', '期末复习计划', '1.8k 人使用', AppColors.tertiarySoftLight), ('🌿', '植物观察日记', '956 人使用', AppColors.roseSoftLight), ('✈️', '旅行手账本', '742 人使用', AppColors.secondarySoftLight), ]; @override Widget build(BuildContext context) { return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: DesignTokens.spacing12, crossAxisSpacing: DesignTokens.spacing12, childAspectRatio: 0.85, ), itemCount: _templates.length, itemBuilder: (context, index) { final t = _templates[index]; return _TemplateCard(emoji: t.$1, name: t.$2, usage: t.$3, bg: t.$4); }, ); } } class _TemplateCard extends StatelessWidget { const _TemplateCard({ required this.emoji, required this.name, required this.usage, required this.bg, }); final String emoji; final String name; final String usage; final Color bg; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Material( color: theme.colorScheme.surface, borderRadius: AppRadius.mdBorder, child: InkWell( onTap: () => context.push('/templates'), borderRadius: AppRadius.mdBorder, child: Container( decoration: BoxDecoration( borderRadius: AppRadius.mdBorder, border: Border.all(color: theme.colorScheme.outlineVariant), ), padding: const EdgeInsets.all(DesignTokens.spacing12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: 96, decoration: BoxDecoration( color: bg, borderRadius: AppRadius.smBorder, ), alignment: Alignment.center, child: Text(emoji, style: const TextStyle(fontSize: 32)), ), const SizedBox(height: DesignTokens.spacing8), Text( name, style: TextStyle( fontFamily: AppTypography.displayFont, fontSize: 14, fontWeight: FontWeight.w600, color: theme.colorScheme.onSurface, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( usage, style: TextStyle(fontSize: 11, color: theme.colorScheme.onSurfaceVariant), ), ], ), ), ), ); } } /// 5. 达人日记(纵向列表) class _ExpertDiariesList extends StatelessWidget { const _ExpertDiariesList(); static const _experts = [ ('🌸', '小桃子', '春日漫步手账', '记录春天的每一朵花开...', '342 赞'), ('☕', '咖啡少年', '咖啡馆日记', '今天尝试了一家新店...', '218 赞'), ('📝', '学习达人', '考研倒计时30天', '坚持就是胜利...', '556 赞'), ]; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Column( children: _experts.map((e) { return Container( margin: const EdgeInsets.only(bottom: DesignTokens.spacing12), padding: const EdgeInsets.all(DesignTokens.spacing16), decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: AppRadius.mdBorder, border: Border.all(color: theme.colorScheme.outlineVariant), boxShadow: AppShadows.soft(context), ), child: Row( children: [ Container( width: 44, height: 44, decoration: BoxDecoration( color: AppColors.surfaceWarmLight, shape: BoxShape.circle, ), alignment: Alignment.center, child: Text(e.$1, style: const TextStyle(fontSize: 20)), ), const SizedBox(width: DesignTokens.spacing12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( e.$2, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: theme.colorScheme.onSurfaceVariant, ), ), const SizedBox(width: DesignTokens.spacing8), Text( '·', style: TextStyle(color: theme.colorScheme.onSurfaceVariant), ), const SizedBox(width: DesignTokens.spacing8), Expanded( child: Text( e.$3, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontFamily: AppTypography.displayFont, fontSize: 15, fontWeight: FontWeight.w600, color: theme.colorScheme.onSurface, ), ), ), ], ), const SizedBox(height: 4), Text( e.$4, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 13, color: theme.colorScheme.onSurfaceVariant, height: 1.5, ), ), ], ), ), const SizedBox(width: DesignTokens.spacing8), Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.favorite_rounded, size: 14, color: AppColors.rose), const SizedBox(width: 4), Text( e.$5, style: TextStyle(fontSize: 11, color: theme.colorScheme.onSurfaceVariant), ), ], ), ], ), ); }).toList(), ); } }