updated book folder structure
This commit is contained in:
@@ -100,25 +100,36 @@ Color _getColorFromName(String colorName) {
|
|||||||
Map<String, List<File>> groupedFiles = {};
|
Map<String, List<File>> groupedFiles = {};
|
||||||
Map<String, DateTime> folderDates = {};
|
Map<String, DateTime> folderDates = {};
|
||||||
Map<String, int> folderScores = {};
|
Map<String, int> folderScores = {};
|
||||||
Map<String, bool> folderTypes = {}; // ✅
|
Map<String, bool> folderTypes = {};
|
||||||
|
|
||||||
for (var folder in folders) {
|
for (var folder in folders) {
|
||||||
String folderName = folder.path.split('/').last;
|
String folderName = folder.path.split('/').last;
|
||||||
DateTime folderDate = folder.statSync().modified;
|
DateTime fallbackDate = folder.statSync().changed; // fallback
|
||||||
|
|
||||||
// ✅ Read isTournament flag
|
// Read metadata
|
||||||
bool isTournament = false;
|
|
||||||
final metadataFile = File('${folder.path}/metadata.json');
|
final metadataFile = File('${folder.path}/metadata.json');
|
||||||
|
bool isTournament = false;
|
||||||
|
DateTime? createdAt;
|
||||||
|
|
||||||
if (await metadataFile.exists()) {
|
if (await metadataFile.exists()) {
|
||||||
try {
|
try {
|
||||||
final metadata = jsonDecode(await metadataFile.readAsString());
|
final metadata = jsonDecode(await metadataFile.readAsString());
|
||||||
|
|
||||||
isTournament = metadata['isTournament'] == true;
|
isTournament = metadata['isTournament'] == true;
|
||||||
|
|
||||||
|
if (metadata['createdAt'] != null) {
|
||||||
|
createdAt = DateTime.tryParse(metadata['createdAt']);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error reading metadata for $folderName: $e");
|
debugPrint("Error reading metadata for $folderName: $e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use createdAt if available, fallback otherwise
|
||||||
|
folderDates[folderName] = createdAt ?? fallbackDate;
|
||||||
folderTypes[folderName] = isTournament;
|
folderTypes[folderName] = isTournament;
|
||||||
|
|
||||||
|
// List target files
|
||||||
List<File> targetFiles = Directory(folder.path)
|
List<File> targetFiles = Directory(folder.path)
|
||||||
.listSync()
|
.listSync()
|
||||||
.whereType<File>()
|
.whereType<File>()
|
||||||
@@ -131,36 +142,30 @@ Color _getColorFromName(String colorName) {
|
|||||||
for (var file in targetFiles) {
|
for (var file in targetFiles) {
|
||||||
final jsonString = await file.readAsString();
|
final jsonString = await file.readAsString();
|
||||||
final data = jsonDecode(jsonString);
|
final data = jsonDecode(jsonString);
|
||||||
final score = _getScoreFromJson(data);
|
totalScore += _getScoreFromJson(data);
|
||||||
totalScore += score;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
groupedFiles[folderName] = targetFiles;
|
groupedFiles[folderName] = targetFiles;
|
||||||
folderDates[folderName] = folderDate;
|
|
||||||
folderScores[folderName] = totalScore;
|
folderScores[folderName] = totalScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort folders by stored or fallback created date (DESC)
|
||||||
final sortedFolderNames = folderDates.keys.toList()
|
final sortedFolderNames = folderDates.keys.toList()
|
||||||
..sort((a, b) => folderDates[b]!.compareTo(folderDates[a]!));
|
..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(() {
|
setState(() {
|
||||||
_groupedFiles = sortedGroupedFiles;
|
_groupedFiles = {
|
||||||
_folderCreationDates = sortedFolderDates;
|
for (var name in sortedFolderNames) name: groupedFiles[name]!
|
||||||
_folderScores = sortedFolderScores;
|
};
|
||||||
_folderTypes = sortedFolderTypes; // ✅ FIXED
|
_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) {
|
} catch (e) {
|
||||||
debugPrint("Error loading target data files: $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
|
// Function to safely get the score from the JSON data
|
||||||
int _getScoreFromJson(Map<String, dynamic> data) {
|
int _getScoreFromJson(Map<String, dynamic> data) {
|
||||||
if (data.containsKey('totalScore') && data['totalScore'] is int) {
|
if (data.containsKey('totalScore') && data['totalScore'] is int) {
|
||||||
@@ -250,22 +256,30 @@ Color _getColorFromName(String colorName) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _filteredFolderNames() {
|
|
||||||
if (_folderFilter == 'all') return _groupedFiles.keys.toList();
|
|
||||||
|
|
||||||
final isTournamentFilter = _folderFilter == 'tournament';
|
Future<void> _renameFolder(String oldFolderName) async {
|
||||||
|
|
||||||
return _groupedFiles.keys
|
|
||||||
.where((folderName) => _folderTypes[folderName] == isTournamentFilter)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename folder
|
|
||||||
Future<void> _renameFolder(String oldFolderName) async {
|
|
||||||
final newNameController = TextEditingController();
|
final newNameController = TextEditingController();
|
||||||
|
|
||||||
bool isTournament = _folderTypes[oldFolderName] == true;
|
bool isTournament = _folderTypes[oldFolderName] == true;
|
||||||
|
|
||||||
final result = await showDialog<Map<String, dynamic>>(
|
final result = await showDialog<Map<String, dynamic>>(
|
||||||
@@ -291,7 +305,6 @@ List<String> _filteredFolderNames() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
controller: newNameController,
|
controller: newNameController,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
@@ -301,8 +314,7 @@ List<String> _filteredFolderNames() {
|
|||||||
fillColor: Colors.grey[800],
|
fillColor: Colors.grey[800],
|
||||||
hintText: 'Enter new folder name',
|
hintText: 'Enter new folder name',
|
||||||
hintStyle: const TextStyle(color: Colors.white54),
|
hintStyle: const TextStyle(color: Colors.white54),
|
||||||
contentPadding:
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide.none,
|
borderSide: BorderSide.none,
|
||||||
@@ -319,7 +331,6 @@ List<String> _filteredFolderNames() {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -329,28 +340,19 @@ List<String> _filteredFolderNames() {
|
|||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isTournament
|
color: isTournament ? _bulletColor.withOpacity(0.2) : Colors.grey[850],
|
||||||
? _bulletColor.withOpacity(0.2)
|
|
||||||
: Colors.grey[850],
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: isTournament
|
color: isTournament ? _bulletColor : Colors.grey[700]!,
|
||||||
? _bulletColor
|
|
||||||
: Colors.grey[700]!,
|
|
||||||
width: 1.5,
|
width: 1.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
horizontal: 16, vertical: 14),
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
isTournament
|
isTournament ? 'Tournament Mode 🏆' : 'Mark as Tournament 🏆',
|
||||||
? 'Tournament Mode 🏆'
|
|
||||||
: 'Mark as Tournament 🏆',
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: isTournament
|
color: isTournament ? _bulletColor : Colors.white70,
|
||||||
? _bulletColor
|
|
||||||
: Colors.white70,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
),
|
),
|
||||||
@@ -359,7 +361,6 @@ List<String> _filteredFolderNames() {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -368,8 +369,7 @@ List<String> _filteredFolderNames() {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[800],
|
backgroundColor: Colors.grey[800],
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -387,8 +387,7 @@ List<String> _filteredFolderNames() {
|
|||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: _bulletColor,
|
backgroundColor: _bulletColor,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
@@ -407,26 +406,20 @@ List<String> _filteredFolderNames() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// ✅ Handle result (rename + update tag)
|
// ✅ Handle result (rename + tag update)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
final newName = result['name'];
|
final newName = result['name'];
|
||||||
final isTournament = result['isTournament'] ?? false;
|
final isTournament = result['isTournament'] ?? false;
|
||||||
|
|
||||||
final directory = await getApplicationDocumentsDirectory();
|
final directory = await getApplicationDocumentsDirectory();
|
||||||
final oldFolderPath =
|
final oldFolderPath = Directory('${directory.path}/saved_images/$oldFolderName');
|
||||||
Directory('${directory.path}/saved_images/$oldFolderName');
|
final targetFolderName = (newName != null && newName.isNotEmpty) ? newName : oldFolderName;
|
||||||
|
final newFolderPath = Directory('${directory.path}/saved_images/$targetFolderName');
|
||||||
final targetFolderName =
|
|
||||||
(newName != null && newName.isNotEmpty) ? newName : oldFolderName;
|
|
||||||
|
|
||||||
final newFolderPath =
|
|
||||||
Directory('${directory.path}/saved_images/$targetFolderName');
|
|
||||||
|
|
||||||
if (targetFolderName != oldFolderName) {
|
if (targetFolderName != oldFolderName) {
|
||||||
if (await newFolderPath.exists()) {
|
if (await newFolderPath.exists()) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text('A folder named "$targetFolderName" already exists.')),
|
||||||
content: Text('A folder named "$targetFolderName" already exists.')),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -434,20 +427,33 @@ List<String> _filteredFolderNames() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Preserve existing createdAt value or set it if missing
|
||||||
final metadataFile = File('${newFolderPath.path}/metadata.json');
|
final metadataFile = File('${newFolderPath.path}/metadata.json');
|
||||||
if (isTournament) {
|
String? createdAt;
|
||||||
await metadataFile.writeAsString(jsonEncode({'isTournament': true}));
|
|
||||||
} else {
|
|
||||||
if (await metadataFile.exists()) {
|
if (await metadataFile.exists()) {
|
||||||
await metadataFile.delete();
|
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
|
// Delete a folder
|
||||||
Future<void> _deleteFolder(String folderName) async {
|
Future<void> _deleteFolder(String folderName) async {
|
||||||
bool? confirmDeletion = await _showConfirmationDialog(
|
bool? confirmDeletion = await _showConfirmationDialog(
|
||||||
|
|||||||
Reference in New Issue
Block a user