import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:go_router/go_router.dart'; import '../../core/core.dart'; import '../../providers/providers.dart'; import '../../widgets/tab_content_layout.dart'; class DiscoveryScreen extends ConsumerWidget { const DiscoveryScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final categoriesAsync = ref.watch(categoriesProvider); final filteredCharactersAsync = ref.watch(filteredCharactersProvider); final selectedCategory = ref.watch(selectedCategoryProvider); const double bottomNavHeight = 90; return TabContentLayout( child: Column( children: [ // 1. Filter Bar SizedBox( height: 50, child: categoriesAsync.when( data: (categories) => ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16), scrollDirection: Axis.horizontal, itemCount: categories.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) { final category = categories[index]; final isActive = selectedCategory == category.code; return Center( child: GestureDetector( onTap: () => ref.read(selectedCategoryProvider.notifier).setCategory(category.code), child: AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: isActive ? Colors.white : Colors.white.withOpacity(0.05), borderRadius: BorderRadius.circular(20), border: Border.all( color: isActive ? Colors.white : Colors.white.withOpacity(0.1), ), ), child: Text( category.label, style: TextStyle( fontSize: 14, fontWeight: isActive ? FontWeight.bold : FontWeight.normal, color: isActive ? const Color(0xFF2E1065) : Colors.white.withOpacity(0.7), ), ), ), ), ); }, ), loading: () => const Center(child: CircularProgressIndicator(strokeWidth: 2)), error: (e, _) => Center(child: Text('加载分类失败', style: TextStyle(color: Colors.white.withOpacity(0.5)))), ), ), // 2. Grid Layout Expanded( child: filteredCharactersAsync.when( data: (characters) => characters.isEmpty ? Center(child: Text('暂无匹配角色', style: TextStyle(color: Colors.white.withOpacity(0.5)))) : GridView.builder( padding: EdgeInsets.fromLTRB(16, 16, 16, bottomNavHeight + 20), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 3 / 4, crossAxisSpacing: 16, mainAxisSpacing: 16, ), itemCount: characters.length, itemBuilder: (context, index) { final char = characters[index]; return _CharacterCard( character: char, onTap: () { if (!char.isLocked) { context.push('/interaction/${char.id}'); } }, ) .animate() .fadeIn(duration: 400.ms, delay: (index * 100).ms) .scale(begin: const Offset(0.9, 0.9)); }, ), loading: () => const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(strokeWidth: 2), SizedBox(height: 16), Text('正在加载角色...', style: TextStyle(color: Colors.white54)), ], ), ), error: (e, _) => Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(LucideIcons.alertCircle, color: Colors.red.withOpacity(0.7), size: 48), const SizedBox(height: 16), Text('加载失败', style: TextStyle(color: Colors.white.withOpacity(0.7))), const SizedBox(height: 8), Text(e.toString(), style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 12)), const SizedBox(height: 16), ElevatedButton( onPressed: () => ref.invalidate(charactersProvider), child: const Text('重试'), ), ], ), ), ), ), ], ), ); } } class _CharacterCard extends StatelessWidget { final CharacterModel character; final VoidCallback onTap; const _CharacterCard({ required this.character, required this.onTap, }); @override Widget build(BuildContext context) { // 获取头像 URL final avatarUrl = CharacterRepository.getAvatarUrl(character.avatarPath); return GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 6), ), BoxShadow( color: const Color(0xFFA855F7).withOpacity(0.1), blurRadius: 20, offset: const Offset(0, 0), ), ], ), child: Stack( fit: StackFit.expand, children: [ // Clipped content area ClipRRect( borderRadius: BorderRadius.circular(24), child: Stack( fit: StackFit.expand, children: [ // Background Image Image.network( avatarUrl, fit: BoxFit.cover, loadingBuilder: (context, child, loadingProgress) { if (loadingProgress == null) return child; return Container( color: Colors.black26, child: const Center(child: CircularProgressIndicator(strokeWidth: 2)), ); }, errorBuilder: (context, error, stackTrace) { return Container( color: const Color(0xFF2E1065), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.user, size: 48, color: Colors.white.withOpacity(0.3)), const SizedBox(height: 8), Text( character.name, style: TextStyle(color: Colors.white.withOpacity(0.5), fontSize: 12), ), ], ), ); }, ), // Gradient Overlay Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.transparent, Color(0xFF2E1065), ], stops: [0.0, 0.5, 1.0], ), ), ), // Top Right: Lock Icon if (character.isLocked) Positioned( top: 12, right: 12, child: Container( width: 32, height: 32, decoration: BoxDecoration( color: Colors.black.withOpacity(0.4), shape: BoxShape.circle, border: Border.all(color: Colors.white.withOpacity(0.2)), ), child: const Icon(LucideIcons.lock, size: 14, color: Colors.white70), ), ), // Bottom Content Positioned( bottom: 0, left: 0, right: 0, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( character.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, shadows: [Shadow(color: Colors.black45, blurRadius: 2, offset: Offset(0, 1))], ), ), const SizedBox(height: 4), Wrap( spacing: 4, runSpacing: 4, children: character.tags.take(2).map((tag) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white.withOpacity(0.12), borderRadius: BorderRadius.circular(10), border: Border.all( color: Colors.white.withOpacity(0.25), width: 0.8, ), ), child: Text( tag.name, style: const TextStyle(fontSize: 10, color: Colors.white), ), ); }).toList(), ), ], ), ), ), ], ), ), // Border Overlay Positioned.fill( child: IgnorePointer( child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), border: Border.all( color: Colors.white.withOpacity(0.15), width: 1.2, ), ), ), ), ), ], ), ), ); } }