diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index a7b828c..d512163 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -6,6 +6,7 @@ import 'target_screen.dart'; import '../widgets/bottom_nav.dart'; //import 'login_screen.dart'; import 'camera_screen.dart'; +import 'spectator_screen.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../utils/translations.dart'; import '../utils/target_colors.dart'; @@ -114,11 +115,25 @@ class _HomeScreenState extends State { automaticallyImplyLeading: false, scrolledUnderElevation: 0, // For Material 3 (Flutter 3.7+) actions: [ + if (_selectedIndex == 1) + IconButton( + icon: const Icon(Icons.visibility_rounded, color: Colors.white), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SpectatorScreen( + selectedLanguage: _selectedLanguage, + ), + ), + ); + }, + ), Padding( padding: const EdgeInsets.only(right: 16.0), child: Center( child: Text( - 'V1.0.2', + 'V1.0.3', style: TextStyle( color: Colors.grey[400], fontSize: 12, diff --git a/lib/screens/preview_screen.dart b/lib/screens/preview_screen.dart index ccf4af0..a6c8fc7 100644 --- a/lib/screens/preview_screen.dart +++ b/lib/screens/preview_screen.dart @@ -809,7 +809,39 @@ class _PreviewScreenState extends State { ); } - Future _saveTargetData() async { + Future _getLastFolder() async { + final directory = await getApplicationDocumentsDirectory(); + final saveDirectory = Directory('${directory.path}/saved_images'); + + if (!await saveDirectory.exists()) { + return null; + } + + final folders = saveDirectory.listSync().whereType().toList(); + if (folders.isEmpty) { + return null; + } + + folders.sort((a, b) => b.statSync().changed.compareTo(a.statSync().changed)); + return folders.first.path.split('/').last; + } + + Future _saveToLastFolder() async { + try { + final lastFolder = await _getLastFolder(); + if (lastFolder == null) { + // No last folder, show options dialog + _saveTargetDataWithDialog(); + return; + } + + await _performSave(lastFolder, false); + } catch (e) { + print("Error saving to last folder: $e"); + } + } + + Future _saveTargetDataWithDialog() async { try { // Show a dialog to let the user select between a new folder or an existing one final result = await _showSaveOptionsDialog(); @@ -830,6 +862,14 @@ class _PreviewScreenState extends State { if (selectedFolder == null) return; + await _performSave(selectedFolder, isTournament); + } catch (e) { + print("Error saving target data: $e"); + } + } + + Future _performSave(String selectedFolder, bool isTournament) async { + try { final directory = await getApplicationDocumentsDirectory(); final saveDirectory = Directory('${directory.path}/saved_images/$selectedFolder'); @@ -876,7 +916,7 @@ class _PreviewScreenState extends State { // Close preview and signal refresh Navigator.pop(context, true); } catch (e) { - print("Error saving target data: $e"); + print("Error performing save: $e"); } } @@ -994,20 +1034,6 @@ class _PreviewScreenState extends State { // 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 (_) {} - } - } // Auto-generate next Training and Tournament folder names int trainingCount = 1; @@ -1083,7 +1109,7 @@ class _PreviewScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.track_changes, - size: 18, color: Colors.white), + size: 16, color: Colors.white), const SizedBox(width: 8), Text( nextTraining, @@ -1132,7 +1158,7 @@ class _PreviewScreenState extends State { mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.emoji_events, - size: 18, color: Colors.white), + size: 16, color: Colors.white), const SizedBox(width: 8), Text( nextTournament, @@ -1153,72 +1179,6 @@ class _PreviewScreenState extends State { ), ), ), - if (lastFolder != null) ...[ - const SizedBox(height: 12), - - // 📁 Last Used Folder Button (highlighted subtly) - SizedBox( - width: double.infinity, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: Colors.white.withValues(alpha: 0.2), width: 1), - borderRadius: BorderRadius.circular(12), - ), - child: ElevatedButton( - onPressed: () { - Navigator.pop( - context, - jsonEncode({ - 'folder': lastFolder, - 'isTournament': lastIsTournament, - }), - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.grey[800], - foregroundColor: Colors.white, - elevation: 0, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - lastIsTournament - ? Icons.emoji_events - : Icons.track_changes, - size: 18, - color: Colors.white, - ), - const SizedBox(width: 8), - Text( - lastFolder, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: Colors.white), - ), - ], - ), - const SizedBox(height: 4), - Text( - _t('Last used folder'), - style: const TextStyle( - fontSize: 12, color: Colors.white70), - ), - ], - ), - ), - ), - ), - ], ], ), ), @@ -1323,33 +1283,6 @@ class _PreviewScreenState extends State { _t('Detection Preview'), style: const TextStyle(color: Colors.white), ), - actions: [ - IconButton( - icon: Icon( - _mode == PreviewMode.mappedTarget - ? Icons.camera_alt - : Icons.center_focus_strong, - ), - color: Colors.white, - onPressed: () async { - setState(() { - _mode = _mode == PreviewMode.mappedTarget - ? PreviewMode.cameraWithDetections - : PreviewMode.mappedTarget; - }); - await _loadSelectedTarget(); // Reload target based on new mode - }, - tooltip: _mode == PreviewMode.mappedTarget - ? _t("Show Camera Detections") - : _t("Show Mapped Target"), - ), - IconButton( - icon: const Icon(Icons.save), - color: Colors.white, - onPressed: _saveTargetData, - tooltip: _t("Save Target Data"), - ), - ], ), body: _isLoading ? const Center(child: CircularProgressIndicator()) @@ -1772,88 +1705,157 @@ class _PreviewScreenState extends State { ], ), _buildEditController(), - // Only show edit button if all bullets are detected - if (_mappedBullets.isNotEmpty && - _mappedBullets.length >= widget.bulletCount) - Positioned( - top: 16, - right: 16, - child: GestureDetector( - onTap: () { - setState(() { - _isEditMode = !_isEditMode; - _selectedBulletIndexInEdit = null; - }); - }, + ], + ), + bottomNavigationBar: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Top bar - Total Score + BottomAppBar( + color: Colors.grey[900], + padding: EdgeInsets.zero, + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: FittedBox( + fit: BoxFit.scaleDown, + child: Row( + children: [ + const Icon(Icons.track_changes, + size: 24, color: Colors.white), + const SizedBox(width: 8), + Text( + _t("Total score:"), + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(width: 16), + Text( + "$_totalScore", + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + // Bottom bar - Action Buttons + BottomAppBar( + color: Colors.grey[900], + padding: EdgeInsets.zero, + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // Toggle view button + IconButton( + icon: Icon( + _mode == PreviewMode.mappedTarget + ? Icons.camera_alt + : Icons.center_focus_strong, + color: Colors.white, + size: 24, + ), + onPressed: () async { + setState(() { + _mode = _mode == PreviewMode.mappedTarget + ? PreviewMode.cameraWithDetections + : PreviewMode.mappedTarget; + }); + await _loadSelectedTarget(); + }, + tooltip: _mode == PreviewMode.mappedTarget + ? _t("Show Camera Detections") + : _t("Show Mapped Target"), + ), + // Split Save button + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: SizedBox( + width: 120, + height: 40, child: Container( - width: 56, - height: 56, decoration: BoxDecoration( - color: _isEditMode ? Colors.red : _bulletColor, - borderRadius: BorderRadius.circular(16), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - offset: Offset(0, 3), - blurRadius: 6, + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + // Save to last folder + Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: _saveToLastFolder, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + bottomLeft: Radius.circular(8), + ), + child: const Icon(Icons.save, + color: Colors.black, size: 20), + ), + ), + ), + // Divider + Container( + width: 1, + color: Colors.grey[300], + ), + // New folder + Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: _saveTargetDataWithDialog, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + child: Icon(Icons.add, + color: Colors.black, size: 24), + ), + ), ), ], ), - child: Icon( - _isEditMode ? Icons.check : Icons.edit, - color: Colors.white, - size: 28, - ), ), ), ), - ], + // Edit button + IconButton( + icon: Icon( + _isEditMode ? Icons.check : Icons.edit, + color: _isEditMode ? Colors.red : Colors.white, + size: 24, + ), + onPressed: () { + setState(() { + _isEditMode = !_isEditMode; + _selectedBulletIndexInEdit = null; + }); + }, + tooltip: _isEditMode ? _t("Done Editing") : _t("Edit Mode"), + ), + ], + ), ), - bottomNavigationBar: BottomAppBar( - color: Colors.grey[900], - padding: EdgeInsets.zero, - child: SizedBox( - height: 150, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Left side - Label with icon - Padding( - padding: const EdgeInsets.only(right: 16), - child: FittedBox( - fit: BoxFit.scaleDown, - child: Row( - children: [ - const Icon(Icons.track_changes, - size: 24, color: Colors.white), - const SizedBox(width: 8), - const Text( - "Total Score:", - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ], - ), - ), - ), - // Right side - Score number (no box) - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "$_totalScore", - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - ), - ], ), - ), + ], ), ); } diff --git a/lib/screens/target_screen.dart b/lib/screens/target_screen.dart index 4ac744e..b36757c 100644 --- a/lib/screens/target_screen.dart +++ b/lib/screens/target_screen.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'stream_screen.dart'; // Import the camera stream screen -import 'spectator_screen.dart'; import '../utils/translations.dart'; import '../utils/target_colors.dart'; @@ -143,48 +142,6 @@ class _TargetScreenState extends State { }, ), ), - const SizedBox(height: 16), - // Spectate button (spans full width, half height) - GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SpectatorScreen( - selectedLanguage: _selectedLanguage, - ), - ), - ); - }, - child: Container( - height: 60, - decoration: BoxDecoration( - color: Colors.grey[900], - borderRadius: BorderRadius.circular(16), - ), - padding: const EdgeInsets.all(16), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.visibility_rounded, - color: Colors.white, - size: 28, - ), - const SizedBox(width: 12), - Text( - _t('Spectate'), - textAlign: TextAlign.center, - style: const TextStyle( - color: Colors.white, - fontSize: 18, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), ], ), ), diff --git a/lib/utils/translations.dart b/lib/utils/translations.dart index f4a01b0..c2503a8 100644 --- a/lib/utils/translations.dart +++ b/lib/utils/translations.dart @@ -307,10 +307,6 @@ class AppTranslations { 'English': 'Tournament', 'Slovenian': 'Tekmovanje', }, - 'Last used folder': { - 'English': 'Last used folder', - 'Slovenian': 'Zadnja uporabljena mapa', - }, 'New folder': { 'English': 'New folder', 'Slovenian': 'Nova mapa', @@ -451,6 +447,10 @@ class AppTranslations { 'English': 'Total score', 'Slovenian': 'Rezultat:', }, + 'Total score:': { + 'English': 'Total score:', + 'Slovenian': 'Skupni seštevek:', + }, // Stream Screen (Camera View) 'Choose a Filter': { diff --git a/pubspec.lock b/pubspec.lock index 1f1e997..2e884c5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -452,26 +452,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -500,10 +500,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -777,10 +777,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" tflite_flutter: dependency: "direct main" description: @@ -889,10 +889,10 @@ packages: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" visibility_detector: dependency: transitive description: @@ -998,5 +998,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 5f625e9..c966c98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: test1 description: A new Flutter project. publish_to: 'none' # This prevents publishing to pub.dev -version: 1.0.1+1 +version: 1.0.3+3 environment: sdk: '>=3.3.0 <4.0.0'