import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:aesthetica_wallpaper/models/drag_puzzle_game.dart'; import 'package:aesthetica_wallpaper/providers/drag_puzzle_provider.dart'; import 'package:aesthetica_wallpaper/screens/puzzle/puzzle_complete_screen.dart'; /// 拖拽式拼图游戏界面 class DragPuzzleScreen extends StatefulWidget { final String imagePath; final DragPuzzleDifficulty difficulty; const DragPuzzleScreen({ super.key, required this.imagePath, required this.difficulty, }); @override State createState() => _DragPuzzleScreenState(); } class _DragPuzzleScreenState extends State { late DragPuzzleProvider _puzzleProvider; @override void initState() { super.initState(); _puzzleProvider = DragPuzzleProvider(); // 创建游戏 WidgetsBinding.instance.addPostFrameCallback((_) { _puzzleProvider.createDragPuzzle( imagePath: widget.imagePath, difficulty: widget.difficulty, ); }); } @override void dispose() { _puzzleProvider.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, body: SafeArea( child: ChangeNotifierProvider.value( value: _puzzleProvider, child: Consumer( builder: (context, provider, child) { final game = provider.currentGame; if (game == null) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(color: Colors.white), SizedBox(height: 16), Text( 'Preparing puzzle...', style: TextStyle(color: Colors.white), ), ], ), ); } // 游戏完成后跳转 if (game.isComplete) { WidgetsBinding.instance.addPostFrameCallback((_) { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => PuzzleCompleteScreen(game: game), ), ); }); } return Column( children: [ // 顶部信息栏 _buildTopBar(game), // 拼图区域(大幅增加) Expanded( flex: 6, // 大幅增加拼图区域 child: _buildPuzzleArea(game, provider), ), // 分隔线 Container( height: 2, color: Colors.grey.shade300, margin: const EdgeInsets.symmetric(horizontal: 16), ), // 拼图块区域(单行横向排列) Container( height: 100, // 固定高度,单行显示 child: _buildPiecesArea(game, provider), ), // 底部工具栏(简化) _buildBottomBar(game, provider), ], ); }, ), ), ), ); } Widget _buildTopBar(DragPuzzleGame game) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.shade900, border: Border( bottom: BorderSide(color: Colors.grey.shade800, width: 1), ), ), child: Row( children: [ // 返回按钮 IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), const Spacer(), // 进度显示 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.blue.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.blue.withValues(alpha: 0.5)), ), child: Text( '${game.placedPieces.length}/${game.pieces.length}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ), const SizedBox(width: 8), // 计时器 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.green.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.green.withValues(alpha: 0.5)), ), child: Row( children: [ const Icon(Icons.timer, size: 16, color: Colors.white), const SizedBox(width: 4), Text( _formatDuration(game.elapsedTime), style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ), const SizedBox(width: 8), // 移动次数 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.orange.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(20), border: Border.all(color: Colors.orange.withValues(alpha: 0.5)), ), child: Row( children: [ const Icon(Icons.touch_app, size: 16, color: Colors.white), const SizedBox(width: 4), Text( '${game.moves}', style: const TextStyle( fontWeight: FontWeight.bold, color: Colors.white, ), ), ], ), ), ], ), ); } Widget _buildPuzzleArea(DragPuzzleGame game, DragPuzzleProvider provider) { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey.shade900, borderRadius: BorderRadius.circular(12), ), child: _buildPuzzleGrid(game, provider), // 直接显示拼图网格 ); } Widget _buildPuzzleGrid(DragPuzzleGame game, DragPuzzleProvider provider) { return LayoutBuilder( builder: (context, constraints) { // 使用可用的最大空间 final maxWidth = constraints.maxWidth; final maxHeight = constraints.maxHeight; // 计算合适的网格大小 // 对于3行2列,整体比例应该是 (2/3) * (9/16) = 3/8 = 0.375 // 即宽度是高度的0.375倍 final gridAspectRatio = (game.gridCols / game.gridRows) * (9.0 / 16.0); double gridWidth, gridHeight; // 根据可用空间计算网格尺寸 if (maxWidth / maxHeight < gridAspectRatio) { // 宽度受限 gridWidth = maxWidth; gridHeight = gridWidth / gridAspectRatio; } else { // 高度受限 gridHeight = maxHeight; gridWidth = gridHeight * gridAspectRatio; } // 每个格子的宽高比(单个格子是9:16) final cellAspectRatio = 9.0 / 16.0; return Center( child: Container( width: gridWidth, height: gridHeight, decoration: BoxDecoration( color: Colors.grey.shade800, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.5), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(12), child: GridView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: game.gridCols, childAspectRatio: cellAspectRatio, mainAxisSpacing: 1, crossAxisSpacing: 1, ), itemCount: game.gridRows * game.gridCols, itemBuilder: (context, index) { final row = index ~/ game.gridCols; final col = index % game.gridCols; return _buildDropTarget(game, provider, row, col); }, ), ), ), ); }, ); } Widget _buildDropTarget( DragPuzzleGame game, DragPuzzleProvider provider, int row, int col, ) { final piece = game.getPieceAt(row, col); final isCorrectPosition = piece?.isCorrectlyPlaced ?? false; return DragTarget( onAccept: (draggedPiece) { provider.placePiece(draggedPiece.id, row, col); }, builder: (context, candidateData, rejectedData) { return Container( margin: const EdgeInsets.all(1), decoration: BoxDecoration( color: candidateData.isNotEmpty ? Colors.blue.withValues(alpha: 0.3) : Colors.grey.shade700, border: Border.all( color: isCorrectPosition ? Colors.green : Colors.grey.shade600, width: isCorrectPosition ? 2 : 1, ), ), child: piece != null ? GestureDetector( onTap: () => provider.removePiece(piece.id), child: Stack( fit: StackFit.expand, children: [ Image(image: piece.image, fit: BoxFit.cover), if (isCorrectPosition) Positioned( top: 4, right: 4, child: Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( color: Colors.green, shape: BoxShape.circle, ), child: const Icon( Icons.check, color: Colors.white, size: 12, ), ), ), ], ), ) : Center( child: Text( '${row * game.gridCols + col + 1}', style: TextStyle(color: Colors.grey.shade500, fontSize: 12), ), ), ); }, ); } Widget _buildPiecesArea(DragPuzzleGame game, DragPuzzleProvider provider) { final unplacedPieces = game.unplacedPieces; return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.grey.shade900, border: Border(top: BorderSide(color: Colors.grey.shade800, width: 1)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Pieces (${unplacedPieces.length})', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 8), Expanded( child: unplacedPieces.isEmpty ? const Center( child: Text( 'All pieces placed!', style: TextStyle(color: Colors.grey, fontSize: 14), ), ) : ListView.builder( scrollDirection: Axis.horizontal, itemCount: unplacedPieces.length, itemBuilder: (context, index) { final piece = unplacedPieces[index]; return Container( width: 60, height: 60, margin: const EdgeInsets.only(right: 8), child: _buildDraggablePiece(piece), ); }, ), ), ], ), ); } Widget _buildDraggablePiece(DragPuzzlePiece piece) { return Draggable( data: piece, feedback: Material( elevation: 8, borderRadius: BorderRadius.circular(8), child: Container( width: 80, height: 80, decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image(image: piece.image, fit: BoxFit.cover), ), ), ), childWhenDragging: Container( decoration: BoxDecoration( color: Colors.grey.shade800, borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.grey.shade700, style: BorderStyle.solid, ), ), child: const Center( child: Icon(Icons.drag_indicator, color: Colors.grey), ), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image(image: piece.image, fit: BoxFit.cover), ), ), ); } Widget _buildBottomBar(DragPuzzleGame game, DragPuzzleProvider provider) { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.grey.shade900, border: Border(top: BorderSide(color: Colors.grey.shade800, width: 1)), ), child: Row( children: [ // 重新开始 _buildToolButton( icon: Icons.refresh, label: 'Restart', color: Colors.orange, onPressed: () => _showRestartDialog(provider), ), const Spacer(), // 预览图(右下角) GestureDetector( onTap: () => _showPreviewDialog(game), child: Container( width: 60, height: 60, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.blue, width: 2), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(6), child: Stack( fit: StackFit.expand, children: [ Image.asset(game.imagePath, fit: BoxFit.cover), Container( color: Colors.black.withValues(alpha: 0.3), child: const Icon( Icons.search, color: Colors.white, size: 24, ), ), ], ), ), ), ), ], ), ); } Widget _buildToolButton({ required IconData icon, required String label, required Color color, required VoidCallback onPressed, }) { return Column( mainAxisSize: MainAxisSize.min, children: [ Container( decoration: BoxDecoration( color: color.withValues(alpha: 0.3), shape: BoxShape.circle, border: Border.all(color: color.withValues(alpha: 0.5)), ), child: IconButton( icon: Icon(icon, color: Colors.white), onPressed: onPressed, ), ), const SizedBox(height: 4), Text( label, style: TextStyle(fontSize: 12, color: Colors.grey.shade400), ), ], ); } void _showRestartDialog(DragPuzzleProvider provider) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Restart Game'), content: const Text( 'Are you sure you want to restart? Current progress will be lost.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Cancel'), ), TextButton( onPressed: () { provider.restartGame(); Navigator.pop(context); }, child: const Text('Restart'), ), ], ), ); } void _showPreviewDialog(DragPuzzleGame game) { showDialog( context: context, builder: (context) => Dialog( child: Container( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Preview', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), Image.asset(game.imagePath, fit: BoxFit.contain, height: 300), const SizedBox(height: 16), TextButton( onPressed: () => Navigator.pop(context), child: const Text('Close'), ), ], ), ), ), ); } String _formatDuration(Duration duration) { final minutes = duration.inMinutes.toString().padLeft(2, '0'); final seconds = (duration.inSeconds % 60).toString().padLeft(2, '0'); return '$minutes:$seconds'; } }