diff --git a/lib/screens/book_screen.dart b/lib/screens/book_screen.dart index 6326eda..82c4ac7 100644 --- a/lib/screens/book_screen.dart +++ b/lib/screens/book_screen.dart @@ -100,67 +100,72 @@ Color _getColorFromName(String colorName) { Map> groupedFiles = {}; Map folderDates = {}; Map folderScores = {}; - Map folderTypes = {}; // ✅ + Map folderTypes = {}; for (var folder in folders) { String folderName = folder.path.split('/').last; - DateTime folderDate = folder.statSync().modified; + DateTime fallbackDate = folder.statSync().changed; // fallback - // ✅ Read isTournament flag - bool isTournament = false; + // 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 targetFiles = Directory(folder.path) .listSync() .whereType() .where((file) => - file.path.endsWith(".json") && - !file.path.endsWith("metadata.json")) + 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); - final score = _getScoreFromJson(data); - totalScore += score; + totalScore += _getScoreFromJson(data); } groupedFiles[folderName] = targetFiles; - folderDates[folderName] = folderDate; 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]!)); - final sortedGroupedFiles = { - for (var folderName in sortedFolderNames) folderName: groupedFiles[folderName]! - }; - final sortedFolderScores = { - for (var folderName in sortedFolderNames) folderName: folderScores[folderName]! - }; - final sortedFolderDates = { - for (var folderName in sortedFolderNames) folderName: folderDates[folderName]! - }; - final sortedFolderTypes = { - for (var folderName in sortedFolderNames) folderName: folderTypes[folderName]! - }; - setState(() { - _groupedFiles = sortedGroupedFiles; - _folderCreationDates = sortedFolderDates; - _folderScores = sortedFolderScores; - _folderTypes = sortedFolderTypes; // ✅ FIXED + _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"); @@ -168,6 +173,7 @@ Color _getColorFromName(String colorName) { } + // Function to safely get the score from the JSON data int _getScoreFromJson(Map data) { if (data.containsKey('totalScore') && data['totalScore'] is int) { @@ -208,64 +214,72 @@ Color _getColorFromName(String colorName) { // Open folder's combined target view in the TargetViewScreen void _openFolderTargetView(String folderName) async { - final targetFiles = _groupedFiles[folderName] ?? []; + final targetFiles = _groupedFiles[folderName] ?? []; - List> allBullets = []; - String latestDate = 'Unknown'; + List> allBullets = []; + String latestDate = 'Unknown'; - for (var file in targetFiles) { - final jsonString = await file.readAsString(); - final data = jsonDecode(jsonString); + 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(); + final bullets = (data['bullets'] as List) + .map((b) => {'x': b['x'], 'y': b['y']}) + .toList(); - allBullets.addAll(bullets); + allBullets.addAll(bullets); - if (data['date'] != null) { - latestDate = data['date']; // You could sort if you want the most recent + 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 + ), + ), + ); } - // Create combined JSON structure - final combinedJson = jsonEncode({ - 'date': latestDate, - 'bullets': allBullets, + List _filteredFolderNames() { + final List 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) + folders.sort((a, b) { + final dateA = _folderCreationDates[a]; + final dateB = _folderCreationDates[b]; + if (dateA == null || dateB == null) return 0; + return dateB.compareTo(dateA); // Newest first }); - // 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 - ), - ), - ); + return folders; } -List _filteredFolderNames() { - if (_folderFilter == 'all') return _groupedFiles.keys.toList(); - final isTournamentFilter = _folderFilter == 'tournament'; - - return _groupedFiles.keys - .where((folderName) => _folderTypes[folderName] == isTournamentFilter) - .toList(); -} - - // Rename folder - Future _renameFolder(String oldFolderName) async { +Future _renameFolder(String oldFolderName) async { final newNameController = TextEditingController(); - bool isTournament = _folderTypes[oldFolderName] == true; final result = await showDialog>( @@ -291,7 +305,6 @@ List _filteredFolderNames() { ), ), const SizedBox(height: 20), - TextField( controller: newNameController, textAlign: TextAlign.center, @@ -301,8 +314,7 @@ List _filteredFolderNames() { fillColor: Colors.grey[800], hintText: 'Enter new folder name', hintStyle: const TextStyle(color: Colors.white54), - contentPadding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, @@ -319,7 +331,6 @@ List _filteredFolderNames() { }, ), const SizedBox(height: 16), - GestureDetector( onTap: () { setState(() { @@ -329,28 +340,19 @@ List _filteredFolderNames() { child: Container( width: double.infinity, decoration: BoxDecoration( - color: isTournament - ? _bulletColor.withOpacity(0.2) - : Colors.grey[850], + color: isTournament ? _bulletColor.withOpacity(0.2) : Colors.grey[850], borderRadius: BorderRadius.circular(12), border: Border.all( - color: isTournament - ? _bulletColor - : Colors.grey[700]!, + color: isTournament ? _bulletColor : Colors.grey[700]!, width: 1.5, ), ), - padding: const EdgeInsets.symmetric( - horizontal: 16, vertical: 14), + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Center( child: Text( - isTournament - ? 'Tournament Mode 🏆' - : 'Mark as Tournament 🏆', + isTournament ? 'Tournament Mode 🏆' : 'Mark as Tournament 🏆', style: TextStyle( - color: isTournament - ? _bulletColor - : Colors.white70, + color: isTournament ? _bulletColor : Colors.white70, fontWeight: FontWeight.w500, fontSize: 16, ), @@ -359,7 +361,6 @@ List _filteredFolderNames() { ), ), const SizedBox(height: 20), - Row( children: [ Expanded( @@ -368,8 +369,7 @@ List _filteredFolderNames() { style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[800], foregroundColor: Colors.white, - padding: - const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -387,8 +387,7 @@ List _filteredFolderNames() { style: ElevatedButton.styleFrom( backgroundColor: _bulletColor, foregroundColor: Colors.white, - padding: - const EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -407,26 +406,20 @@ List _filteredFolderNames() { }, ); - // ✅ Handle result (rename + update tag) + // ✅ 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'); + 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.')), + SnackBar(content: Text('A folder named "$targetFolderName" already exists.')), ); return; } else { @@ -434,20 +427,33 @@ List _filteredFolderNames() { } } + // ✅ Preserve existing createdAt value or set it if missing final metadataFile = File('${newFolderPath.path}/metadata.json'); - if (isTournament) { - await metadataFile.writeAsString(jsonEncode({'isTournament': true})); - } else { - if (await metadataFile.exists()) { - await metadataFile.delete(); - } + String? createdAt; + + if (await metadataFile.exists()) { + try { + final existing = jsonDecode(await metadataFile.readAsString()); + createdAt = existing['createdAt']; + } catch (_) {} } - _loadTargetDataFiles(); // refresh UI + 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 _deleteFolder(String folderName) async { bool? confirmDeletion = await _showConfirmationDialog(