Files
SDKmuta/lib/screens/book_screen.dart
T
2025-10-16 17:42:46 +02:00

1083 lines
38 KiB
Dart

import 'dart:io';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; // For DateFormat
import 'package:path_provider/path_provider.dart'; // For getApplicationDocumentsDirectory
import 'target_view.dart'; // Import the TargetViewScreen
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
class BookScreen extends StatefulWidget {
const BookScreen({super.key});
@override
State<BookScreen> createState() => _BookScreenState();
}
class _BookScreenState extends State<BookScreen> with WidgetsBindingObserver {
Map<String, List<File>> _groupedFiles = {}; // Files grouped by folder
Map<String, DateTime> _folderCreationDates = {}; // Folder creation dates
Map<String, int> _folderScores = {}; // Folder scores (sum of scores of all target files inside)
Color _bulletColor = Colors.blue; // default fallback
Map<String, bool> _folderTypes = {}; // true = tournament, false = training
String _folderFilter = 'all';
bool isRenameEnabled = false;
String _sortMode = 'date'; // or 'score'
String _selectedLanguage = 'English';
String? _expandedFolder; // Track which folder is expanded
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // Register to listen for lifecycle events
_loadBulletColor();
_loadLanguage();
_loadTargetDataFiles(); // Initial load when the screen is first created
}
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this); // Unregister from lifecycle events when disposed
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed) {
// Reload data when the screen is resumed or comes into view
_loadTargetDataFiles();
}
}
Future<void> _loadBulletColor() async {
final prefs = await SharedPreferences.getInstance();
final selected = prefs.getString('bulletColor') ?? 'Blue';
setState(() {
_bulletColor = _getColorFromName(selected);
});
}
Color _getColorFromName(String colorName) {
switch (colorName.toLowerCase()) {
case 'blue':
return Colors.blue;
case 'green':
return Colors.green;
case 'yellow':
return Colors.yellow;
case 'purple':
return Colors.purple;
case 'cyan':
return Colors.cyan;
case 'teal':
return Colors.teal;
case 'indigo':
return Colors.indigo;
case 'pink':
return Colors.pink;
case 'lime':
return Colors.lime;
case 'mint':
return Colors.greenAccent;
case 'orange':
return Colors.orange;
case 'violet':
return Colors.deepPurpleAccent;
case 'amber':
return Colors.amber;
case 'brown':
return Colors.brown;
case 'grey':
return Colors.grey;
default:
return Colors.blue; // fallback
}
}
// Load the target data files from the directory
void _loadTargetDataFiles() async {
try {
final directory = await getApplicationDocumentsDirectory();
final dataDirectory = Directory('${directory.path}/saved_images');
if (!await dataDirectory.exists()) {
await dataDirectory.create(recursive: true);
}
List<FileSystemEntity> folders = dataDirectory.listSync().whereType<Directory>().toList();
Map<String, List<File>> groupedFiles = {};
Map<String, DateTime> folderDates = {};
Map<String, int> folderScores = {};
Map<String, bool> folderTypes = {};
for (var folder in folders) {
String folderName = folder.path.split('/').last;
DateTime fallbackDate = folder.statSync().changed; // fallback
// Read metadata
final metadataFile = File('${folder.path}/metadata.json');
bool isTournament = false;
DateTime? createdAt;
if (await metadataFile.exists()) {
try {
final metadata = jsonDecode(await metadataFile.readAsString());
isTournament = metadata['isTournament'] == true;
if (metadata['createdAt'] != null) {
createdAt = DateTime.tryParse(metadata['createdAt']);
}
} catch (e) {
debugPrint("Error reading metadata for $folderName: $e");
}
}
// Use createdAt if available, fallback otherwise
folderDates[folderName] = createdAt ?? fallbackDate;
folderTypes[folderName] = isTournament;
// List target files
List<File> targetFiles = Directory(folder.path)
.listSync()
.whereType<File>()
.where((file) =>
file.path.endsWith(".json") &&
!file.path.endsWith("metadata.json"))
.toList();
int totalScore = 0;
for (var file in targetFiles) {
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString);
totalScore += _getScoreFromJson(data);
}
groupedFiles[folderName] = targetFiles;
folderScores[folderName] = totalScore;
}
// Sort folders by stored or fallback created date (DESC)
final sortedFolderNames = folderDates.keys.toList()
..sort((a, b) => folderDates[b]!.compareTo(folderDates[a]!));
setState(() {
_groupedFiles = {
for (var name in sortedFolderNames) name: groupedFiles[name]!
};
_folderCreationDates = {
for (var name in sortedFolderNames) name: folderDates[name]!
};
_folderScores = {
for (var name in sortedFolderNames) name: folderScores[name]!
};
_folderTypes = {
for (var name in sortedFolderNames) name: folderTypes[name]!
};
});
} catch (e) {
debugPrint("Error loading target data files: $e");
}
}
// Function to safely get the score from the JSON data
int _getScoreFromJson(Map<String, dynamic> data) {
if (data.containsKey('totalScore') && data['totalScore'] is int) {
return data['totalScore'];
}
return 0; // Default to 0 if the score is not present or invalid
}
// Delete a file
Future<void> _deleteFile(File file) async {
bool? confirmDeletion = await _showConfirmationDialog(
context,
_t('Are you sure you want to delete this file?'),
);
if (confirmDeletion == true) {
try {
await file.delete();
_loadTargetDataFiles(); // Refresh after deleting the file
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Error deleting file: $e")));
}
}
}
// Open file preview in the TargetViewScreen
void _openFilePreview(File targetFile) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TargetViewScreen(
jsonFiles: [targetFile], // Pass the selected file in a list
refreshFolders: _loadTargetDataFiles, // Pass the refreshFolders callback
),
),
);
}
// Open folder's combined target view in the TargetViewScreen
void _openFolderTargetView(String folderName) async {
final targetFiles = _groupedFiles[folderName] ?? [];
List<Map<String, dynamic>> allBullets = [];
String latestDate = 'Unknown';
for (var file in targetFiles) {
final jsonString = await file.readAsString();
final data = jsonDecode(jsonString);
final bullets = (data['bullets'] as List)
.map((b) => {'x': b['x'], 'y': b['y']})
.toList();
allBullets.addAll(bullets);
if (data['date'] != null) {
latestDate = data['date']; // You could sort if you want the most recent
}
}
// Create combined JSON structure
final combinedJson = jsonEncode({
'date': latestDate,
'bullets': allBullets,
});
// Save this JSON temporarily to disk
final tempDir = await getTemporaryDirectory();
final combinedFile = File('${tempDir.path}/combined_${folderName}.json');
await combinedFile.writeAsString(combinedJson);
// Navigate to view screen with the combined file
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TargetViewScreen(
jsonFiles: [combinedFile], // treat it like a normal file
refreshFolders: _loadTargetDataFiles,
isCombinedView: true, // 👈 This line
),
),
);
}
List<String> _filteredFolderNames() {
final List<String> folders = _groupedFiles.keys.toList();
if (_folderFilter != 'all') {
final isTournament = _folderFilter == 'tournament';
folders.retainWhere((folderName) => _folderTypes[folderName] == isTournament);
}
// 🔽 Sort using `createdAt` from _folderCreationDates (which is now based on metadata.json)
// 🔽 Apply sorting based on selected sort mode
folders.sort((a, b) {
if (_sortMode == 'score') {
final scoreA = _folderScores[a] ?? 0;
final scoreB = _folderScores[b] ?? 0;
return scoreB.compareTo(scoreA); // High score first
} else {
final dateA = _folderCreationDates[a];
final dateB = _folderCreationDates[b];
if (dateA == null || dateB == null) return 0;
return dateB.compareTo(dateA); // Newest first
}
});
return folders;
}
Future<void> _renameFolder(String oldFolderName) async {
final newNameController = TextEditingController();
bool isTournament = _folderTypes[oldFolderName] == true;
final result = await showDialog<Map<String, dynamic>>(
context: context,
builder: (context) {
return Dialog(
backgroundColor: Colors.grey[900],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: StatefulBuilder(
builder: (context, setState) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
_t('Folder Settings'),
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
TextField(
controller: newNameController,
textAlign: TextAlign.center,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
filled: true,
fillColor: Colors.grey[800],
hintText: _t('Enter new folder name'),
hintStyle: const TextStyle(color: Colors.white54),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: _bulletColor),
),
),
onChanged: (value) {
setState(() {
isRenameEnabled = value.trim().isNotEmpty;
});
},
),
const SizedBox(height: 16),
GestureDetector(
onTap: () {
setState(() {
isTournament = !isTournament;
});
},
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: isTournament ? _bulletColor.withOpacity(0.2) : Colors.grey[850],
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isTournament ? _bulletColor : Colors.grey[700]!,
width: 1.5,
),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Center(
child: Text(
isTournament ? _t('Tournament Mode 🏆') : _t('Mark as Tournament 🏆'),
style: TextStyle(
color: isTournament ? _bulletColor : Colors.white70,
fontWeight: FontWeight.w500,
fontSize: 16,
),
),
),
),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(_t('Cancel')),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop({
'name': newNameController.text.trim(),
'isTournament': isTournament,
}),
style: ElevatedButton.styleFrom(
backgroundColor: _bulletColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(_t('Apply')),
),
),
],
)
],
);
},
),
),
);
},
);
// ✅ Handle result (rename + tag update)
if (result != null) {
final newName = result['name'];
final isTournament = result['isTournament'] ?? false;
final directory = await getApplicationDocumentsDirectory();
final oldFolderPath = Directory('${directory.path}/saved_images/$oldFolderName');
final targetFolderName = (newName != null && newName.isNotEmpty) ? newName : oldFolderName;
final newFolderPath = Directory('${directory.path}/saved_images/$targetFolderName');
if (targetFolderName != oldFolderName) {
if (await newFolderPath.exists()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('A folder named "$targetFolderName" already exists.')),
);
return;
} else {
await oldFolderPath.rename(newFolderPath.path);
}
}
// ✅ Preserve existing createdAt value or set it if missing
final metadataFile = File('${newFolderPath.path}/metadata.json');
String? createdAt;
if (await metadataFile.exists()) {
try {
final existing = jsonDecode(await metadataFile.readAsString());
createdAt = existing['createdAt'];
} catch (_) {}
}
createdAt ??= DateTime.now().toIso8601String(); // fallback if not found
final updatedMetadata = {
'isTournament': isTournament,
'createdAt': createdAt,
};
await metadataFile.writeAsString(jsonEncode(updatedMetadata));
_loadTargetDataFiles(); // Refresh UI
}
}
// Delete a folder
Future<void> _deleteFolder(String folderName) async {
bool? confirmDeletion = await _showConfirmationDialog(
context,
'${_t('Are you sure you want to delete the folder')} "$folderName" ${_t('and all of its contents?')}',
);
if (confirmDeletion == true) {
try {
final directory = await getApplicationDocumentsDirectory();
final folderPath = Directory('${directory.path}/saved_images/$folderName');
if (await folderPath.exists()) {
await folderPath.delete(recursive: true);
}
_loadTargetDataFiles(); // Refresh file/folder list
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Folder "$folderName" deleted successfully.'),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error deleting folder: $e")),
);
}
}
}
Future<bool?> _showConfirmationDialog(BuildContext context, String message) async {
return showDialog<bool>(
context: context,
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.grey[900],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
_t('Confirm Deletion'),
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
message,
style: const TextStyle(color: Colors.white70, fontSize: 16),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// "Yes" Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(_t("Yes, delete")),
),
),
const SizedBox(height: 12),
// "No" Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(false),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(_t("Cancel")),
),
),
],
),
),
);
},
);
}
Widget _buildFolderRow(String folderName) {
final numberOfTargets = _groupedFiles[folderName]!.length;
final totalScore = _folderScores[folderName];
final folderDate = _folderCreationDates[folderName];
final formattedDate = folderDate != null
? DateFormat('dd/MM/yyyy').format(folderDate)
: 'Unknown Date';
return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
// Header section: Name and Date
InkWell(
onTap: () {
setState(() {
_expandedFolder = _expandedFolder == folderName ? null : folderName;
});
},
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// Small colored indicator
Container(
width: 4,
height: 20,
decoration: BoxDecoration(
color: _bulletColor,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
_folderTypes[folderName] == true
? '🏆 $folderName'
: folderName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
],
),
Row(
children: [
Text(
"📅 $formattedDate",
style: const TextStyle(fontSize: 13, color: Colors.black87),
),
const SizedBox(width: 8),
Icon(
_expandedFolder == folderName ? Icons.expand_less : Icons.expand_more,
color: Colors.black87,
),
],
),
],
),
),
),
// Expandable targets list (in the middle)
if (_expandedFolder == folderName) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
),
child: _buildTargetsList(folderName),
),
],
// Bottom section: Info badge and action buttons
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Info badge section (non-clickable, matches button style)
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.only(
bottomLeft: const Radius.circular(12),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Targets count
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.filter_center_focus, size: 16, color: Colors.black87),
const SizedBox(width: 4),
Text(
"$numberOfTargets",
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87),
),
],
),
// Score
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.emoji_events, size: 16, color: Colors.amber),
const SizedBox(width: 4),
Text(
"$totalScore",
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87),
),
],
),
],
),
),
),
// Preview button section
InkWell(
onTap: () => _openFolderTargetView(folderName),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: _bulletColor.withValues(alpha: 0.2),
border: Border(
left: BorderSide(color: Colors.grey[400]!, width: 1),
),
),
child: Icon(Icons.visibility, color: _bulletColor, size: 20),
),
),
// Edit button section
InkWell(
onTap: () => _renameFolder(folderName),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: _bulletColor.withValues(alpha: 0.2),
border: Border(
left: BorderSide(color: Colors.grey[400]!, width: 1),
),
),
child: Icon(Icons.edit, color: _bulletColor, size: 20),
),
),
// Delete button section
InkWell(
onTap: () => _deleteFolder(folderName),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.2),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(12),
),
border: Border(
left: BorderSide(color: Colors.grey[400]!, width: 1),
),
),
child: const Icon(Icons.delete, color: Colors.red, size: 20),
),
),
],
),
),
],
),
);
}
Widget _buildTargetsList(String folderName) {
final targetFiles = _groupedFiles[folderName] ?? [];
if (targetFiles.isEmpty) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
_t("No targets in this folder"),
style: const TextStyle(color: Colors.black54, fontSize: 14),
textAlign: TextAlign.center,
),
);
}
return Column(
children: targetFiles.asMap().entries.map((entry) {
final index = entry.key;
final file = entry.value;
final score = _getScoreFromJson(jsonDecode(file.readAsStringSync()));
final fileName = "${_t('Target')} ${index + 1}";
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey[300]!, width: 1),
),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Target info section
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
children: [
Expanded(
child: Text(
fileName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
const SizedBox(width: 8),
const Icon(
Icons.emoji_events,
color: Colors.amber,
size: 14,
),
const SizedBox(width: 4),
Text(
"$score",
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
),
// Preview button
InkWell(
onTap: () => _openFilePreview(file),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: _bulletColor.withValues(alpha: 0.15),
border: Border(
left: BorderSide(color: Colors.grey[300]!, width: 1),
),
),
child: Icon(Icons.visibility, color: _bulletColor, size: 18),
),
),
// Delete button
InkWell(
onTap: () => _deleteFile(file),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.1),
border: Border(
left: BorderSide(color: Colors.grey[300]!, width: 1),
),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
child: const Icon(
Icons.delete,
color: Colors.red,
size: 18,
),
),
),
],
),
),
);
}).toList(),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// 🔽 Filter dropdown at the top
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: GestureDetector(
onTap: () => _showFolderFilterPopup(context),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: Colors.grey[300], // Same as folder background
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
_folderFilter == 'all'
? _t('Showing: 📂 All folders')
: _folderFilter == 'tournament'
? _t('Showing: 🏆 Tournament folders')
: _t('Showing: 🎯 Training folders'),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
const Icon(Icons.filter_list),
],
),
),
),
),
// 📁 Folder list or empty state
Expanded(
child: _groupedFiles.isEmpty
? Center(child: Text(_t("No saved target data found"), style: const TextStyle(fontSize: 18)))
: Scrollbar(
thumbVisibility: true, // always show scrollbar
thickness: 6, // optional: adjust width
radius: const Radius.circular(8),
child: ListView.builder(
itemCount: _filteredFolderNames().length,
itemBuilder: (context, index) {
String folderName = _filteredFolderNames()[index];
return _buildFolderRow(folderName);
},
),
),
),
],
),
);
}
void _showFolderFilterPopup(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, setState) {
return Dialog(
backgroundColor: Colors.grey[900],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_t('Filter Folders'),
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
// Folder type filters
_buildStyledFilterButton('all', _t('📂 All folders'), context),
const SizedBox(height: 12),
_buildStyledFilterButton('tournament', _t('🏆 Tournament'), context),
const SizedBox(height: 12),
_buildStyledFilterButton('training', _t('🎯 Training'), context),
const SizedBox(height: 24),
Text(
_t('Sort By'),
style: const TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w500),
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: GestureDetector(
onTap: () {
setState(() => _sortMode = 'date');
this.setState(() {}); // 👈 refresh parent widget
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
margin: const EdgeInsets.only(right: 6),
decoration: BoxDecoration(
color: _sortMode == 'date' ? _bulletColor : Colors.grey[800],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
_t('📅 Sort by Date'),
textAlign: TextAlign.center,
style: TextStyle(
color: _sortMode == 'date' ? Colors.white : Colors.white70,
fontWeight: FontWeight.w500,
fontSize: 14,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
),
),
Expanded(
child: GestureDetector(
onTap: () {
setState(() => _sortMode = 'score');
this.setState(() {}); // 👈 refresh parent widget
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
margin: const EdgeInsets.only(left: 6),
decoration: BoxDecoration(
color: _sortMode == 'score' ? _bulletColor : Colors.grey[800],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
_t('🏆 Sort by Score'),
textAlign: TextAlign.center,
style: TextStyle(
color: _sortMode == 'score' ? Colors.white : Colors.white70,
fontWeight: FontWeight.w500,
fontSize: 14,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
),
),
),
],
),
],
),
),
);
},
);
},
);
}
Widget _buildStyledFilterButton(String value, String label, BuildContext context) {
final isSelected = _folderFilter == value;
return SizedBox(
width: double.infinity,
child: GestureDetector(
onTap: () {
setState(() {
_folderFilter = value;
});
Navigator.of(context).pop();
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14),
decoration: BoxDecoration(
color: isSelected ? _bulletColor : Colors.grey[800],
borderRadius: BorderRadius.circular(12),
),
child: Center(
child: Text(
label,
style: TextStyle(
color: isSelected ? Colors.white : Colors.white70,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
),
),
);
}
}