match-three/lib/screens/level_selection_screen.dart
savinmax 3f12ce8d3f Add dynamic grid sizing support for levels
- Add gridWidth and gridHeight properties to level configuration
- Update GameGrid to accept custom dimensions instead of using constants
- Modify GridComponent to calculate gem size based on grid dimensions
- Update MatchThreeGame constructor to pass grid dimensions
- Ensure proper scaling and positioning for variable grid sizes
2025-09-21 18:06:00 +02:00

317 lines
8.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/game_bloc.dart';
import '../services/level_service.dart';
import '../game/models/level.dart';
import 'game_screen.dart';
class LevelSelectionScreen extends StatefulWidget {
const LevelSelectionScreen({super.key});
@override
State<LevelSelectionScreen> createState() => _LevelSelectionScreenState();
}
class _LevelSelectionScreenState extends State<LevelSelectionScreen> {
List<LevelWithProgress>? _levelsWithProgress;
bool _isLoading = true;
String? _error;
@override
void initState() {
super.initState();
_loadLevels();
}
Future<void> _loadLevels() async {
try {
final levelsWithProgress =
await LevelService.instance.getLevelsWithProgress();
setState(() {
_levelsWithProgress = levelsWithProgress;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.purple, Colors.deepPurple],
),
),
child: SafeArea(
child: Column(
children: [
// Header
Padding(
padding: const EdgeInsets.all(20.0),
child: Row(
children: [
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back,
color: Colors.white, size: 30),
),
const Expanded(
child: Text(
'Select Level',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
const SizedBox(width: 50), // Balance the back button
],
),
),
// Content
Expanded(
child: _buildContent(),
),
],
),
),
),
);
}
Widget _buildContent() {
if (_isLoading) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
}
if (_error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.white, size: 64),
const SizedBox(height: 16),
const Text(
'Error loading levels',
style: const TextStyle(color: Colors.white, fontSize: 18),
),
const SizedBox(height: 8),
Text(
_error!,
style: const TextStyle(color: Colors.white70, fontSize: 14),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {
setState(() {
_isLoading = true;
_error = null;
});
_loadLevels();
},
child: const Text('Retry'),
),
],
),
);
}
if (_levelsWithProgress == null || _levelsWithProgress!.isEmpty) {
return const Center(
child: Text(
'No levels available',
style: TextStyle(color: Colors.white, fontSize: 18),
),
);
}
return Padding(
padding: const EdgeInsets.all(20.0),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.8,
),
itemCount: _levelsWithProgress!.length,
itemBuilder: (context, index) {
final levelWithProgress = _levelsWithProgress![index];
return _buildLevelCard(levelWithProgress);
},
),
);
}
Widget _buildLevelCard(LevelWithProgress levelWithProgress) {
final level = levelWithProgress.level;
final progress = levelWithProgress.progress;
final isUnlocked = levelWithProgress.isUnlocked;
final isCompleted = levelWithProgress.isCompleted;
return GestureDetector(
onTap: isUnlocked ? () => _startLevel(level) : null,
child: Container(
decoration: BoxDecoration(
color: isUnlocked ? Colors.white : Colors.grey.shade400,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Level number
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: isUnlocked
? (isCompleted ? Colors.green : Colors.blue)
: Colors.grey,
shape: BoxShape.circle,
),
child: Center(
child: isUnlocked
? Text(
'${level.id}',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
)
: const Icon(
Icons.lock,
color: Colors.white,
size: 24,
),
),
),
const SizedBox(height: 8),
// Level name
Text(
level.name,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: isUnlocked ? Colors.black87 : Colors.grey.shade600,
),
textAlign: TextAlign.center,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
// Stars (if completed)
if (isCompleted) ...[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (starIndex) {
return Icon(
starIndex < progress.stars ? Icons.star : Icons.star_border,
color:
starIndex < progress.stars ? Colors.amber : Colors.grey,
size: 16,
);
}),
),
const SizedBox(height: 4),
Text(
'Best: ${progress.bestScore}',
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade600,
),
),
] else if (isUnlocked) ...[
// Level constraints info
_buildConstraintInfo(level),
],
],
),
),
);
}
Widget _buildConstraintInfo(Level level) {
final constraints = <String>[];
if (level.constraints.hasScoreTarget) {
constraints.add('${level.constraints.targetScore!} pts');
}
if (level.constraints.hasMoveLimit) {
constraints.add('${level.constraints.maxMoves!} moves');
}
if (level.constraints.hasTimeLimit) {
constraints.add('${level.constraints.timeLimit!}s');
}
if (level.objectives.hasGemTypeObjectives) {
constraints.add('Clear gems');
}
return Column(
children: constraints
.take(2)
.map(
(constraint) => Text(
constraint,
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade600,
),
),
)
.toList(),
);
}
void _startLevel(Level level) {
// Start the level first
print("Starting Level ${level.id}");
// Then navigate to game screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: context.read<GameBloc>(),
child: GameScreen(
levelId: level.id,
gridHeight: level.gridHeight,
gridWidth: level.gridWidth,
),
),
),
).then((_) {
// Refresh levels when returning from game
_loadLevels();
});
}
}