// 贴纸库页面 — 贴纸包浏览 + 分类 Tab import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.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 '../../../widgets/empty_state_widget.dart'; import '../../../widgets/error_state_widget.dart'; import '../bloc/sticker_bloc.dart'; /// 贴纸库页面 — 分类浏览贴纸包 class StickerLibraryPage extends StatefulWidget { const StickerLibraryPage({super.key}); @override State createState() => _StickerLibraryPageState(); } class _StickerLibraryPageState extends State { late final StickerBloc _bloc; final _searchController = TextEditingController(); /// 默认分类 — 从 API 数据动态补充 static const _defaultCategories = ['推荐', '可爱', '植物', '手绘', '校园', '节日', '文字', '和纸胶带']; List get _categories { final apiCategories = _bloc.state.packs .map((p) => p.category) .whereType() .toSet() .toList(); if (apiCategories.isEmpty) return _defaultCategories; // 合并:推荐 + API 返回的分类 return ['推荐', ...apiCategories]; } @override void initState() { super.initState(); _bloc = StickerBloc(api: context.read()); _bloc.load(); } @override void dispose() { _bloc.dispose(); _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; 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 ErrorStateWidget( message: state.errorMessage ?? '加载失败', onRetry: _bloc.load, icon: Icons.error_outline, ); } 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: 8), // ---- 搜索框 ---- Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: '搜索贴纸...', prefixIcon: const Icon(Icons.search, size: 20), filled: true, fillColor: colorScheme.surface, border: OutlineInputBorder( borderRadius: AppRadius.pillBorder, borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), isDense: true, ), style: theme.textTheme.bodyMedium, onChanged: (v) { _bloc.search(v); }, ), ), const SizedBox(height: 12), // ---- 分类选择器(设计规格 8 分类) ---- SizedBox( height: 40, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), children: _categories.map((cat) { final isSelected = cat == state.selectedCategory || (cat == '推荐' && state.selectedCategory == '全部'); return Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( selected: isSelected, label: Text(cat), onSelected: (_) { if (cat == '推荐') { _bloc.selectCategory('全部'); } else { _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: 12), // ---- 精选贴纸包卡片(动态数据) ---- if (state.selectedCategory == '全部' && state.filteredPacks.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: _FeaturedPackCard(pack: state.filteredPacks.first), ), if (state.selectedCategory == '全部' && state.filteredPacks.isNotEmpty) const SizedBox(height: 16), // ---- 贴纸包网格 ---- Expanded( child: state.filteredPacks.isEmpty ? const EmptyStateWidget( icon: Icons.sticky_note_2_outlined, title: '暂无贴纸包', ) : GridView.builder( padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 12, crossAxisSpacing: 12, childAspectRatio: 0.85, ), itemCount: state.filteredPacks.length, itemBuilder: (context, index) { return _StickerPackCard( pack: state.filteredPacks[index], colorScheme: colorScheme, ); }, ), ), ], ); }, ), ), ); } } /// 精选贴纸包卡片 — 渐变背景 + 动态数据 class _FeaturedPackCard extends StatelessWidget { const _FeaturedPackCard({required this.pack}); final StickerPack pack; @override Widget build(BuildContext context) { final theme = Theme.of(context); return GestureDetector( onTap: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('打开精选贴纸包: ${pack.name}')), ); }, child: Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.accent, AppColors.tertiary], ), borderRadius: AppRadius.lgBorder, ), child: Row( children: [ Container( width: 64, height: 64, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.2), borderRadius: AppRadius.mdBorder, ), alignment: Alignment.center, child: Text(pack.displayCover, style: const TextStyle(fontSize: 36)), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(pack.name, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, color: Colors.white, )), const SizedBox(height: 4), Text( pack.description ?? '${pack.stickerCount} 张精选贴纸', style: theme.textTheme.bodySmall?.copyWith( color: Colors.white.withValues(alpha: 0.85), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: pack.isFree ? AppColors.secondary : AppColors.rose, borderRadius: AppRadius.pillBorder, ), child: Text( pack.isFree ? '免费' : '精品', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ), ), ], ), ), ); } } /// 贴纸包卡片 class _StickerPackCard extends StatelessWidget { const _StickerPackCard({ required this.pack, required this.colorScheme, }); final StickerPack pack; final ColorScheme colorScheme; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( elevation: 0, shape: RoundedRectangleBorder( borderRadius: AppRadius.mdBorder, side: BorderSide(color: colorScheme.outlineVariant), ), child: InkWell( onTap: () { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('打开贴纸包: ${pack.name}')), ); }, borderRadius: AppRadius.mdBorder, child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 贴纸包封面图标 Container( width: 64, height: 64, decoration: BoxDecoration( color: colorScheme.primaryContainer.withValues(alpha: 0.3), borderRadius: AppRadius.mdBorder, ), alignment: Alignment.center, child: Text( pack.coverImageUrl != null ? '🎨' : pack.displayCover, style: const TextStyle(fontSize: 32), ), ), const SizedBox(height: 12), // 名称 Text( pack.name, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), // 数量和价格标签 Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '${pack.stickerCount} 张', style: theme.textTheme.bodySmall?.copyWith( color: colorScheme.onSurface.withValues(alpha: 0.5), ), ), if (!pack.isFree) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( color: AppColors.accent.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Text( '积分', style: theme.textTheme.labelSmall?.copyWith( color: AppColors.accent, ), ), ), ], ], ), ], ), ), ), ); } }