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,
+185 -183
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,88 +1705,157 @@ 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;
});
},
],
),
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,
),
),
),
],
),
),
],
),
);
}
-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': {