From 3914be95b1318d088dd142a1a60ba66789b6586e Mon Sep 17 00:00:00 2001 From: Bl3kiiie Date: Tue, 8 Apr 2025 17:34:11 +0200 Subject: [PATCH] Saving and sorting - Book update --- lib/main.dart | 19 +- lib/screens/book_screen.dart | 338 ++++++++++++++++++++------------ lib/screens/home_screen.dart | 1 + lib/screens/preview_screen.dart | 284 ++++++++++++--------------- 4 files changed, 350 insertions(+), 292 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 6b5f166..a9c0b1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,28 +2,35 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // Import for SystemChrome import 'screens/home_screen.dart'; -void main() { +void main() async { WidgetsFlutterBinding.ensureInitialized(); - + + // Lock orientation to portrait only + await SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + // Set system UI overlay style globally for the app SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( - statusBarColor: Colors.grey[900], // Set status bar color (top bar) - systemNavigationBarColor: Colors.grey[900], // Set navigation bar color - systemNavigationBarIconBrightness: Brightness.light, // Set navigation icon color to light + statusBarColor: Colors.grey[900], + systemNavigationBarColor: Colors.grey[900], + systemNavigationBarIconBrightness: Brightness.light, ), ); runApp(const MyApp()); } + class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( - title: 'App with Calendar', + title: 'SDK Muta', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.white), diff --git a/lib/screens/book_screen.dart b/lib/screens/book_screen.dart index 82c4ac7..74e64f7 100644 --- a/lib/screens/book_screen.dart +++ b/lib/screens/book_screen.dart @@ -22,6 +22,7 @@ class _BookScreenState extends State with WidgetsBindingObserver { Map _folderTypes = {}; // true = tournament, false = training String _folderFilter = 'all'; bool isRenameEnabled = false; + String _sortMode = 'date'; // or 'score' @override void initState() { @@ -172,8 +173,6 @@ 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) { @@ -267,17 +266,24 @@ Color _getColorFromName(String colorName) { } // 🔽 Sort using `createdAt` from _folderCreationDates (which is now based on metadata.json) - folders.sort((a, b) { + // 🔽 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 _renameFolder(String oldFolderName) async { final newNameController = TextEditingController(); bool isTournament = _folderTypes[oldFolderName] == true; @@ -564,61 +570,74 @@ Future _renameFolder(String oldFolderName) async { } - // Build the folder row with folder name, trash, and rename options -// Build the folder row with folder name, trash, rename options, and view all targets icon Widget _buildFolderRow(String folderName) { final numberOfTargets = _groupedFiles[folderName]!.length; final totalScore = _folderScores[folderName]; + final folderDate = _folderCreationDates[folderName]; + final formattedDate = folderDate != null + ? DateFormat('yyyy-MM-dd').format(folderDate) + : 'Unknown Date'; + + final isExpanded = _expandedFolder == folderName; return Container( - padding: const EdgeInsets.all(16), - margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), + margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), decoration: BoxDecoration( - color: Colors.grey[300], // Lighter background for folder + color: isExpanded ? _bulletColor.withOpacity(0.15) : Colors.grey[300], borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isExpanded ? _bulletColor : Colors.transparent, + width: 1.5, + ), + boxShadow: isExpanded + ? [ + BoxShadow( + color: _bulletColor.withOpacity(0.2), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ] + : [], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( - contentPadding: const EdgeInsets.all(0), - title: Row( - children: [ - Text(folderName, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), - if (_folderTypes[folderName] == true) - const Padding( - padding: EdgeInsets.only(left: 6), - child: Text('🏆', style: TextStyle(fontSize: 18)), - ), - ], + dense: true, // Compact layout + contentPadding: EdgeInsets.zero, + title: Text( + _folderTypes[folderName] == true + ? '🏆 $folderName' + : folderName, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, ), - subtitle: Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), // Add padding between title and info - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon(Icons.insert_drive_file, color: Colors.black45), - const SizedBox(width: 4), // Small space between the icon and the text - Text( - "$numberOfTargets / 🎯 $totalScore", - style: const TextStyle(fontSize: 16, color: Colors.black), - ), - ], + padding: const EdgeInsets.only(top: 4.0), + child: Text( + "📝 $numberOfTargets / 🎯 $totalScore / 📅 $formattedDate", + style: const TextStyle(fontSize: 13, color: Colors.black87), ), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( + iconSize: 22, + visualDensity: VisualDensity.compact, icon: const Icon(Icons.my_location, color: Colors.grey), onPressed: () => _openFolderTargetView(folderName), ), IconButton( + iconSize: 22, + visualDensity: VisualDensity.compact, icon: Icon(Icons.edit, color: _bulletColor), onPressed: () => _renameFolder(folderName), ), IconButton( + iconSize: 22, + visualDensity: VisualDensity.compact, icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _deleteFolder(folderName), ), @@ -626,13 +645,11 @@ Widget _buildFolderRow(String folderName) { ), onTap: () { setState(() { - // If the folder is already expanded, collapse it, otherwise expand it - _expandedFolder = _expandedFolder == folderName ? null : folderName; + _expandedFolder = isExpanded ? null : folderName; }); }, ), - if (_expandedFolder == folderName) - // When expanded, display the file row inside the folder + if (isExpanded) ..._groupedFiles[folderName]! .asMap() .entries @@ -689,51 +706,53 @@ Widget _buildFolderRow(String folderName) { @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, + return Scaffold( + body: Column( children: [ - Text( - _folderFilter == 'all' - ? 'Showing: All folders' - : _folderFilter == 'tournament' - ? 'Showing: 🏆 Tournament folders' - : 'Showing: 🎯 Training folders', - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - const Icon(Icons.filter_list), - ], + // 🔽 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' + ? 'Showing: 📂 All folders' + : _folderFilter == 'tournament' + ? 'Showing: 🏆 Tournament folders' + : 'Showing: 🎯 Training folders', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + ), + const Icon(Icons.filter_list), + ], + ), ), ), ), -), - - - - // 📁 Folder list or empty state - Expanded( + // 📁 Folder list or empty state + Expanded( child: _groupedFiles.isEmpty ? const Center(child: Text("No saved target data found", style: TextStyle(fontSize: 18))) - : ListView.builder( - itemCount: _filteredFolderNames().length, - itemBuilder: (context, index) { - String folderName = _filteredFolderNames()[index]; - return _buildFolderRow(folderName); - }, + : 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); + }, + ), ), ), ], @@ -745,66 +764,134 @@ void _showFolderFilterPopup(BuildContext context) { showDialog( context: context, builder: (BuildContext context) { - return Dialog( - backgroundColor: Colors.grey[900], - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Container( - width: double.infinity, - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Text( - 'Filter Folders', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - const SizedBox(height: 24), + 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: [ + const Text( + 'Filter Folders', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 24), - _buildFilterOption('all', '📂 All folders'), - const SizedBox(height: 12), - _buildFilterOption('tournament', '🏆 Tournament'), - const SizedBox(height: 12), - _buildFilterOption('training', '🎯 Training'), - ], - ), - ), + // Folder type filters + _buildStyledFilterButton('all', '📂 All folders', context), + const SizedBox(height: 12), + _buildStyledFilterButton('tournament', '🏆 Tournament', context), + const SizedBox(height: 12), + _buildStyledFilterButton('training', '🎯 Training', context), + + const SizedBox(height: 24), + + const Text( + 'Sort By', + style: TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 12), + + Row( + children: [ + Expanded( + child: GestureDetector( + onTap: () { + setState(() => _sortMode = 'date'); + this.setState(() {}); // 👈 refresh parent widget + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: _sortMode == 'date' ? _bulletColor : Colors.grey[800], + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + '📅 Sort by Date', + style: TextStyle( + color: _sortMode == 'date' ? Colors.white : Colors.white70, + fontWeight: FontWeight.w500, + fontSize: 15, + ), + ), + ), + ), + ), + ), + Expanded( + child: GestureDetector( + onTap: () { + setState(() => _sortMode = 'score'); + this.setState(() {}); // 👈 refresh parent widget + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: _sortMode == 'score' ? _bulletColor : Colors.grey[800], + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + '🏆 Sort by Score', + style: TextStyle( + color: _sortMode == 'score' ? Colors.white : Colors.white70, + fontWeight: FontWeight.w500, + fontSize: 15, + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }, ); }, ); } -Widget _buildFilterOption(String value, String label) { +Widget _buildStyledFilterButton(String value, String label, BuildContext context) { final isSelected = _folderFilter == value; - return GestureDetector( - onTap: () { - setState(() { - _folderFilter = value; - }); - Navigator.of(context).pop(); - }, - child: Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - decoration: BoxDecoration( - color: isSelected ? _bulletColor.withOpacity(0.3) : Colors.grey[800], - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: isSelected ? _bulletColor : Colors.transparent, - width: 1.5, + 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: const TextStyle(color: Colors.white, fontSize: 16), + child: Center( + child: Text( + label, + style: TextStyle( + color: isSelected ? Colors.white : Colors.white70, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), ), ), ), @@ -812,5 +899,4 @@ Widget _buildFilterOption(String value, String label) { } - } diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 3b1dfcf..e5045c5 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -75,6 +75,7 @@ class _HomeScreenState extends State { backgroundColor: Colors.grey[900], iconTheme: const IconThemeData(color: Colors.white), automaticallyImplyLeading: false, + scrolledUnderElevation: 0, // For Material 3 (Flutter 3.7+) actions: [ IconButton( icon: const Icon(Icons.logout, color: Colors.red), diff --git a/lib/screens/preview_screen.dart b/lib/screens/preview_screen.dart index 8b67619..4a02bc8 100644 --- a/lib/screens/preview_screen.dart +++ b/lib/screens/preview_screen.dart @@ -525,199 +525,164 @@ void _moveBullet(Offset offsetDelta) { - Future _showSaveOptionsDialog() async { + Future _showSaveOptionsDialog() async { final directory = await getApplicationDocumentsDirectory(); final saveDirectory = Directory('${directory.path}/saved_images'); - // Ensure directory exists if (!await saveDirectory.exists()) { await saveDirectory.create(recursive: true); } - // Load existing folders and their modified time - final folderStats = >[]; - final folders = saveDirectory.listSync(); - for (var folder in folders) { - if (folder is Directory) { - final stat = folder.statSync(); - folderStats.add({ - 'name': folder.path.split('/').last, - 'changed': stat.changed, - }); + final folders = saveDirectory.listSync().whereType().toList(); + + // Sort to get most recently modified + folders.sort((a, b) => b.statSync().changed.compareTo(a.statSync().changed)); + final String? lastFolder = folders.isNotEmpty ? folders.first.path.split('/').last : null; + + bool lastIsTournament = false; + + if (lastFolder != null) { + final metadataFile = File('${folders.first.path}/metadata.json'); + if (await metadataFile.exists()) { + try { + final metadata = jsonDecode(await metadataFile.readAsString()); + lastIsTournament = metadata['isTournament'] == true; + } catch (_) {} } } - // Sort by modified time, newest first - folderStats.sort((a, b) => - (b['changed'] as DateTime).compareTo(a['changed'] as DateTime)); - String? lastFolderName = - folderStats.isNotEmpty ? folderStats.first['name'] : null; + // Auto-generate next Training and Tournament folder names + int trainingCount = 1; + int tournamentCount = 1; - TextEditingController _controller = TextEditingController(); - bool isNewFolderEnabled = folderStats.isEmpty; // pre-enable if none - bool isTournament = false; + for (var folder in folders) { + final name = folder.path.split('/').last; + if (name.startsWith('Training')) { + final num = int.tryParse(name.replaceAll('Training ', '')); + if (num != null && num >= trainingCount) trainingCount = num + 1; + } + if (name.startsWith('Tournament')) { + final num = int.tryParse(name.replaceAll('Tournament ', '')); + if (num != null && num >= tournamentCount) tournamentCount = num + 1; + } + } - return await showDialog( + final String nextTraining = 'Training $trainingCount'; + final String nextTournament = 'Tournament $tournamentCount'; + + return showDialog( 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: [ - const Padding( - padding: EdgeInsets.only(bottom: 16.0), - child: Text( - 'Save Target Data', - style: TextStyle( - color: Colors.white, - fontSize: 20, - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Save Target Data', + style: TextStyle( + color: Colors.white, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 24), + + // 🎯 Training Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop( + context, + jsonEncode({'folder': nextTraining, 'isTournament': false}), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: _bulletColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), ), - const Text( - "Select where you'd like to save the data:", - style: TextStyle(color: Colors.white70), - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), + child: Text('🎯 Save to "$nextTraining"'), + ), + ), + const SizedBox(height: 12), - // Quick save to newest folder - if (lastFolderName != null) ...[ - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.pop(context, lastFolderName); - }, - style: ElevatedButton.styleFrom( - backgroundColor: _bulletColor, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Text("Save to '$lastFolderName'"), - ), - ), - const SizedBox(height: 16), - ], - - // Always show folder name input - SizedBox( - width: double.infinity, - child: TextField( - controller: _controller, - style: const TextStyle(color: Colors.white), - textAlign: TextAlign.center, - decoration: InputDecoration( - filled: true, - fillColor: Colors.grey[800], - hintText: "Enter a 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(() { - isNewFolderEnabled = value.trim().isNotEmpty; - }); - }, + // 🏆 Tournament Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.pop( + context, + jsonEncode({'folder': nextTournament, 'isTournament': true}), + ); + }, + style: ElevatedButton.styleFrom( + backgroundColor: _bulletColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), ), - const SizedBox(height: 12), + child: Text('🏆 Save to "$nextTournament"'), + ), + ), + if (lastFolder != null) ...[ + const SizedBox(height: 12), - // Tournament toggle - if (isNewFolderEnabled) ...[ - 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 - ? 'Tournament Mode 🏆' - : 'Mark as Tournament 🏆', - style: TextStyle( - color: isTournament - ? _bulletColor - : Colors.white70, - fontWeight: FontWeight.w500, - fontSize: 16, - ), - ), - ), - ), + // 📁 Last Used Folder Button (highlighted subtly) + SizedBox( + width: double.infinity, + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.white.withOpacity(0.2), width: 1), + borderRadius: BorderRadius.circular(12), ), - const SizedBox(height: 16), - ], - - // Create folder button - SizedBox( - width: double.infinity, child: ElevatedButton( - onPressed: isNewFolderEnabled - ? () => Navigator.pop( - context, - jsonEncode({ - 'folder': _controller.text.trim(), - 'isTournament': isTournament, - }), - ) - : null, + onPressed: () { + Navigator.pop( + context, + jsonEncode({ + 'folder': lastFolder, + 'isTournament': lastIsTournament, + }), + ); + }, style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 14), - backgroundColor: - isNewFolderEnabled ? _bulletColor : Colors.grey[700], + backgroundColor: _bulletColor.withOpacity(0.9), foregroundColor: Colors.white, - disabledBackgroundColor: Colors.grey[700], + elevation: 0, + padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), - child: const Text("Create New Folder"), + child: Column( + children: [ + Text( + '${lastIsTournament ? "🏆" : "🎯"} Save to "$lastFolder"', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + const Text( + 'Last used folder', + style: TextStyle(fontSize: 12, color: Colors.white70), + ), + ], + ), ), ), - ], - ); - }, + ), + ], + ], ), ), ); @@ -725,7 +690,6 @@ void _moveBullet(Offset offsetDelta) { ); } - Map? _getRingCenterSize(List results) { try { final ring = results.firstWhere((r) => r.classId == 2);