// 模板画廊页面 — 日记模板浏览和选择 import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:nuanji_app/core/theme/app_colors.dart'; import 'package:nuanji_app/core/theme/app_radius.dart'; import 'package:nuanji_app/data/remote/api_client.dart'; import '../bloc/template_bloc.dart'; /// 视图模式 enum _ViewMode { daily, weekly, monthly } /// 模板画廊页面 — 浏览和选择日记模板 class TemplateGalleryPage extends StatefulWidget { const TemplateGalleryPage({super.key}); @override State createState() => _TemplateGalleryPageState(); } class _TemplateGalleryPageState extends State { late final TemplateBloc _bloc; _ViewMode _viewMode = _ViewMode.daily; @override void initState() { super.initState(); _bloc = TemplateBloc(api: context.read()); _bloc.load(); } @override void dispose() { _bloc.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final isDark = theme.brightness == Brightness.dark; final surfaceWarm = isDark ? AppColors.surfaceWarmDark : AppColors.surfaceWarmLight; return Scaffold( body: SafeArea( child: ListenableBuilder( listenable: _bloc, builder: (context, _) { final state = _bloc.state; if (state.isLoading) { return const Center(child: CircularProgressIndicator()); } if (state.errorMessage != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 48, color: colorScheme.error), const SizedBox(height: 16), FilledButton.tonal( onPressed: _bloc.load, child: const Text('重试'), ), ], ), ); } return Column( children: [ // ---- 自定义顶栏 ---- Padding( padding: const EdgeInsets.fromLTRB(8, 8, 16, 0), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back_ios_new, size: 20), onPressed: () => Navigator.of(context).pop(), ), Text('模板画廊', style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, )), ], ), ), const SizedBox(height: 12), // ---- 视图选择器 (日/周/月) ---- Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ _ViewModeButton( emoji: '📅', label: '日视图', selected: _viewMode == _ViewMode.daily, surfaceWarm: surfaceWarm, onTap: () => setState(() => _viewMode = _ViewMode.daily), ), const SizedBox(width: 8), _ViewModeButton( emoji: '📊', label: '周视图', selected: _viewMode == _ViewMode.weekly, surfaceWarm: surfaceWarm, onTap: () => setState(() => _viewMode = _ViewMode.weekly), ), const SizedBox(width: 8), _ViewModeButton( emoji: '📈', label: '月视图', selected: _viewMode == _ViewMode.monthly, surfaceWarm: surfaceWarm, onTap: () => setState(() => _viewMode = _ViewMode.monthly), ), ], ), ), const SizedBox(height: 12), // ---- 分类选择器 ---- SizedBox( height: 40, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), children: state.categories.map((cat) { final isSelected = cat == state.selectedCategory; return Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( selected: isSelected, label: Text(cat), onSelected: (_) => _bloc.selectCategory(cat), selectedColor: AppColors.accent.withValues(alpha: 0.15), checkmarkColor: AppColors.accent, labelStyle: TextStyle( color: isSelected ? AppColors.accent : colorScheme.onSurface, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ); }).toList(), ), ), const SizedBox(height: 8), // ---- 模板网格 (200px 高预览) ---- Expanded( child: state.filteredTemplates.isEmpty ? const Center(child: Text('暂无模板')) : GridView.builder( padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 12, crossAxisSpacing: 12, childAspectRatio: 0.52, ), itemCount: state.filteredTemplates.length, itemBuilder: (context, index) { return _TemplateCard( template: state.filteredTemplates[index], ); }, ), ), ], ); }, ), ), ); } } /// 视图模式按钮 class _ViewModeButton extends StatelessWidget { const _ViewModeButton({ required this.emoji, required this.label, required this.selected, required this.surfaceWarm, required this.onTap, }); final String emoji; final String label; final bool selected; final Color surfaceWarm; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: selected ? surfaceWarm : Colors.transparent, border: Border.all( color: selected ? AppColors.accent : Theme.of(context).colorScheme.outlineVariant, width: selected ? 1.5 : 1, ), borderRadius: AppRadius.pillBorder, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text(emoji, style: const TextStyle(fontSize: 14)), const SizedBox(width: 4), Text(label, style: TextStyle( fontSize: 13, fontWeight: selected ? FontWeight.w600 : FontWeight.normal, color: selected ? AppColors.accent : Theme.of(context).colorScheme.onSurface, )), ], ), ), ); } } /// 模板卡片 — 200px 预览 + 使用按钮 + 标签 class _TemplateCard extends StatelessWidget { const _TemplateCard({required this.template}); final Template template; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final isDark = theme.brightness == Brightness.dark; final secondarySoft = isDark ? AppColors.secondarySoftDark : AppColors.secondarySoftLight; final tertiarySoft = isDark ? AppColors.tertiarySoftDark : AppColors.tertiarySoftLight; return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: AppRadius.mdBorder, side: BorderSide(color: colorScheme.outlineVariant), ), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 模板预览区 — 200px 高 Expanded( child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ colorScheme.primaryContainer.withValues(alpha: 0.5), AppColors.tertiary.withValues(alpha: 0.3), ], ), borderRadius: AppRadius.mdBorder, ), alignment: Alignment.center, child: Text( template.emoji, style: const TextStyle(fontSize: 48), ), ), ), const SizedBox(height: 10), // 模板名称 Text( template.name, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), // 描述 if (template.description != null) Text( template.description!, style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.5), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), // 标签(从模板 category 动态生成) Wrap( spacing: 6, runSpacing: 4, children: [ if (template.category != null && template.category!.isNotEmpty) _TagPill( label: template.category!, bgColor: secondarySoft, textColor: AppColors.secondary, ), _TagPill( label: template.isFree ? '免费' : '精品', bgColor: template.isFree ? tertiarySoft : AppColors.roseSoftLight, textColor: template.isFree ? AppColors.tertiary : AppColors.rose, ), ], ), const SizedBox(height: 8), // 使用按钮 SizedBox( width: double.infinity, child: FilledButton( onPressed: () { context.push('/editor?template=${template.id}'); }, style: FilledButton.styleFrom( backgroundColor: AppColors.accent, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), shape: RoundedRectangleBorder(borderRadius: AppRadius.pillBorder), ), child: const Text('使用', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Colors.white, )), ), ), ], ), ), ); } } /// 标签胶囊 class _TagPill extends StatelessWidget { const _TagPill({required this.label, required this.bgColor, required this.textColor}); final String label; final Color bgColor; final Color textColor; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: bgColor, borderRadius: AppRadius.pillBorder, ), child: Text(label, style: TextStyle( fontSize: 10, fontWeight: FontWeight.w500, color: textColor, )), ); } }