326 lines
12 KiB
Dart
326 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_animate/flutter_animate.dart';
|
|
import 'package:lucide_icons/lucide_icons.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../data/mock_data.dart';
|
|
import '../../models/scenario.dart';
|
|
import '../../widgets/tab_content_layout.dart';
|
|
|
|
class LibraryScreen extends StatefulWidget {
|
|
const LibraryScreen({super.key});
|
|
|
|
@override
|
|
State<LibraryScreen> createState() => _LibraryScreenState();
|
|
}
|
|
|
|
class _LibraryScreenState extends State<LibraryScreen> {
|
|
String _activeCategory = '全部';
|
|
|
|
final List<String> _categories = ['全部', '职场', '邻家', '科幻', 'ASMR'];
|
|
|
|
List<Scenario> get _filteredScenarios {
|
|
if (_activeCategory == '全部') return mockScenarios;
|
|
return mockScenarios.where((s) =>
|
|
s.category == _activeCategory || s.tags.contains(_activeCategory)
|
|
).toList();
|
|
}
|
|
|
|
Color _getIntensityColor(String intensity) {
|
|
switch (intensity) {
|
|
case 'Low':
|
|
return const Color(0xFF34D399); // Emerald
|
|
case 'Medium':
|
|
return const Color(0xFF60A5FA); // Blue
|
|
case 'High':
|
|
return const Color(0xFFFBBF24); // Amber
|
|
case 'Extreme':
|
|
return const Color(0xFFF472B6); // Pink
|
|
default:
|
|
return Colors.white.withOpacity(0.5);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const double bottomNavHeight = 90;
|
|
|
|
return TabContentLayout(
|
|
child: Column(
|
|
children: [
|
|
// Filter Bar
|
|
SizedBox(
|
|
height: 50,
|
|
child: 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 = _activeCategory == category;
|
|
return Center(
|
|
child: GestureDetector(
|
|
onTap: () => setState(() => _activeCategory = category),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
gradient: isActive
|
|
? const LinearGradient(
|
|
colors: [Color(0xFFC084FC), Color(0xFFF472B6)],
|
|
)
|
|
: null,
|
|
color: isActive ? null : Colors.white.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: isActive ? Colors.transparent : Colors.white.withOpacity(0.1),
|
|
),
|
|
boxShadow: isActive
|
|
? [
|
|
BoxShadow(
|
|
color: const Color(0xFFC084FC).withOpacity(0.4),
|
|
blurRadius: 15,
|
|
),
|
|
]
|
|
: null,
|
|
),
|
|
child: Text(
|
|
category,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
|
|
color: isActive ? Colors.white : Colors.white.withOpacity(0.7),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// Scenario List
|
|
Expanded(
|
|
child: _filteredScenarios.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(LucideIcons.activity, size: 32, color: Colors.white.withOpacity(0.5)),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'暂无相关剧本',
|
|
style: TextStyle(fontSize: 12, color: Colors.white.withOpacity(0.5)),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
padding: EdgeInsets.fromLTRB(16, 8, 16, bottomNavHeight + 20),
|
|
itemCount: _filteredScenarios.length,
|
|
itemBuilder: (context, index) {
|
|
final scenario = _filteredScenarios[index];
|
|
return _ScenarioCard(
|
|
scenario: scenario,
|
|
intensityColor: _getIntensityColor(scenario.intensity),
|
|
onTap: () {
|
|
if (!scenario.isLocked) {
|
|
context.push('/player/${scenario.id}');
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('需要积分解锁此高级内容'),
|
|
backgroundColor: Color(0xFF4C1D95),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
)
|
|
.animate()
|
|
.fadeIn(duration: 400.ms, delay: (index * 80).ms)
|
|
.slideX(begin: 0.05, end: 0);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ScenarioCard extends StatelessWidget {
|
|
final Scenario scenario;
|
|
final Color intensityColor;
|
|
final VoidCallback onTap;
|
|
|
|
const _ScenarioCard({
|
|
required this.scenario,
|
|
required this.intensityColor,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF4C1D95).withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
// Left: Thumbnail
|
|
Container(
|
|
width: 80,
|
|
height: 80,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.3),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
Image.network(
|
|
scenario.cover,
|
|
fit: BoxFit.cover,
|
|
loadingBuilder: (context, child, loadingProgress) {
|
|
if (loadingProgress == null) return child;
|
|
return Container(
|
|
color: Colors.black26,
|
|
child: const Center(
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
if (scenario.isLocked)
|
|
Container(
|
|
color: const Color(0xFF2E1065).withOpacity(0.6),
|
|
child: const Center(
|
|
child: Icon(LucideIcons.lock, size: 16, color: Colors.white70),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
|
|
// Center: Info
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// Category & Intensity
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(4),
|
|
border: Border.all(color: Colors.white.withOpacity(0.1)),
|
|
),
|
|
child: Text(
|
|
scenario.category,
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
fontFamily: 'monospace',
|
|
letterSpacing: 1,
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Icon(LucideIcons.zap, size: 10, color: intensityColor),
|
|
const SizedBox(width: 2),
|
|
Text(
|
|
scenario.intensity,
|
|
style: TextStyle(
|
|
fontSize: 9,
|
|
fontWeight: FontWeight.bold,
|
|
color: intensityColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 6),
|
|
|
|
// Title
|
|
Text(
|
|
scenario.title,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
const SizedBox(height: 6),
|
|
|
|
// Tags
|
|
Wrap(
|
|
spacing: 6,
|
|
children: scenario.tags.take(3).map((tag) {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.05),
|
|
borderRadius: BorderRadius.circular(4),
|
|
border: Border.all(color: Colors.white.withOpacity(0.05)),
|
|
),
|
|
child: Text(
|
|
'#$tag',
|
|
style: TextStyle(
|
|
fontSize: 10,
|
|
color: Colors.white.withOpacity(0.7),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Right: Play Button
|
|
Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: scenario.isLocked
|
|
? Colors.white.withOpacity(0.05)
|
|
: const Color(0xFFC084FC).withOpacity(0.2),
|
|
),
|
|
child: Center(
|
|
child: Icon(
|
|
scenario.isLocked ? LucideIcons.lock : LucideIcons.play,
|
|
size: scenario.isLocked ? 16 : 18,
|
|
color: scenario.isLocked
|
|
? Colors.white.withOpacity(0.3)
|
|
: const Color(0xFFC084FC),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|