This commit is contained in:
2025-10-16 17:42:46 +02:00
parent dc86955721
commit 2e3a69a23e
12 changed files with 1421 additions and 604 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"idf.pythonInstallPath": "/usr/bin/python3",
"cmake.ignoreCMakeListsMissing": true
}
+314 -131
View File
@@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; // For DateFormat
import 'package:path_provider/path_provider.dart'; // For getApplicationDocumentsDirectory import 'package:path_provider/path_provider.dart'; // For getApplicationDocumentsDirectory
import 'target_view.dart'; // Import the TargetViewScreen import 'target_view.dart'; // Import the TargetViewScreen
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
class BookScreen extends StatefulWidget { class BookScreen extends StatefulWidget {
const BookScreen({super.key}); const BookScreen({super.key});
@@ -17,21 +18,34 @@ class _BookScreenState extends State<BookScreen> with WidgetsBindingObserver {
Map<String, List<File>> _groupedFiles = {}; // Files grouped by folder Map<String, List<File>> _groupedFiles = {}; // Files grouped by folder
Map<String, DateTime> _folderCreationDates = {}; // Folder creation dates Map<String, DateTime> _folderCreationDates = {}; // Folder creation dates
Map<String, int> _folderScores = {}; // Folder scores (sum of scores of all target files inside) Map<String, int> _folderScores = {}; // Folder scores (sum of scores of all target files inside)
String? _expandedFolder;
Color _bulletColor = Colors.blue; // default fallback Color _bulletColor = Colors.blue; // default fallback
Map<String, bool> _folderTypes = {}; // true = tournament, false = training Map<String, bool> _folderTypes = {}; // true = tournament, false = training
String _folderFilter = 'all'; String _folderFilter = 'all';
bool isRenameEnabled = false; bool isRenameEnabled = false;
String _sortMode = 'date'; // or 'score' String _sortMode = 'date'; // or 'score'
String _selectedLanguage = 'English';
String? _expandedFolder; // Track which folder is expanded
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); // Register to listen for lifecycle events WidgetsBinding.instance.addObserver(this); // Register to listen for lifecycle events
_loadBulletColor(); _loadBulletColor();
_loadLanguage();
_loadTargetDataFiles(); // Initial load when the screen is first created _loadTargetDataFiles(); // Initial load when the screen is first created
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); // Unregister from lifecycle events when disposed WidgetsBinding.instance.removeObserver(this); // Unregister from lifecycle events when disposed
@@ -192,7 +206,7 @@ Color _getColorFromName(String colorName) {
Future<void> _deleteFile(File file) async { Future<void> _deleteFile(File file) async {
bool? confirmDeletion = await _showConfirmationDialog( bool? confirmDeletion = await _showConfirmationDialog(
context, context,
'Are you sure you want to delete this file?', _t('Are you sure you want to delete this file?'),
); );
if (confirmDeletion == true) { if (confirmDeletion == true) {
@@ -309,9 +323,9 @@ Future<void> _renameFolder(String oldFolderName) async {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Text( Text(
'Folder Settings', _t('Folder Settings'),
style: TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -325,7 +339,7 @@ Future<void> _renameFolder(String oldFolderName) async {
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.grey[800], fillColor: Colors.grey[800],
hintText: 'Enter new folder name', hintText: _t('Enter new folder name'),
hintStyle: const TextStyle(color: Colors.white54), hintStyle: const TextStyle(color: Colors.white54),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
@@ -363,7 +377,7 @@ Future<void> _renameFolder(String oldFolderName) async {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
child: Center( child: Center(
child: Text( child: Text(
isTournament ? 'Tournament Mode 🏆' : 'Mark as Tournament 🏆', isTournament ? _t('Tournament Mode 🏆') : _t('Mark as Tournament 🏆'),
style: TextStyle( style: TextStyle(
color: isTournament ? _bulletColor : Colors.white70, color: isTournament ? _bulletColor : Colors.white70,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@@ -387,7 +401,7 @@ Future<void> _renameFolder(String oldFolderName) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text('Cancel'), child: Text(_t('Cancel')),
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -405,7 +419,7 @@ Future<void> _renameFolder(String oldFolderName) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text('Apply'), child: Text(_t('Apply')),
), ),
), ),
], ],
@@ -471,7 +485,7 @@ Future<void> _renameFolder(String oldFolderName) async {
Future<void> _deleteFolder(String folderName) async { Future<void> _deleteFolder(String folderName) async {
bool? confirmDeletion = await _showConfirmationDialog( bool? confirmDeletion = await _showConfirmationDialog(
context, context,
'Are you sure you want to delete the folder "$folderName" and all of its contents?', '${_t('Are you sure you want to delete the folder')} "$folderName" ${_t('and all of its contents?')}',
); );
if (confirmDeletion == true) { if (confirmDeletion == true) {
@@ -485,12 +499,6 @@ Future<void> _renameFolder(String oldFolderName) async {
_loadTargetDataFiles(); // Refresh file/folder list _loadTargetDataFiles(); // Refresh file/folder list
if (_expandedFolder == folderName) {
setState(() {
_expandedFolder = null;
});
}
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('Folder "$folderName" deleted successfully.'), content: Text('Folder "$folderName" deleted successfully.'),
@@ -517,9 +525,9 @@ Future<void> _renameFolder(String oldFolderName) async {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Text( Text(
'Confirm Deletion', _t('Confirm Deletion'),
style: TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -547,7 +555,7 @@ Future<void> _renameFolder(String oldFolderName) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text("Yes, delete"), child: Text(_t("Yes, delete")),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -565,7 +573,7 @@ Future<void> _renameFolder(String oldFolderName) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text("Cancel"), child: Text(_t("Cancel")),
), ),
), ),
], ],
@@ -582,125 +590,292 @@ Widget _buildFolderRow(String folderName) {
final totalScore = _folderScores[folderName]; final totalScore = _folderScores[folderName];
final folderDate = _folderCreationDates[folderName]; final folderDate = _folderCreationDates[folderName];
final formattedDate = folderDate != null final formattedDate = folderDate != null
? DateFormat('yyyy-MM-dd').format(folderDate) ? DateFormat('dd/MM/yyyy').format(folderDate)
: 'Unknown Date'; : 'Unknown Date';
final isExpanded = _expandedFolder == folderName;
return Container( return Container(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isExpanded ? _bulletColor.withOpacity(0.15) : Colors.grey[300], color: Colors.grey[300],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isExpanded ? _bulletColor : Colors.transparent,
width: 1.5,
),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTile( // Header section: Name and Date
dense: true, // Compact layout InkWell(
contentPadding: EdgeInsets.zero, onTap: () {
title: Text( setState(() {
_folderTypes[folderName] == true _expandedFolder = _expandedFolder == folderName ? null : folderName;
? '🏆 $folderName' });
: folderName, },
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600), borderRadius: const BorderRadius.only(
), topLeft: Radius.circular(12),
subtitle: Padding( topRight: Radius.circular(12),
padding: const EdgeInsets.only(top: 4.0), ),
child: Text( child: Container(
"📝 $numberOfTargets / 🎯 $totalScore / 📅 $formattedDate", padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
style: const TextStyle(fontSize: 13, color: Colors.black87), child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// Small colored indicator
Container(
width: 4,
height: 20,
decoration: BoxDecoration(
color: _bulletColor,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 8),
Text(
_folderTypes[folderName] == true
? '🏆 $folderName'
: folderName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
),
],
),
Row(
children: [
Text(
"📅 $formattedDate",
style: const TextStyle(fontSize: 13, color: Colors.black87),
),
const SizedBox(width: 8),
Icon(
_expandedFolder == folderName ? Icons.expand_less : Icons.expand_more,
color: Colors.black87,
),
],
),
],
), ),
), ),
trailing: Row( ),
mainAxisSize: MainAxisSize.min, // Expandable targets list (in the middle)
if (_expandedFolder == folderName) ...[
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey[100],
),
child: _buildTargetsList(folderName),
),
],
// Bottom section: Info badge and action buttons
IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
IconButton( // Info badge section (non-clickable, matches button style)
iconSize: 22, Expanded(
visualDensity: VisualDensity.compact, child: Container(
icon: Icon(Icons.my_location, color: _bulletColor), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
onPressed: () => _openFolderTargetView(folderName), decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.only(
bottomLeft: const Radius.circular(12),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Targets count
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.filter_center_focus, size: 16, color: Colors.black87),
const SizedBox(width: 4),
Text(
"$numberOfTargets",
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87),
),
],
),
// Score
Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.emoji_events, size: 16, color: Colors.amber),
const SizedBox(width: 4),
Text(
"$totalScore",
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87),
),
],
),
],
),
),
), ),
IconButton( // Preview button section
iconSize: 22, InkWell(
visualDensity: VisualDensity.compact, onTap: () => _openFolderTargetView(folderName),
icon: Icon(Icons.edit, color: _bulletColor), child: Container(
onPressed: () => _renameFolder(folderName), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: _bulletColor.withValues(alpha: 0.2),
border: Border(
left: BorderSide(color: Colors.grey[400]!, width: 1),
),
),
child: Icon(Icons.visibility, color: _bulletColor, size: 20),
),
), ),
IconButton( // Edit button section
iconSize: 22, InkWell(
visualDensity: VisualDensity.compact, onTap: () => _renameFolder(folderName),
icon: const Icon(Icons.delete, color: Colors.red), child: Container(
onPressed: () => _deleteFolder(folderName), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: _bulletColor.withValues(alpha: 0.2),
border: Border(
left: BorderSide(color: Colors.grey[400]!, width: 1),
),
),
child: Icon(Icons.edit, color: _bulletColor, size: 20),
),
),
// Delete button section
InkWell(
onTap: () => _deleteFolder(folderName),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.2),
borderRadius: const BorderRadius.only(
bottomRight: Radius.circular(12),
),
border: Border(
left: BorderSide(color: Colors.grey[400]!, width: 1),
),
),
child: const Icon(Icons.delete, color: Colors.red, size: 20),
),
), ),
], ],
), ),
onTap: () {
setState(() {
_expandedFolder = isExpanded ? null : folderName;
});
},
), ),
if (isExpanded)
..._groupedFiles[folderName]!
.asMap()
.entries
.map((entry) => _buildFileRow(entry.value, folderName, entry.key))
.toList(),
], ],
), ),
); );
} }
// Build the row for files in the folder with everything in a single row Widget _buildTargetsList(String folderName) {
Widget _buildFileRow(File file, String folderName, int index) { final targetFiles = _groupedFiles[folderName] ?? [];
final fileName = "Target ${index + 1}"; // Use number starting from 1
final folderDate = _folderCreationDates[folderName];
final formattedDate = folderDate != null
? DateFormat('yyyy-MM-dd').format(folderDate)
: 'Unknown Date'; // or any default fallback string
final score = _getScoreFromJson(jsonDecode(file.readAsStringSync())); // Read the score for the file if (targetFiles.isEmpty) {
return Padding(
return Container( padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), // Reduced vertical padding for compact height child: Text(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), // Tight margin _t("No targets in this folder"),
decoration: BoxDecoration( style: const TextStyle(color: Colors.black54, fontSize: 14),
color: Colors.grey[200], // Light grey background for files textAlign: TextAlign.center,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey[300]!, width: 1), // Light border around the file
),
child: InkWell(
onTap: () => _openFilePreview(file), // Open the file preview when the row is tapped
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // Distribute space across the row
children: [
// File name
Text(fileName, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
// Date
Text(formattedDate, style: const TextStyle(fontSize: 12, color: Colors.black54)),
// Target emoji and score
Text("🎯 $score", style: const TextStyle(fontSize: 14, color: Colors.black54)),
// Delete button
IconButton(
icon: const Icon(Icons.delete, color: Color.fromARGB(255, 252, 102, 92)),
onPressed: () => _deleteFile(file), // Trigger file deletion
),
],
), ),
), );
}
return Column(
children: targetFiles.asMap().entries.map((entry) {
final index = entry.key;
final file = entry.value;
final score = _getScoreFromJson(jsonDecode(file.readAsStringSync()));
final fileName = "${_t('Target')} ${index + 1}";
return Container(
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey[300]!, width: 1),
),
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Target info section
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
child: Row(
children: [
Expanded(
child: Text(
fileName,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
),
const SizedBox(width: 8),
const Icon(
Icons.emoji_events,
color: Colors.amber,
size: 14,
),
const SizedBox(width: 4),
Text(
"$score",
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
],
),
),
),
// Preview button
InkWell(
onTap: () => _openFilePreview(file),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: _bulletColor.withValues(alpha: 0.15),
border: Border(
left: BorderSide(color: Colors.grey[300]!, width: 1),
),
),
child: Icon(Icons.visibility, color: _bulletColor, size: 18),
),
),
// Delete button
InkWell(
onTap: () => _deleteFile(file),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.1),
border: Border(
left: BorderSide(color: Colors.grey[300]!, width: 1),
),
borderRadius: const BorderRadius.only(
topRight: Radius.circular(10),
bottomRight: Radius.circular(10),
),
),
child: const Icon(
Icons.delete,
color: Colors.red,
size: 18,
),
),
),
],
),
),
);
}).toList(),
); );
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@@ -723,10 +898,10 @@ Widget build(BuildContext context) {
children: [ children: [
Text( Text(
_folderFilter == 'all' _folderFilter == 'all'
? 'Showing: 📂 All folders' ? _t('Showing: 📂 All folders')
: _folderFilter == 'tournament' : _folderFilter == 'tournament'
? 'Showing: 🏆 Tournament folders' ? _t('Showing: 🏆 Tournament folders')
: 'Showing: 🎯 Training folders', : _t('Showing: 🎯 Training folders'),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
), ),
const Icon(Icons.filter_list), const Icon(Icons.filter_list),
@@ -738,7 +913,7 @@ Widget build(BuildContext context) {
// 📁 Folder list or empty state // 📁 Folder list or empty state
Expanded( Expanded(
child: _groupedFiles.isEmpty child: _groupedFiles.isEmpty
? const Center(child: Text("No saved target data found", style: TextStyle(fontSize: 18))) ? Center(child: Text(_t("No saved target data found"), style: const TextStyle(fontSize: 18)))
: Scrollbar( : Scrollbar(
thumbVisibility: true, // always show scrollbar thumbVisibility: true, // always show scrollbar
thickness: 6, // optional: adjust width thickness: 6, // optional: adjust width
@@ -771,9 +946,9 @@ void _showFolderFilterPopup(BuildContext context) {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( Text(
'Filter Folders', _t('Filter Folders'),
style: TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -782,21 +957,22 @@ void _showFolderFilterPopup(BuildContext context) {
const SizedBox(height: 24), const SizedBox(height: 24),
// Folder type filters // Folder type filters
_buildStyledFilterButton('all', '📂 All folders', context), _buildStyledFilterButton('all', _t('📂 All folders'), context),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildStyledFilterButton('tournament', '🏆 Tournament', context), _buildStyledFilterButton('tournament', _t('🏆 Tournament'), context),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildStyledFilterButton('training', '🎯 Training', context), _buildStyledFilterButton('training', _t('🎯 Training'), context),
const SizedBox(height: 24), const SizedBox(height: 24),
const Text( Text(
'Sort By', _t('Sort By'),
style: TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w500), style: const TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w500),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Expanded( Expanded(
child: GestureDetector( child: GestureDetector(
@@ -805,20 +981,23 @@ void _showFolderFilterPopup(BuildContext context) {
this.setState(() {}); // 👈 refresh parent widget this.setState(() {}); // 👈 refresh parent widget
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
margin: const EdgeInsets.only(right: 8), margin: const EdgeInsets.only(right: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _sortMode == 'date' ? _bulletColor : Colors.grey[800], color: _sortMode == 'date' ? _bulletColor : Colors.grey[800],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Center( child: Center(
child: Text( child: Text(
'📅 Sort by Date', _t('📅 Sort by Date'),
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: _sortMode == 'date' ? Colors.white : Colors.white70, color: _sortMode == 'date' ? Colors.white : Colors.white70,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 15, fontSize: 14,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
), ),
@@ -831,19 +1010,23 @@ void _showFolderFilterPopup(BuildContext context) {
this.setState(() {}); // 👈 refresh parent widget this.setState(() {}); // 👈 refresh parent widget
}, },
child: Container( child: Container(
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
margin: const EdgeInsets.only(left: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: _sortMode == 'score' ? _bulletColor : Colors.grey[800], color: _sortMode == 'score' ? _bulletColor : Colors.grey[800],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: Center( child: Center(
child: Text( child: Text(
'🏆 Sort by Score', _t('🏆 Sort by Score'),
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: _sortMode == 'score' ? Colors.white : Colors.white70, color: _sortMode == 'score' ? Colors.white : Colors.white70,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 15, fontSize: 14,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
), ),
+145 -269
View File
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../services/google_calendar_service.dart'; import '../services/google_calendar_service.dart';
import '../utils/translations.dart';
class CalendarScreen extends StatefulWidget { class CalendarScreen extends StatefulWidget {
const CalendarScreen({super.key}); const CalendarScreen({super.key});
@@ -16,14 +18,28 @@ class _CalendarScreenState extends State<CalendarScreen> {
List<CalendarEvent> _events = []; List<CalendarEvent> _events = [];
String? _errorMessage; String? _errorMessage;
String _bulletColor = 'Blue'; String _bulletColor = 'Blue';
String _selectedLanguage = 'English';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initializeDateFormatting('sl_SI', null); // Initialize Slovenian locale
_loadBulletColor(); _loadBulletColor();
_loadLanguage();
_loadEvents(); _loadEvents();
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
@override @override
void dispose() { void dispose() {
_calendarService.dispose(); _calendarService.dispose();
@@ -128,15 +144,21 @@ class _CalendarScreenState extends State<CalendarScreen> {
return a.startTime!.compareTo(b.startTime!); return a.startTime!.compareTo(b.startTime!);
}); });
setState(() { if (mounted) {
_events = filteredEvents; setState(() {
}); _events = filteredEvents;
});
}
} catch (e) { } catch (e) {
setState(() { if (mounted) {
_errorMessage = 'Error loading events: $e'; setState(() {
}); _errorMessage = 'Error loading events: $e';
});
}
} finally { } finally {
setState(() => _isLoading = false); if (mounted) {
setState(() => _isLoading = false);
}
} }
} }
@@ -172,7 +194,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
ElevatedButton.icon( ElevatedButton.icon(
onPressed: _loadEvents, onPressed: _loadEvents,
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
label: const Text('Retry'), label: Text(_t('Retry')),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: accentColor, backgroundColor: accentColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@@ -199,13 +221,13 @@ class _CalendarScreenState extends State<CalendarScreen> {
children: [ children: [
Icon(Icons.event_available, size: 64, color: Colors.grey[400]), Icon(Icons.event_available, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
'No upcoming events', _t('No upcoming events'),
style: TextStyle(fontSize: 16, color: Colors.grey), style: const TextStyle(fontSize: 16, color: Colors.grey),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Pull down to refresh', _t('Pull down to refresh'),
style: TextStyle(fontSize: 14, color: Colors.grey[400]), style: TextStyle(fontSize: 14, color: Colors.grey[400]),
), ),
], ],
@@ -229,15 +251,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
final startTime = event.startTime; final startTime = event.startTime;
final isAllDay = event.isAllDay; final isAllDay = event.isAllDay;
String timeText;
if (isAllDay) {
timeText = 'All day';
} else if (startTime != null) {
timeText = DateFormat('HH:mm').format(startTime.toLocal());
} else {
timeText = '';
}
// Format the date for display // Format the date for display
String dateText = ''; String dateText = '';
bool isToday = false; bool isToday = false;
@@ -248,300 +261,163 @@ class _CalendarScreenState extends State<CalendarScreen> {
final difference = eventDay.difference(today).inDays; final difference = eventDay.difference(today).inDays;
if (difference == 0) { if (difference == 0) {
dateText = 'Today'; dateText = _t('Today');
isToday = true; isToday = true;
} else if (difference == 1) { } else if (difference == 1) {
dateText = 'Tomorrow'; dateText = _t('Tomorrow');
} else { } else {
dateText = DateFormat('EEEE, MMMM d').format(startTime); // Use locale based on selected language
final locale = _selectedLanguage == 'Slovenian' ? 'sl_SI' : 'en_US';
dateText = DateFormat('EEEE, MMMM d', locale).format(startTime);
// Capitalize first letter of day and month
if (dateText.isNotEmpty) {
dateText = dateText[0].toUpperCase() + dateText.substring(1);
}
} }
} }
return Container( return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.grey[300],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
), ),
child: Material( child: Column(
color: Colors.transparent, crossAxisAlignment: CrossAxisAlignment.start,
child: InkWell( children: [
onTap: () => _showEventDetails(event, accentColor), // Date header
borderRadius: BorderRadius.circular(12), Container(
child: Column( width: double.infinity,
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [ decoration: BoxDecoration(
// Date header color: isToday
Container( ? accentColor.withValues(alpha: 0.15)
width: double.infinity, : Colors.grey[200],
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), borderRadius: const BorderRadius.only(
decoration: BoxDecoration( topLeft: Radius.circular(12),
color: isToday topRight: Radius.circular(12),
? accentColor.withValues(alpha: 0.15) ),
: Colors.grey[50], ),
borderRadius: const BorderRadius.only( child: Row(
topLeft: Radius.circular(12), children: [
topRight: Radius.circular(12), // Small colored indicator
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: accentColor,
borderRadius: BorderRadius.circular(2),
), ),
), ),
child: Row( const SizedBox(width: 8),
children: [ Text(
Container( dateText,
width: 4, style: TextStyle(
height: 16, fontSize: 14,
decoration: BoxDecoration( fontWeight: FontWeight.bold,
color: accentColor, color: isToday ? accentColor : Colors.black87,
borderRadius: BorderRadius.circular(2), ),
),
),
const SizedBox(width: 8),
Text(
dateText,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: isToday ? accentColor : Colors.black87,
),
),
if (isToday) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: accentColor,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'NOW',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
],
), ),
), if (isToday) ...[
// Time and title const SizedBox(width: 8),
Padding( Container(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
child: Row( decoration: BoxDecoration(
crossAxisAlignment: CrossAxisAlignment.center, color: accentColor,
children: [ borderRadius: BorderRadius.circular(8),
// Time indicator with accent color ),
Container( child: Text(
width: 60, _t('NOW'),
height: 60, style: const TextStyle(
decoration: BoxDecoration( fontSize: 10,
color: accentColor.withValues(alpha: 0.1), fontWeight: FontWeight.bold,
borderRadius: BorderRadius.circular(12), color: Colors.white,
),
child: Center(
child: Text(
timeText,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: isAllDay ? 12 : 14,
fontWeight: FontWeight.bold,
color: accentColor,
),
),
), ),
), ),
const SizedBox(width: 16), ),
// Title ],
],
),
),
// Main content
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Title with icon
Row(
children: [
Icon(Icons.event, size: 20, color: accentColor),
const SizedBox(width: 8),
Expanded( Expanded(
child: Text( child: Text(
event.summary ?? 'Untitled Event', event.summary ?? _t('Untitled Event'),
style: const TextStyle( style: const TextStyle(
fontSize: 15, fontSize: 15,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Colors.black87, color: Colors.black87,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 8),
// Arrow icon
Icon(
Icons.arrow_forward_ios,
size: 16,
color: Colors.grey[400],
),
], ],
), ),
), const SizedBox(height: 8),
], // Time range (if not all day)
), if (!isAllDay && startTime != null && event.endTime != null) ...[
), _buildDetailRowInline(
), icon: Icons.access_time,
); value: '${DateFormat('HH:mm').format(startTime.toLocal())} - ${DateFormat('HH:mm').format(event.endTime!.toLocal())}',
} accentColor: accentColor,
void _showEventDetails(CalendarEvent event, Color accentColor) {
final startTime = event.startTime;
final endTime = event.endTime;
final isAllDay = event.isAllDay;
showDialog(
context: context,
builder: (context) => Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
constraints: const BoxConstraints(maxWidth: 400),
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 4,
height: 24,
decoration: BoxDecoration(
color: accentColor,
borderRadius: BorderRadius.circular(2),
),
), ),
const SizedBox(width: 12), const SizedBox(height: 6),
Expanded( ],
child: Text( if (isAllDay) ...[
event.summary ?? 'Event Details', _buildDetailRowInline(
style: const TextStyle( icon: Icons.access_time,
fontSize: 20, value: _t('All day'),
fontWeight: FontWeight.bold, accentColor: accentColor,
color: Colors.black87, ),
), const SizedBox(height: 6),
), ],
// Location
if (event.location != null && event.location!.isNotEmpty) ...[
_buildDetailRowInline(
icon: Icons.location_on,
value: event.location!,
accentColor: accentColor,
), ),
], ],
),
const SizedBox(height: 24),
if (startTime != null) ...[
_buildDetailRow(
icon: Icons.calendar_today,
label: 'Date',
value: DateFormat('EEEE, MMMM d, y').format(startTime),
accentColor: accentColor,
),
const SizedBox(height: 16),
], ],
if (!isAllDay && startTime != null && endTime != null) ...[ ),
_buildDetailRow(
icon: Icons.access_time,
label: 'Time',
value:
'${DateFormat('HH:mm').format(startTime.toLocal())} - ${DateFormat('HH:mm').format(endTime.toLocal())}',
accentColor: accentColor,
),
const SizedBox(height: 16),
],
if (isAllDay) ...[
_buildDetailRow(
icon: Icons.access_time,
label: 'Duration',
value: 'All day event',
accentColor: accentColor,
),
const SizedBox(height: 16),
],
if (event.location != null && event.location!.isNotEmpty) ...[
_buildDetailRow(
icon: Icons.location_on,
label: 'Location',
value: event.location!,
accentColor: accentColor,
),
const SizedBox(height: 16),
],
if (event.description != null && event.description!.isNotEmpty) ...[
_buildDetailRow(
icon: Icons.notes,
label: 'Notes',
value: event.description!,
accentColor: accentColor,
),
const SizedBox(height: 16),
],
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => Navigator.pop(context),
style: ElevatedButton.styleFrom(
backgroundColor: accentColor,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'Close',
style: TextStyle(fontWeight: FontWeight.w600),
),
),
),
],
), ),
), ],
), ),
); );
} }
Widget _buildDetailRow({ Widget _buildDetailRowInline({
required IconData icon, required IconData icon,
required String label,
required String value, required String value,
required Color accentColor, required Color accentColor,
}) { }) {
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Icon(icon, size: 16, color: accentColor),
padding: const EdgeInsets.all(8), const SizedBox(width: 6),
decoration: BoxDecoration(
color: accentColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 20, color: accentColor),
),
const SizedBox(width: 12),
Expanded( Expanded(
child: Column( child: Text(
crossAxisAlignment: CrossAxisAlignment.start, value,
children: [ style: const TextStyle(
Text( fontSize: 13,
label, color: Colors.black87,
style: TextStyle( ),
fontSize: 12, maxLines: 1,
fontWeight: FontWeight.w600, overflow: TextOverflow.ellipsis,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 14,
color: Colors.black87,
),
),
],
), ),
), ),
], ],
); );
} }
} }
+23 -8
View File
@@ -5,6 +5,7 @@ import 'package:image/image.dart' as img;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'yolo_detector.dart'; import 'yolo_detector.dart';
import 'preview_screen.dart'; import 'preview_screen.dart';
import '../utils/translations.dart';
class CameraScreen extends StatefulWidget { class CameraScreen extends StatefulWidget {
const CameraScreen({super.key}); const CameraScreen({super.key});
@@ -21,6 +22,7 @@ class _CameraScreenState extends State<CameraScreen> {
// The variable to hold the number of bullets entered by the user (default set to 5) // The variable to hold the number of bullets entered by the user (default set to 5)
int _numberOfBullets = 5; int _numberOfBullets = 5;
String _selectedLanguage = 'English';
@override @override
void initState() { void initState() {
@@ -28,9 +30,21 @@ class _CameraScreenState extends State<CameraScreen> {
_loadBulletCount(); _loadBulletCount();
_initCamera(); _initCamera();
_loadBulletColor(); // 👈 _loadBulletColor(); // 👈
_loadLanguage();
YOLODetector.create().then((detector) => _yoloDetector = detector); YOLODetector.create().then((detector) => _yoloDetector = detector);
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
Future<void> _loadBulletColor() async { Future<void> _loadBulletColor() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final selected = prefs.getString('bulletColor') ?? 'Blue'; final selected = prefs.getString('bulletColor') ?? 'Blue';
@@ -168,13 +182,14 @@ Color _getColorFromName(String colorName) {
padding: const EdgeInsets.all(24.0), // Increased padding for better spacing padding: const EdgeInsets.all(24.0), // Increased padding for better spacing
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.only(bottom: 16.0), padding: const EdgeInsets.only(bottom: 16.0),
child: Text( child: Text(
'Select the number of bullets', _t('Select the number of bullets'),
style: TextStyle( textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -245,9 +260,9 @@ Color _getColorFromName(String colorName) {
extendBody: true, extendBody: true,
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
title: const Text( title: Text(
"Align the target inside the square", _t("Align the target inside the square"),
style: TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: Colors.white,
+37 -5
View File
@@ -6,6 +6,8 @@ import 'target_screen.dart';
import '../widgets/bottom_nav.dart'; import '../widgets/bottom_nav.dart';
//import 'login_screen.dart'; //import 'login_screen.dart';
import 'camera_screen.dart'; import 'camera_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
@@ -17,6 +19,7 @@ class HomeScreen extends StatefulWidget {
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 1; // Default to Shooting Screen int _selectedIndex = 1; // Default to Shooting Screen
String _selectedLanguage = 'English';
static final List<Widget> _pages = <Widget>[ static final List<Widget> _pages = <Widget>[
BookScreen(), BookScreen(),
@@ -26,7 +29,7 @@ class _HomeScreenState extends State<HomeScreen> {
SettingsScreen(), SettingsScreen(),
]; ];
static final List<String> _pageTitles = [ final List<String> _pageTitles = [
'Book', 'Book',
'Shooting', 'Shooting',
'Scan', 'Scan',
@@ -34,6 +37,23 @@ class _HomeScreenState extends State<HomeScreen> {
'Settings' 'Settings'
]; ];
@override
void initState() {
super.initState();
_loadLanguage();
}
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
void _onItemTapped(int index) { void _onItemTapped(int index) {
if (index == 2) { if (index == 2) {
_openCameraScan(); _openCameraScan();
@@ -42,6 +62,8 @@ class _HomeScreenState extends State<HomeScreen> {
setState(() { setState(() {
_selectedIndex = index; _selectedIndex = index;
}); });
// Reload language when switching screens to pick up any changes from Settings
_loadLanguage();
} }
//void _logout() { //void _logout() {
@@ -69,7 +91,7 @@ class _HomeScreenState extends State<HomeScreen> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
_pageTitles[_selectedIndex], _t(_pageTitles[_selectedIndex]),
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
@@ -77,9 +99,18 @@ class _HomeScreenState extends State<HomeScreen> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
scrolledUnderElevation: 0, // For Material 3 (Flutter 3.7+) scrolledUnderElevation: 0, // For Material 3 (Flutter 3.7+)
actions: [ actions: [
IconButton( Padding(
icon: const Icon(Icons.logout, color: Colors.red), padding: const EdgeInsets.only(right: 16.0),
onPressed: null, // disables the button child: Center(
child: Text(
'V1.0.0',
style: TextStyle(
color: Colors.grey[400],
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
), ),
], ],
), ),
@@ -87,6 +118,7 @@ class _HomeScreenState extends State<HomeScreen> {
bottomNavigationBar: BottomNavBar( bottomNavigationBar: BottomNavBar(
selectedIndex: _selectedIndex, selectedIndex: _selectedIndex,
onItemTapped: _onItemTapped, onItemTapped: _onItemTapped,
selectedLanguage: _selectedLanguage,
), ),
); );
} }
+118 -40
View File
@@ -8,6 +8,7 @@ import 'yolo_detector.dart';
import 'dart:convert'; // For JSON encoding import 'dart:convert'; // For JSON encoding
import 'dart:math'; import 'dart:math';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
class PreviewScreen extends StatefulWidget { class PreviewScreen extends StatefulWidget {
final String imagePath; final String imagePath;
@@ -56,6 +57,7 @@ class _PreviewScreenState extends State<PreviewScreen> {
Color get _activeColor => _isEditMode ? _editColor : _bulletColor; Color get _activeColor => _isEditMode ? _editColor : _bulletColor;
Color get _activeColorWithOpacity => _activeColor.withOpacity(_bulletOpacity); Color get _activeColorWithOpacity => _activeColor.withOpacity(_bulletOpacity);
String _selectedLanguage = 'English';
@override @override
void initState() { void initState() {
@@ -63,9 +65,21 @@ class _PreviewScreenState extends State<PreviewScreen> {
_realImageDetections = widget.detections; _realImageDetections = widget.detections;
_targetImageFuture = _loadAndDecodeTargetImage(); _targetImageFuture = _loadAndDecodeTargetImage();
_loadBulletColor(); // 👈 Add this _loadBulletColor(); // 👈 Add this
_loadLanguage();
_runDetectionAndMapping(); _runDetectionAndMapping();
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
Future<void> _loadBulletColor() async { Future<void> _loadBulletColor() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final selectedColor = prefs.getString('bulletColor') ?? 'Blue'; final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
@@ -241,9 +255,9 @@ void _manualScoreEntry() {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( Text(
'Enter Missing Bullet Scores', _t('Enter Missing Bullet Scores'),
style: TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -263,7 +277,7 @@ void _manualScoreEntry() {
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
style: const TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Bullet Score ${_mappedBullets.length + index + 1}', labelText: '${_t('Bullet Score')} ${_mappedBullets.length + index + 1}',
labelStyle: const TextStyle(color: Colors.white54), labelStyle: const TextStyle(color: Colors.white54),
filled: true, filled: true,
fillColor: Colors.grey[800], fillColor: Colors.grey[800],
@@ -308,7 +322,7 @@ void _manualScoreEntry() {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text('Add Bullets'), child: Text(_t('Add Bullets')),
), ),
), ),
], ],
@@ -355,9 +369,10 @@ void _manualScoreEntry() {
// Use timestamp for a unique file name // Use timestamp for a unique file name
final now = DateTime.now(); final now = DateTime.now();
final dateString = "${now.year}-${now.month}-${now.day}"; final dateString = "${now.day.toString().padLeft(2, '0')}/${now.month.toString().padLeft(2, '0')}/${now.year}";
final dateStringFile = "${now.year}-${now.month}-${now.day}";
final timeString = "${now.hour}-${now.minute}-${now.second}"; final timeString = "${now.hour}-${now.minute}-${now.second}";
final fileName = "target_${dateString}_$timeString.json"; final fileName = "target_${dateStringFile}_$timeString.json";
final filePath = '${saveDirectory.path}/$fileName'; final filePath = '${saveDirectory.path}/$fileName';
// Prepare target data to save // Prepare target data to save
@@ -500,20 +515,25 @@ void _moveBullet(Offset offsetDelta) {
int trainingCount = 1; int trainingCount = 1;
int tournamentCount = 1; int tournamentCount = 1;
// Get localized folder names
final trainingWord = _t('Training');
final tournamentWord = _t('Tournament');
for (var folder in folders) { for (var folder in folders) {
final name = folder.path.split('/').last; final name = folder.path.split('/').last;
if (name.startsWith('Training')) { // Check for both English and Slovenian names
final num = int.tryParse(name.replaceAll('Training ', '')); if (name.startsWith('Training') || name.startsWith('Trening')) {
final num = int.tryParse(name.replaceAll('Training ', '').replaceAll('Trening ', ''));
if (num != null && num >= trainingCount) trainingCount = num + 1; if (num != null && num >= trainingCount) trainingCount = num + 1;
} }
if (name.startsWith('Tournament')) { if (name.startsWith('Tournament') || name.startsWith('Tekmovanje')) {
final num = int.tryParse(name.replaceAll('Tournament ', '')); final num = int.tryParse(name.replaceAll('Tournament ', '').replaceAll('Tekmovanje ', ''));
if (num != null && num >= tournamentCount) tournamentCount = num + 1; if (num != null && num >= tournamentCount) tournamentCount = num + 1;
} }
} }
final String nextTraining = 'Training $trainingCount'; final String nextTraining = '$trainingWord $trainingCount';
final String nextTournament = 'Tournament $tournamentCount'; final String nextTournament = '$tournamentWord $tournamentCount';
return showDialog<String>( return showDialog<String>(
context: context, context: context,
@@ -526,9 +546,9 @@ void _moveBullet(Offset offsetDelta) {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( Text(
'Save Target Data', _t('Save Target Data'),
style: TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -536,7 +556,7 @@ void _moveBullet(Offset offsetDelta) {
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
// 🎯 Training Button // 🎯 Training Button (New folder)
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
@@ -554,12 +574,24 @@ void _moveBullet(Offset offsetDelta) {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: Text('🎯 Save to "$nextTraining"'), child: Column(
children: [
Text(
'🎯 $nextTraining',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 4),
Text(
_t('New folder'),
style: const TextStyle(fontSize: 12, color: Colors.white70),
),
],
),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
// 🏆 Tournament Button // 🏆 Tournament Button (New folder)
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
@@ -577,7 +609,19 @@ void _moveBullet(Offset offsetDelta) {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: Text('🏆 Save to "$nextTournament"'), child: Column(
children: [
Text(
'🏆 $nextTournament',
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 4),
Text(
_t('New folder'),
style: const TextStyle(fontSize: 12, color: Colors.white70),
),
],
),
), ),
), ),
if (lastFolder != null) ...[ if (lastFolder != null) ...[
@@ -613,13 +657,13 @@ void _moveBullet(Offset offsetDelta) {
child: Column( child: Column(
children: [ children: [
Text( Text(
'${lastIsTournament ? "🏆" : "🎯"} Save to "$lastFolder"', '${lastIsTournament ? "🏆" : "🎯"} $lastFolder',
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
const Text( Text(
'Last used folder', _t('Last used folder'),
style: TextStyle(fontSize: 12, color: Colors.white70), style: const TextStyle(fontSize: 12, color: Colors.white70),
), ),
], ],
), ),
@@ -715,9 +759,9 @@ Widget build(BuildContext context) {
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
iconTheme: const IconThemeData(color: Colors.white), iconTheme: const IconThemeData(color: Colors.white),
title: const Text( title: Text(
'Detection Preview', _t('Detection Preview'),
style: TextStyle(color: Colors.white), style: const TextStyle(color: Colors.white),
), ),
actions: [ actions: [
IconButton( IconButton(
@@ -735,14 +779,14 @@ Widget build(BuildContext context) {
}); });
}, },
tooltip: _mode == PreviewMode.mappedTarget tooltip: _mode == PreviewMode.mappedTarget
? "Show Camera Detections" ? _t("Show Camera Detections")
: "Show Mapped Target", : _t("Show Mapped Target"),
), ),
IconButton( IconButton(
icon: const Icon(Icons.save), icon: const Icon(Icons.save),
color: Colors.white, color: Colors.white,
onPressed: _saveTargetData, onPressed: _saveTargetData,
tooltip: "Save Target Data", tooltip: _t("Save Target Data"),
), ),
], ],
), ),
@@ -1045,17 +1089,51 @@ Widget build(BuildContext context) {
), ),
bottomNavigationBar: BottomAppBar( bottomNavigationBar: BottomAppBar(
color: Colors.grey[900], color: Colors.grey[900],
padding: EdgeInsets.zero,
child: SizedBox( child: SizedBox(
height: 56, height: 150,
child: Center( child: Row(
child: Text( mainAxisAlignment: MainAxisAlignment.center,
"🎯 Total Score: $_totalScore", children: [
style: const TextStyle( // Left side - Label (darker grey)
fontSize: 28, const Padding(
fontWeight: FontWeight.bold, padding: EdgeInsets.only(right: 16),
color: Colors.white, child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"🎯 Total Score:",
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
), ),
), // Right side - Score badge (grey with rounded corners)
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.grey[700]!,
width: 2,
),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"$_totalScore",
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
],
), ),
), ),
), ),
+179 -21
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
class SettingsScreen extends StatefulWidget { class SettingsScreen extends StatefulWidget {
const SettingsScreen({super.key}); const SettingsScreen({super.key});
@@ -40,16 +41,26 @@ class _SettingsScreenState extends State<SettingsScreen> {
'Grey', 'Grey',
]; ];
String _selectedLanguage = 'English';
final List<Map<String, String>> _languages = [
{'code': 'en', 'name': 'English', 'flag': '🇬🇧'},
{'code': 'sl', 'name': 'Slovenian', 'flag': '🇸🇮'},
];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadCameraIPs(); _loadCameraIPs();
_loadBulletColor(); _loadBulletColor();
_loadBulletColor(); _loadLanguage();
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Color _getColorFromName(String colorName) { Color _getColorFromName(String colorName) {
switch (colorName.toLowerCase()) { switch (colorName.toLowerCase()) {
case 'blue': case 'blue':
@@ -108,6 +119,21 @@ Future<void> _saveBulletColor(String color) async {
setState(() => _selectedBulletColor = color); setState(() => _selectedBulletColor = color);
} }
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
String savedLanguage = prefs.getString('language') ?? 'English';
setState(() {
_selectedLanguage = savedLanguage;
});
}
Future<void> _saveLanguage(String language) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('language', language);
setState(() => _selectedLanguage = language);
}
Future<void> _loadCameraIPs() async { Future<void> _loadCameraIPs() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@@ -164,7 +190,7 @@ Future<void> _saveBulletColor(String color) async {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Text(
"Camera ${index + 1}", "${_t('Camera')} ${index + 1}",
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
@@ -183,7 +209,7 @@ Future<void> _saveBulletColor(String color) async {
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Colors.grey[800], fillColor: Colors.grey[800],
hintText: "Enter Camera IP", hintText: _t("Enter Camera IP"),
hintStyle: const TextStyle(color: Colors.white54), hintStyle: const TextStyle(color: Colors.white54),
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
@@ -207,7 +233,9 @@ Future<void> _saveBulletColor(String color) async {
String newIP = controller.text.trim(); String newIP = controller.text.trim();
if (newIP.isNotEmpty) { if (newIP.isNotEmpty) {
await _saveCameraIP(index, newIP); await _saveCameraIP(index, newIP);
Navigator.pop(context); if (context.mounted) {
Navigator.pop(context);
}
} }
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
@@ -218,7 +246,7 @@ Future<void> _saveBulletColor(String color) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text("Save"), child: Text(_t("Save")),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -229,7 +257,9 @@ Future<void> _saveBulletColor(String color) async {
child: ElevatedButton( child: ElevatedButton(
onPressed: () async { onPressed: () async {
await _clearCameraIP(index); await _clearCameraIP(index);
Navigator.pop(context); if (context.mounted) {
Navigator.pop(context);
}
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[700], backgroundColor: Colors.grey[700],
@@ -239,7 +269,7 @@ Future<void> _saveBulletColor(String color) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text("Reset to Default"), child: Text(_t("Reset to Default")),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -257,7 +287,7 @@ Future<void> _saveBulletColor(String color) async {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text("Cancel"), child: Text(_t("Cancel")),
), ),
), ),
], ],
@@ -280,6 +310,8 @@ Future<void> _saveBulletColor(String color) async {
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 8), const SizedBox(height: 8),
_buildLanguageSelector(),
const SizedBox(height: 16),
_buildBulletColorSelector(), _buildBulletColorSelector(),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -290,13 +322,61 @@ Future<void> _saveBulletColor(String color) async {
); );
} }
Widget _buildLanguageSelector() {
final selectedLang = _languages.firstWhere(
(lang) => lang['name'] == _selectedLanguage,
orElse: () => _languages[0],
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_t("Language"),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 8),
GestureDetector(
onTap: _showLanguagePopup,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Text(
selectedLang['flag'] ?? '🌐',
style: const TextStyle(fontSize: 20),
),
const SizedBox(width: 12),
Expanded(
child: Text(
_t(selectedLang['name'] ?? 'English'),
style: const TextStyle(color: Colors.black87, fontSize: 14),
),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
),
],
);
}
Widget _buildBulletColorSelector() { Widget _buildBulletColorSelector() {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
"Select Bullet Color", _t("Select Bullet Color"),
style: TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: Colors.black87,
@@ -325,7 +405,7 @@ Future<void> _saveBulletColor(String color) async {
), ),
Expanded( Expanded(
child: Text( child: Text(
_selectedBulletColor, _t(_selectedBulletColor),
style: const TextStyle(color: Colors.black87, fontSize: 14), style: const TextStyle(color: Colors.black87, fontSize: 14),
), ),
), ),
@@ -338,6 +418,82 @@ Future<void> _saveBulletColor(String color) async {
); );
} }
void _showLanguagePopup() {
showDialog(
context: context,
builder: (context) {
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: [
Text(
_t("Select Language"),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 20),
..._languages.map((lang) {
final isSelected = lang['name'] == _selectedLanguage;
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
await _saveLanguage(lang['name']!);
if (context.mounted) {
Navigator.of(context).pop();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: isSelected
? _getColorFromName(_selectedBulletColor)
: Colors.grey[800],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
lang['flag']!,
style: const TextStyle(fontSize: 20),
),
const SizedBox(width: 12),
Text(
_t(lang['name']!),
style: const TextStyle(fontSize: 16),
),
],
),
),
),
);
}),
const SizedBox(height: 8),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(_t("Cancel"), style: const TextStyle(color: Colors.white70)),
),
],
),
),
);
},
);
}
void _showBulletColorPopup() { void _showBulletColorPopup() {
showDialog( showDialog(
context: context, context: context,
@@ -350,9 +506,9 @@ void _showBulletColorPopup() {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( Text(
"Select Bullet Color", _t("Select Bullet Color"),
style: TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.white, color: Colors.white,
@@ -369,7 +525,9 @@ void _showBulletColorPopup() {
return GestureDetector( return GestureDetector(
onTap: () async { onTap: () async {
await _saveBulletColor(colorName); await _saveBulletColor(colorName);
Navigator.of(context).pop(); if (context.mounted) {
Navigator.of(context).pop();
}
}, },
child: Container( child: Container(
width: 48, width: 48,
@@ -389,7 +547,7 @@ void _showBulletColorPopup() {
const SizedBox(height: 20), const SizedBox(height: 20),
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text("Cancel", style: TextStyle(color: Colors.white70)), child: Text(_t("Cancel"), style: const TextStyle(color: Colors.white70)),
), ),
], ],
), ),
@@ -403,9 +561,9 @@ void _showBulletColorPopup() {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( Text(
"Camera Settings", _t("Camera Settings"),
style: TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: Colors.black87,
@@ -429,7 +587,7 @@ void _showBulletColorPopup() {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"Camera ${index + 1}", "${_t('Camera')} ${index + 1}",
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black87, color: Colors.black87,
+38 -20
View File
@@ -9,6 +9,7 @@ import 'package:image/image.dart' as img;
import 'yolo_detector.dart'; import 'yolo_detector.dart';
import 'preview_screen.dart'; import 'preview_screen.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
import '../utils/translations.dart';
enum ImageFilterType { none, greyscale, invert} enum ImageFilterType { none, greyscale, invert}
@@ -38,10 +39,16 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTick
Matrix4? _originalMatrix; Matrix4? _originalMatrix;
Color _bulletColor = Colors.blue; // default/fallback bullet color Color _bulletColor = Colors.blue; // default/fallback bullet color
String _selectedLanguage = 'English';
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadLanguage();
_loadStreamUrl(); _loadStreamUrl();
_loadBulletCount(); _loadBulletCount();
_loadSelectedFilter(); _loadSelectedFilter();
@@ -61,6 +68,13 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTick
); );
} }
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
Future<void> _loadBulletColor() async { Future<void> _loadBulletColor() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final selected = prefs.getString('bulletColor') ?? 'Blue'; final selected = prefs.getString('bulletColor') ?? 'Blue';
@@ -297,13 +311,14 @@ Color _getColorFromName(String colorName) {
padding: const EdgeInsets.all(24.0), // Increased padding for better spacing padding: const EdgeInsets.all(24.0), // Increased padding for better spacing
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Padding( Padding(
padding: EdgeInsets.only(bottom: 16.0), padding: const EdgeInsets.only(bottom: 16.0),
child: Text( child: Text(
'Select the number of bullets', _t('Select the number of bullets'),
style: TextStyle( textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -365,9 +380,9 @@ Color _getColorFromName(String colorName) {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const Text( Text(
'Choose a Filter', _t('Choose a Filter'),
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold), style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Wrap( Wrap(
@@ -377,6 +392,7 @@ Color _getColorFromName(String colorName) {
children: ImageFilterType.values.map((f) { children: ImageFilterType.values.map((f) {
final isSelected = _selectedFilter == f; final isSelected = _selectedFilter == f;
final label = f.toString().split('.').last; final label = f.toString().split('.').last;
final translatedLabel = _t(label[0].toUpperCase() + label.substring(1));
return ElevatedButton( return ElevatedButton(
onPressed: () async { onPressed: () async {
setState(() { setState(() {
@@ -384,13 +400,15 @@ Color _getColorFromName(String colorName) {
}); });
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setInt('selectedFilter', f.index); // Save filter index await prefs.setInt('selectedFilter', f.index); // Save filter index
Navigator.of(context).pop(); if (context.mounted) {
Navigator.of(context).pop();
}
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: isSelected ? _bulletColor : Colors.grey[800], backgroundColor: isSelected ? _bulletColor : Colors.grey[800],
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
child: Text(label[0].toUpperCase() + label.substring(1)), child: Text(translatedLabel),
); );
}).toList(), }).toList(),
), ),
@@ -455,7 +473,7 @@ void dispose() {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Target ${widget.cameraIndex}"), title: Text("${_t('Camera')} ${widget.cameraIndex}"),
backgroundColor: Colors.grey[900], backgroundColor: Colors.grey[900],
foregroundColor: Colors.white, foregroundColor: Colors.white,
leading: IconButton( leading: IconButton(
@@ -523,14 +541,14 @@ void dispose() {
Positioned.fill( Positioned.fill(
child: Container( child: Container(
color: Colors.black.withOpacity(0.4), color: Colors.black.withOpacity(0.4),
child: const Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
CircularProgressIndicator(color: Colors.white), const CircularProgressIndicator(color: Colors.white),
SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
"Processing image...", _t("Processing image..."),
style: TextStyle(color: Colors.white, fontSize: 16), style: const TextStyle(color: Colors.white, fontSize: 16),
), ),
], ],
), ),
@@ -571,14 +589,14 @@ void dispose() {
children: [ children: [
const Icon(Icons.error, color: Colors.red, size: 100), const Icon(Icons.error, color: Colors.red, size: 100),
const SizedBox(height: 20), const SizedBox(height: 20),
const Text( Text(
"Unable to load camera stream", _t("Unable to load camera stream"),
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold), style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
ElevatedButton( ElevatedButton(
onPressed: _loadStreamUrl, onPressed: _loadStreamUrl,
child: const Text("Retry"), child: Text(_t("Retry")),
), ),
], ],
); );
+102 -86
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'stream_screen.dart'; // Import the camera stream screen import 'stream_screen.dart'; // Import the camera stream screen
import '../utils/translations.dart';
class TargetScreen extends StatefulWidget { class TargetScreen extends StatefulWidget {
const TargetScreen({super.key}); const TargetScreen({super.key});
@@ -10,109 +11,124 @@ class TargetScreen extends StatefulWidget {
} }
class _TargetScreenState extends State<TargetScreen> { class _TargetScreenState extends State<TargetScreen> {
List<bool> _cameraButtonsEnabled = List.generate(6, (_) => false); // Initially, all buttons are disabled List<bool> _cameraButtonsEnabled =
List.generate(6, (_) => false); // Initially, all buttons are disabled
String _selectedLanguage = 'English';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadLanguage();
_loadCameraData(); _loadCameraData();
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
/// Load the camera IPs from SharedPreferences and update button status /// Load the camera IPs from SharedPreferences and update button status
Future<void> _loadCameraData() async { Future<void> _loadCameraData() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final List<String> defaultIPs = [ final List<String> defaultIPs = [
'192.168.0.134:9081', '192.168.0.134:9081',
'192.168.0.134:9082', '192.168.0.134:9082',
'192.168.0.134:9083', '192.168.0.134:9083',
'192.168.0.134:9084', '192.168.0.134:9084',
'192.168.0.134:9085', '192.168.0.134:9085',
'192.168.0.134:9086', '192.168.0.134:9086',
]; ];
List<bool> cameraDataStatus = List.generate(6, (index) { List<bool> cameraDataStatus = List.generate(6, (index) {
String key = 'cameraIP_${index + 1}'; String key = 'cameraIP_${index + 1}';
if (!prefs.containsKey(key)) { if (!prefs.containsKey(key)) {
prefs.setString(key, defaultIPs[index]); prefs.setString(key, defaultIPs[index]);
return true; // Default set return true; // Default set
} }
String? ip = prefs.getString(key); String? ip = prefs.getString(key);
return ip != null && ip.isNotEmpty && ip != 'Not Set'; return ip != null && ip.isNotEmpty && ip != 'Not Set';
}); });
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_cameraButtonsEnabled = cameraDataStatus; _cameraButtonsEnabled = cameraDataStatus;
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Padding( body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column( child: Column(
children: [ children: [
Expanded( Expanded(
child: GridView.builder( child: GridView.builder(
itemCount: 6, itemCount: 6,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
mainAxisSpacing: 16, mainAxisSpacing: 16,
crossAxisSpacing: 16, crossAxisSpacing: 16,
childAspectRatio: 1, // square-ish buttons childAspectRatio: 1, // square-ish buttons
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final isEnabled = _cameraButtonsEnabled[index]; final isEnabled = _cameraButtonsEnabled[index];
return InkWell( return InkWell(
onTap: isEnabled onTap: isEnabled
? () { ? () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CameraStreamScreen(cameraIndex: index + 1), builder: (context) =>
), CameraStreamScreen(cameraIndex: index + 1),
); ),
} );
: null, }
borderRadius: BorderRadius.circular(16), : null,
child: Container( borderRadius: BorderRadius.circular(16),
decoration: BoxDecoration( child: Container(
color: isEnabled ? Colors.grey[900] : Colors.grey[300], decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16), color: isEnabled ? Colors.grey[900] : Colors.grey[300],
), borderRadius: BorderRadius.circular(16),
padding: const EdgeInsets.all(16), ),
child: Column( padding: const EdgeInsets.all(16),
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.center,
Icon( children: [
Icons.videocam_rounded, Icon(
color: isEnabled ? Colors.white : Colors.grey[500], Icons.videocam_rounded,
size: 36, color: isEnabled ? Colors.white : Colors.grey[500],
), size: 36,
const SizedBox(height: 12),
Text(
"Target ${index + 1}",
textAlign: TextAlign.center,
style: TextStyle(
color: isEnabled ? Colors.white : Colors.grey[600],
fontSize: 20,
fontWeight: FontWeight.w600,
), ),
), const SizedBox(height: 12),
], Text(
"${_t('Camera')} ${index + 1}",
textAlign: TextAlign.center,
style: TextStyle(
color:
isEnabled ? Colors.white : Colors.grey[600],
fontSize: 20,
fontWeight: FontWeight.w600,
),
),
],
),
), ),
), );
); },
}, ),
), ),
), ],
], ),
), ),
), );
); }
}
} }
+68 -17
View File
@@ -5,6 +5,7 @@ import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert'; import 'dart:convert';
import 'package:image/image.dart' as img; import 'package:image/image.dart' as img;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
class TargetViewScreen extends StatefulWidget { class TargetViewScreen extends StatefulWidget {
final List<File> jsonFiles; final List<File> jsonFiles;
@@ -38,15 +39,28 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
final double bulletSize = 19; final double bulletSize = 19;
Color _bulletColor = Colors.blue; // default/fallback bullet color Color _bulletColor = Colors.blue; // default/fallback bullet color
String _selectedLanguage = 'English';
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadBulletColor(); _loadBulletColor();
_loadLanguage();
_loadJsonAndImage(); _loadJsonAndImage();
} }
String _t(String key) {
return AppTranslations.translate(key, _selectedLanguage);
}
Future<void> _loadLanguage() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_selectedLanguage = prefs.getString('language') ?? 'English';
});
}
Future<void> _loadBulletColor() async { Future<void> _loadBulletColor() async {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final selectedColor = prefs.getString('bulletColor') ?? 'Blue'; final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
@@ -116,7 +130,25 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
final jsonString = await currentFile.readAsString(); final jsonString = await currentFile.readAsString();
final data = jsonDecode(jsonString); final data = jsonDecode(jsonString);
_date = data['date'] ?? 'Unknown Date'; // Parse and convert date format from yyyy-m-d to dd/MM/yyyy
String rawDate = data['date'] ?? 'Unknown Date';
if (rawDate != 'Unknown Date' && rawDate.contains('-')) {
try {
final parts = rawDate.split('-');
if (parts.length == 3) {
final year = parts[0];
final month = parts[1].padLeft(2, '0');
final day = parts[2].padLeft(2, '0');
_date = '$day/$month/$year';
} else {
_date = rawDate;
}
} catch (e) {
_date = rawDate;
}
} else {
_date = rawDate;
}
final bulletsJson = data['bullets'] as List; final bulletsJson = data['bullets'] as List;
_bullets = bulletsJson.map((b) => Offset(b['x'], b['y'])).toList(); _bullets = bulletsJson.map((b) => Offset(b['x'], b['y'])).toList();
@@ -185,9 +217,9 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Text( Text(
'Delete Target', _t('Delete Target'),
style: TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -195,9 +227,9 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
const Text( Text(
"Are you sure you want to delete this target?", _t("Are you sure you want to delete this target?"),
style: TextStyle(color: Colors.white70, fontSize: 16), style: const TextStyle(color: Colors.white70, fontSize: 16),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@@ -218,7 +250,7 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
), ),
child: const Text("Yes, delete"), child: Text(_t("Yes, delete")),
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -339,17 +371,36 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
), ),
bottomNavigationBar: BottomAppBar( bottomNavigationBar: BottomAppBar(
color: Colors.grey[900], color: Colors.grey[900],
padding: EdgeInsets.zero,
child: SizedBox( child: SizedBox(
height: 56, height: 150,
child: Center( child: Row(
child: Text( mainAxisAlignment: MainAxisAlignment.center,
"🎯 Total Score: $_totalScore", children: [
style: const TextStyle( // Combined badge with text and score
fontSize: 22, Container(
fontWeight: FontWeight.bold, padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
color: Colors.white, decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.grey[700]!,
width: 2,
),
),
child: FittedBox(
fit: BoxFit.scaleDown,
child: Text(
"🎯 ${_t('Total score')} $_totalScore",
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
), ),
), ],
), ),
), ),
), ),
+379
View File
@@ -0,0 +1,379 @@
class AppTranslations {
static Map<String, Map<String, String>> translations = {
// Language names
'Language': {
'English': 'Language',
'Slovenian': 'Jezik',
},
'Select Language': {
'English': 'Select Language',
'Slovenian': 'Izberi jezik',
},
'English': {
'English': 'English',
'Slovenian': 'Angleščina',
},
'Slovenian': {
'English': 'Slovenian',
'Slovenian': 'Slovenščina',
},
// Bullet color
'Select Bullet Color': {
'English': 'Select Bullet Color',
'Slovenian': 'Izberi barvo zadetka',
},
// Camera settings
'Camera Settings': {
'English': 'Camera Settings',
'Slovenian': 'Nastavitve kamere',
},
'Camera': {
'English': 'Camera',
'Slovenian': 'Kamera',
},
'Enter Camera IP': {
'English': 'Enter Camera IP',
'Slovenian': 'Vnesi IP kamere',
},
'Save': {
'English': 'Save',
'Slovenian': 'Shrani',
},
'Reset to Default': {
'English': 'Reset to Default',
'Slovenian': 'Ponastavi na privzeto',
},
'Cancel': {
'English': 'Cancel',
'Slovenian': 'Prekliči',
},
// Color names
'Blue': {
'English': 'Blue',
'Slovenian': 'Modra',
},
'Green': {
'English': 'Green',
'Slovenian': 'Zelena',
},
'Yellow': {
'English': 'Yellow',
'Slovenian': 'Rumena',
},
'Purple': {
'English': 'Purple',
'Slovenian': 'Vijolična',
},
'Cyan': {
'English': 'Cyan',
'Slovenian': 'Cian',
},
'Teal': {
'English': 'Teal',
'Slovenian': 'Modrozelena',
},
'Indigo': {
'English': 'Indigo',
'Slovenian': 'Indigo',
},
'Pink': {
'English': 'Pink',
'Slovenian': 'Rožnata',
},
'Lime': {
'English': 'Lime',
'Slovenian': 'Limona',
},
'Mint': {
'English': 'Mint',
'Slovenian': 'Meta',
},
'Orange': {
'English': 'Orange',
'Slovenian': 'Oranžna',
},
'Violet': {
'English': 'Violet',
'Slovenian': 'Vijolična',
},
'Amber': {
'English': 'Amber',
'Slovenian': 'Jantarna',
},
'Brown': {
'English': 'Brown',
'Slovenian': 'Rjava',
},
'Grey': {
'English': 'Grey',
'Slovenian': 'Siva',
},
// Calendar Screen
'Retry': {
'English': 'Retry',
'Slovenian': 'Poskusi znova',
},
'No upcoming events': {
'English': 'No upcoming events',
'Slovenian': 'Ni prihajajočih dogodkov',
},
'Pull down to refresh': {
'English': 'Pull down to refresh',
'Slovenian': 'Potegni navzdol za osvežitev',
},
'Today': {
'English': 'Today',
'Slovenian': 'Danes',
},
'Tomorrow': {
'English': 'Tomorrow',
'Slovenian': 'Jutri',
},
'NOW': {
'English': 'NOW',
'Slovenian': 'ZDAJ',
},
'Untitled Event': {
'English': 'Untitled Event',
'Slovenian': 'Brez naslova',
},
'All day': {
'English': 'All day',
'Slovenian': 'Cel dan',
},
// Preview Screen
'Enter Missing Bullet Scores': {
'English': 'Enter Missing Bullet Scores',
'Slovenian': 'Vnesi manjkajoče ocene zadetkov',
},
'Bullet Score': {
'English': 'Bullet Score',
'Slovenian': 'Ocena zadetka',
},
'Add Bullets': {
'English': 'Add Bullets',
'Slovenian': 'Dodaj zadetke',
},
'Save Target Data': {
'English': 'Save Target Data',
'Slovenian': 'Shrani podatke tarče',
},
'Training': {
'English': 'Training',
'Slovenian': 'Trening',
},
'Tournament': {
'English': 'Tournament',
'Slovenian': 'Tekmovanje',
},
'Last used folder': {
'English': 'Last used folder',
'Slovenian': 'Zadnja uporabljena mapa',
},
'New folder': {
'English': 'New folder',
'Slovenian': 'Nova mapa',
},
'Detection Preview': {
'English': 'Detection Preview',
'Slovenian': 'Predogled zaznave',
},
'Show Camera Detections': {
'English': 'Show Camera Detections',
'Slovenian': 'Prikaži zaznave kamere',
},
'Show Mapped Target': {
'English': 'Show Mapped Target',
'Slovenian': 'Prikaži preslikano tarčo',
},
// Book Screen
'Folder Settings': {
'English': 'Folder Settings',
'Slovenian': 'Nastavitve mape',
},
'Enter new folder name': {
'English': 'Enter new folder name',
'Slovenian': 'Vnesi novo ime mape',
},
'Tournament Mode 🏆': {
'English': 'Tournament Mode 🏆',
'Slovenian': 'Tekmovalni način 🏆',
},
'Mark as Tournament 🏆': {
'English': 'Mark as Tournament 🏆',
'Slovenian': 'Označi kot tekmovanje 🏆',
},
'Apply': {
'English': 'Apply',
'Slovenian': 'Uporabi',
},
'Confirm Deletion': {
'English': 'Confirm Deletion',
'Slovenian': 'Potrdi brisanje',
},
'Yes, delete': {
'English': 'Yes, delete',
'Slovenian': 'Da, izbriši',
},
'No targets in this folder': {
'English': 'No targets in this folder',
'Slovenian': 'V tej mapi ni tarč',
},
'Target': {
'English': 'Target',
'Slovenian': 'Tarča',
},
'Close': {
'English': 'Close',
'Slovenian': 'Zapri',
},
'Showing: 📂 All folders': {
'English': 'Showing: 📂 All folders',
'Slovenian': 'Prikaz: 📂 Vse mape',
},
'Showing: 🏆 Tournament folders': {
'English': 'Showing: 🏆 Tournament folders',
'Slovenian': 'Prikaz: 🏆 Tekmovalne mape',
},
'Showing: 🎯 Training folders': {
'English': 'Showing: 🎯 Training folders',
'Slovenian': 'Prikaz: 🎯 Treningske mape',
},
'No saved target data found': {
'English': 'No saved target data found',
'Slovenian': 'Ni shranjenih podatkov tarče',
},
'Filter Folders': {
'English': 'Filter Folders',
'Slovenian': 'Filtriraj mape',
},
'📂 All folders': {
'English': '📂 All folders',
'Slovenian': '📂 Vse mape',
},
'🏆 Tournament': {
'English': '🏆 Tournament',
'Slovenian': '🏆 Tekmovanje',
},
'🎯 Training': {
'English': '🎯 Training',
'Slovenian': '🎯 Trening',
},
'Sort By': {
'English': 'Sort By',
'Slovenian': 'Razvrsti po',
},
'📅 Sort by Date': {
'English': '📅 Sort by Date',
'Slovenian': '📅 Razvrsti po datumu',
},
'🏆 Sort by Score': {
'English': '🏆 Sort by Score',
'Slovenian': '🏆 Razvrsti po oceni',
},
// Navigation Menu
'Book': {
'English': 'Book',
'Slovenian': 'Knjiga',
},
'Shooting': {
'English': 'Shooting',
'Slovenian': 'Streljanje',
},
'Calendar': {
'English': 'Calendar',
'Slovenian': 'Koledar',
},
'Settings': {
'English': 'Settings',
'Slovenian': 'Nastavitve',
},
'Scan': {
'English': 'Scan',
'Slovenian': 'Skeniraj',
},
// Camera Screen
'Align the target inside the square': {
'English': 'Align the target inside the square',
'Slovenian': 'Poravnaj tarčo znotraj kvadrata',
},
'Select the number of bullets': {
'English': 'Select the number of bullets',
'Slovenian': 'Izberi število zadetkov',
},
// Book Screen - Preview
'Total score': {
'English': 'Total score',
'Slovenian': 'Rezultat:',
},
// Stream Screen (Camera View)
'Choose a Filter': {
'English': 'Choose a Filter',
'Slovenian': 'Izberi filter',
},
'None': {
'English': 'None',
'Slovenian': 'Brez',
},
'Greyscale': {
'English': 'Greyscale',
'Slovenian': 'Sivine',
},
'Invert': {
'English': 'Invert',
'Slovenian': 'Inverz',
},
'Processing image...': {
'English': 'Processing image...',
'Slovenian': 'Obdelava slike...',
},
'Unable to load camera stream': {
'English': 'Unable to load camera stream',
'Slovenian': 'Ni mogoče naložiti kamere',
},
// Delete confirmations
'Are you sure you want to delete this file?': {
'English': 'Are you sure you want to delete this file?',
'Slovenian': 'Ali ste prepričani, da želite izbrisati to datoteko?',
},
'Are you sure you want to delete this target?': {
'English': 'Are you sure you want to delete this target?',
'Slovenian': 'Ali ste prepričani, da želite izbrisati to tarčo?',
},
'Delete Target': {
'English': 'Delete Target',
'Slovenian': 'Izbriši tarčo',
},
'Yes, delete': {
'English': 'Yes, delete',
'Slovenian': 'Da, izbriši',
},
'Are you sure you want to delete the folder': {
'English': 'Are you sure you want to delete the folder',
'Slovenian': 'Ali ste prepričani, da želite izbrisati mapo',
},
'and all of its contents?': {
'English': 'and all of its contents?',
'Slovenian': 'in vso njeno vsebino?',
},
};
static String translate(String key, String language) {
if (translations.containsKey(key) &&
translations[key]!.containsKey(language)) {
return translations[key]![language]!;
}
return key; // Return the key itself if translation not found
}
}
+13 -6
View File
@@ -1,15 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../utils/translations.dart';
class BottomNavBar extends StatelessWidget { class BottomNavBar extends StatelessWidget {
final int selectedIndex; final int selectedIndex;
final Function(int) onItemTapped; final Function(int) onItemTapped;
final String selectedLanguage;
const BottomNavBar({ const BottomNavBar({
super.key, super.key,
required this.selectedIndex, required this.selectedIndex,
required this.onItemTapped, required this.onItemTapped,
required this.selectedLanguage,
}); });
String _t(String key) {
return AppTranslations.translate(key, selectedLanguage);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
@@ -26,12 +33,12 @@ Widget build(BuildContext context) {
unselectedFontSize: 12, unselectedFontSize: 12,
selectedItemColor: Colors.white, selectedItemColor: Colors.white,
unselectedItemColor: Colors.white70, unselectedItemColor: Colors.white70,
items: const [ items: [
BottomNavigationBarItem(icon: Icon(Icons.book), label: "Book"), BottomNavigationBarItem(icon: const Icon(Icons.book), label: _t("Book")),
BottomNavigationBarItem(icon: Icon(Icons.my_location), label: "Shooting"), BottomNavigationBarItem(icon: const Icon(Icons.my_location), label: _t("Shooting")),
BottomNavigationBarItem(icon: Icon(Icons.camera_alt), label: "Camera"), BottomNavigationBarItem(icon: const Icon(Icons.camera_alt), label: _t("Camera")),
BottomNavigationBarItem(icon: Icon(Icons.calendar_today), label: "Calendar"), BottomNavigationBarItem(icon: const Icon(Icons.calendar_today), label: _t("Calendar")),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: "Settings"), BottomNavigationBarItem(icon: const Icon(Icons.settings), label: _t("Settings")),
], ],
), ),
), ),