update for preview_screen and saving

This commit is contained in:
2026-03-07 09:33:38 +01:00
parent 2ea44fab64
commit d253a9c4a3
6 changed files with 221 additions and 247 deletions
+16 -1
View File
@@ -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<HomeScreen> {
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,
+165 -163
View File
@@ -809,7 +809,39 @@ class _PreviewScreenState extends State<PreviewScreen> {
);
}
Future<void> _saveTargetData() async {
Future<String?> _getLastFolder() async {
final directory = await getApplicationDocumentsDirectory();
final saveDirectory = Directory('${directory.path}/saved_images');
if (!await saveDirectory.exists()) {
return null;
}
final folders = saveDirectory.listSync().whereType<Directory>().toList();
if (folders.isEmpty) {
return null;
}
folders.sort((a, b) => b.statSync().changed.compareTo(a.statSync().changed));
return folders.first.path.split('/').last;
}
Future<void> _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<void> _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<PreviewScreen> {
if (selectedFolder == null) return;
await _performSave(selectedFolder, isTournament);
} catch (e) {
print("Error saving target data: $e");
}
}
Future<void> _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<PreviewScreen> {
// 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<PreviewScreen> {
// 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<PreviewScreen> {
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<PreviewScreen> {
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<PreviewScreen> {
),
),
),
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<PreviewScreen> {
_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,54 +1705,22 @@ class _PreviewScreenState extends State<PreviewScreen> {
],
),
_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;
});
},
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,
),
],
),
child: Icon(
_isEditMode ? Icons.check : Icons.edit,
color: Colors.white,
size: 28,
),
),
),
),
],
),
bottomNavigationBar: BottomAppBar(
bottomNavigationBar: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Top bar - Total Score
BottomAppBar(
color: Colors.grey[900],
padding: EdgeInsets.zero,
child: SizedBox(
height: 150,
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Left side - Label with icon
Padding(
padding: const EdgeInsets.only(right: 16),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Row(
@@ -1827,34 +1728,135 @@ class _PreviewScreenState extends State<PreviewScreen> {
const Icon(Icons.track_changes,
size: 24, color: Colors.white),
const SizedBox(width: 8),
const Text(
"Total Score:",
style: TextStyle(
fontSize: 28,
Text(
_t("Total score:"),
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
),
),
// Right side - Score number (no box)
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
const SizedBox(width: 16),
Text(
"$_totalScore",
style: const TextStyle(
fontSize: 28,
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(
decoration: BoxDecoration(
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),
),
),
),
],
),
),
),
),
// 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"),
),
],
),
),
),
],
),
);
}
}
-43
View File
@@ -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<TargetScreen> {
},
),
),
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,
),
),
],
),
),
),
],
),
),
+4 -4
View File
@@ -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': {
+15 -15
View File
@@ -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"
+1 -1
View File
@@ -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'