V1.0.0
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"idf.pythonInstallPath": "/usr/bin/python3",
|
||||
"cmake.ignoreCMakeListsMissing": true
|
||||
}
|
||||
+299
-116
@@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; // For DateFormat
|
||||
import 'package:path_provider/path_provider.dart'; // For getApplicationDocumentsDirectory
|
||||
import 'target_view.dart'; // Import the TargetViewScreen
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class BookScreen extends StatefulWidget {
|
||||
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, DateTime> _folderCreationDates = {}; // Folder creation dates
|
||||
Map<String, int> _folderScores = {}; // Folder scores (sum of scores of all target files inside)
|
||||
String? _expandedFolder;
|
||||
Color _bulletColor = Colors.blue; // default fallback
|
||||
Map<String, bool> _folderTypes = {}; // true = tournament, false = training
|
||||
String _folderFilter = 'all';
|
||||
bool isRenameEnabled = false;
|
||||
String _sortMode = 'date'; // or 'score'
|
||||
String _selectedLanguage = 'English';
|
||||
String? _expandedFolder; // Track which folder is expanded
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this); // Register to listen for lifecycle events
|
||||
_loadBulletColor();
|
||||
_loadLanguage();
|
||||
_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
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this); // Unregister from lifecycle events when disposed
|
||||
@@ -192,7 +206,7 @@ Color _getColorFromName(String colorName) {
|
||||
Future<void> _deleteFile(File file) async {
|
||||
bool? confirmDeletion = await _showConfirmationDialog(
|
||||
context,
|
||||
'Are you sure you want to delete this file?',
|
||||
_t('Are you sure you want to delete this file?'),
|
||||
);
|
||||
|
||||
if (confirmDeletion == true) {
|
||||
@@ -309,9 +323,9 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Folder Settings',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t('Folder Settings'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -325,7 +339,7 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
hintText: 'Enter new folder name',
|
||||
hintText: _t('Enter new folder name'),
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
@@ -363,7 +377,7 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Center(
|
||||
child: Text(
|
||||
isTournament ? 'Tournament Mode 🏆' : 'Mark as Tournament 🏆',
|
||||
isTournament ? _t('Tournament Mode 🏆') : _t('Mark as Tournament 🏆'),
|
||||
style: TextStyle(
|
||||
color: isTournament ? _bulletColor : Colors.white70,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -387,7 +401,7 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text('Cancel'),
|
||||
child: Text(_t('Cancel')),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -405,7 +419,7 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
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 {
|
||||
bool? confirmDeletion = await _showConfirmationDialog(
|
||||
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) {
|
||||
@@ -485,12 +499,6 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
|
||||
_loadTargetDataFiles(); // Refresh file/folder list
|
||||
|
||||
if (_expandedFolder == folderName) {
|
||||
setState(() {
|
||||
_expandedFolder = null;
|
||||
});
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Folder "$folderName" deleted successfully.'),
|
||||
@@ -517,9 +525,9 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Confirm Deletion',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t('Confirm Deletion'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -547,7 +555,7 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Yes, delete"),
|
||||
child: Text(_t("Yes, delete")),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -565,7 +573,7 @@ Future<void> _renameFolder(String oldFolderName) async {
|
||||
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 folderDate = _folderCreationDates[folderName];
|
||||
final formattedDate = folderDate != null
|
||||
? DateFormat('yyyy-MM-dd').format(folderDate)
|
||||
? DateFormat('dd/MM/yyyy').format(folderDate)
|
||||
: 'Unknown Date';
|
||||
|
||||
final isExpanded = _expandedFolder == folderName;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: isExpanded ? _bulletColor.withOpacity(0.15) : Colors.grey[300],
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isExpanded ? _bulletColor : Colors.transparent,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true, // Compact layout
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
// Header section: Name and Date
|
||||
InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_expandedFolder = _expandedFolder == folderName ? null : folderName;
|
||||
});
|
||||
},
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||
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),
|
||||
),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: Text(
|
||||
"📝 $numberOfTargets / 🎯 $totalScore / 📅 $formattedDate",
|
||||
],
|
||||
),
|
||||
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(
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// 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: [
|
||||
// Info badge section (non-clickable, matches button style)
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
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: [
|
||||
IconButton(
|
||||
iconSize: 22,
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: Icon(Icons.my_location, color: _bulletColor),
|
||||
onPressed: () => _openFolderTargetView(folderName),
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 22,
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: Icon(Icons.edit, color: _bulletColor),
|
||||
onPressed: () => _renameFolder(folderName),
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 22,
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () => _deleteFolder(folderName),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Preview button section
|
||||
InkWell(
|
||||
onTap: () => _openFolderTargetView(folderName),
|
||||
child: Container(
|
||||
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),
|
||||
),
|
||||
),
|
||||
// Edit button section
|
||||
InkWell(
|
||||
onTap: () => _renameFolder(folderName),
|
||||
child: Container(
|
||||
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 _buildFileRow(File file, String folderName, int index) {
|
||||
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
|
||||
Widget _buildTargetsList(String folderName) {
|
||||
final targetFiles = _groupedFiles[folderName] ?? [];
|
||||
|
||||
final score = _getScoreFromJson(jsonDecode(file.readAsStringSync())); // Read the score for the file
|
||||
if (targetFiles.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
_t("No targets in this folder"),
|
||||
style: const TextStyle(color: Colors.black54, fontSize: 14),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), // Reduced vertical padding for compact height
|
||||
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), // Tight margin
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[200], // Light grey background for files
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.grey[300]!, width: 1), // Light border around the file
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: Colors.grey[300]!, width: 1),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _openFilePreview(file), // Open the file preview when the row is tapped
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, // Distribute space across the row
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
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)),
|
||||
|
||||
// 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
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete, color: Color.fromARGB(255, 252, 102, 92)),
|
||||
onPressed: () => _deleteFile(file), // Trigger file deletion
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -723,10 +898,10 @@ Widget build(BuildContext context) {
|
||||
children: [
|
||||
Text(
|
||||
_folderFilter == 'all'
|
||||
? 'Showing: 📂 All folders'
|
||||
? _t('Showing: 📂 All folders')
|
||||
: _folderFilter == 'tournament'
|
||||
? 'Showing: 🏆 Tournament folders'
|
||||
: 'Showing: 🎯 Training folders',
|
||||
? _t('Showing: 🏆 Tournament folders')
|
||||
: _t('Showing: 🎯 Training folders'),
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const Icon(Icons.filter_list),
|
||||
@@ -738,7 +913,7 @@ Widget build(BuildContext context) {
|
||||
// 📁 Folder list or empty state
|
||||
Expanded(
|
||||
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(
|
||||
thumbVisibility: true, // always show scrollbar
|
||||
thickness: 6, // optional: adjust width
|
||||
@@ -771,9 +946,9 @@ void _showFolderFilterPopup(BuildContext context) {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Filter Folders',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t('Filter Folders'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -782,21 +957,22 @@ void _showFolderFilterPopup(BuildContext context) {
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Folder type filters
|
||||
_buildStyledFilterButton('all', '📂 All folders', context),
|
||||
_buildStyledFilterButton('all', _t('📂 All folders'), context),
|
||||
const SizedBox(height: 12),
|
||||
_buildStyledFilterButton('tournament', '🏆 Tournament', context),
|
||||
_buildStyledFilterButton('tournament', _t('🏆 Tournament'), context),
|
||||
const SizedBox(height: 12),
|
||||
_buildStyledFilterButton('training', '🎯 Training', context),
|
||||
_buildStyledFilterButton('training', _t('🎯 Training'), context),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
const Text(
|
||||
'Sort By',
|
||||
style: TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w500),
|
||||
Text(
|
||||
_t('Sort By'),
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 16, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
@@ -805,20 +981,23 @@ void _showFolderFilterPopup(BuildContext context) {
|
||||
this.setState(() {}); // 👈 refresh parent widget
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||
margin: const EdgeInsets.only(right: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _sortMode == 'date' ? _bulletColor : Colors.grey[800],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'📅 Sort by Date',
|
||||
_t('📅 Sort by Date'),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: _sortMode == 'date' ? Colors.white : Colors.white70,
|
||||
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
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||
margin: const EdgeInsets.only(left: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _sortMode == 'score' ? _bulletColor : Colors.grey[800],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'🏆 Sort by Score',
|
||||
_t('🏆 Sort by Score'),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: _sortMode == 'score' ? Colors.white : Colors.white70,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 15,
|
||||
fontSize: 14,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../services/google_calendar_service.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class CalendarScreen extends StatefulWidget {
|
||||
const CalendarScreen({super.key});
|
||||
@@ -16,14 +18,28 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
List<CalendarEvent> _events = [];
|
||||
String? _errorMessage;
|
||||
String _bulletColor = 'Blue';
|
||||
String _selectedLanguage = 'English';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initializeDateFormatting('sl_SI', null); // Initialize Slovenian locale
|
||||
_loadBulletColor();
|
||||
_loadLanguage();
|
||||
_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
|
||||
void dispose() {
|
||||
_calendarService.dispose();
|
||||
@@ -128,17 +144,23 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
return a.startTime!.compareTo(b.startTime!);
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_events = filteredEvents;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_errorMessage = 'Error loading events: $e';
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -172,7 +194,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
ElevatedButton.icon(
|
||||
onPressed: _loadEvents,
|
||||
icon: const Icon(Icons.refresh),
|
||||
label: const Text('Retry'),
|
||||
label: Text(_t('Retry')),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: accentColor,
|
||||
foregroundColor: Colors.white,
|
||||
@@ -199,13 +221,13 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
children: [
|
||||
Icon(Icons.event_available, size: 64, color: Colors.grey[400]),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'No upcoming events',
|
||||
style: TextStyle(fontSize: 16, color: Colors.grey),
|
||||
Text(
|
||||
_t('No upcoming events'),
|
||||
style: const TextStyle(fontSize: 16, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Pull down to refresh',
|
||||
_t('Pull down to refresh'),
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey[400]),
|
||||
),
|
||||
],
|
||||
@@ -229,15 +251,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
final startTime = event.startTime;
|
||||
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
|
||||
String dateText = '';
|
||||
bool isToday = false;
|
||||
@@ -248,33 +261,27 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
final difference = eventDay.difference(today).inDays;
|
||||
|
||||
if (difference == 0) {
|
||||
dateText = 'Today';
|
||||
dateText = _t('Today');
|
||||
isToday = true;
|
||||
} else if (difference == 1) {
|
||||
dateText = 'Tomorrow';
|
||||
dateText = _t('Tomorrow');
|
||||
} 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(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.05),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => _showEventDetails(event, accentColor),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -285,7 +292,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
decoration: BoxDecoration(
|
||||
color: isToday
|
||||
? accentColor.withValues(alpha: 0.15)
|
||||
: Colors.grey[50],
|
||||
: Colors.grey[200],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
@@ -293,6 +300,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
// Small colored indicator
|
||||
Container(
|
||||
width: 4,
|
||||
height: 16,
|
||||
@@ -318,9 +326,9 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
color: accentColor,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Text(
|
||||
'NOW',
|
||||
style: TextStyle(
|
||||
child: Text(
|
||||
_t('NOW'),
|
||||
style: const TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
@@ -331,217 +339,85 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// Time and title
|
||||
// Main content
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Time indicator with accent color
|
||||
Container(
|
||||
width: 60,
|
||||
height: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: accentColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
timeText,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: isAllDay ? 12 : 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: accentColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
// Title
|
||||
// Title with icon
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.event, size: 20, color: accentColor),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
event.summary ?? 'Untitled Event',
|
||||
event.summary ?? _t('Untitled Event'),
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
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],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
Expanded(
|
||||
child: Text(
|
||||
event.summary ?? 'Event Details',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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(
|
||||
const SizedBox(height: 8),
|
||||
// Time range (if not all day)
|
||||
if (!isAllDay && startTime != null && event.endTime != null) ...[
|
||||
_buildDetailRowInline(
|
||||
icon: Icons.access_time,
|
||||
label: 'Time',
|
||||
value:
|
||||
'${DateFormat('HH:mm').format(startTime.toLocal())} - ${DateFormat('HH:mm').format(endTime.toLocal())}',
|
||||
value: '${DateFormat('HH:mm').format(startTime.toLocal())} - ${DateFormat('HH:mm').format(event.endTime!.toLocal())}',
|
||||
accentColor: accentColor,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
if (isAllDay) ...[
|
||||
_buildDetailRow(
|
||||
_buildDetailRowInline(
|
||||
icon: Icons.access_time,
|
||||
label: 'Duration',
|
||||
value: 'All day event',
|
||||
value: _t('All day'),
|
||||
accentColor: accentColor,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
// Location
|
||||
if (event.location != null && event.location!.isNotEmpty) ...[
|
||||
_buildDetailRow(
|
||||
_buildDetailRowInline(
|
||||
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 String label,
|
||||
required String value,
|
||||
required Color accentColor,
|
||||
}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: accentColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(icon, size: 20, color: accentColor),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Icon(icon, size: 16, color: accentColor),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
child: Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontSize: 13,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:image/image.dart' as img;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'yolo_detector.dart';
|
||||
import 'preview_screen.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class CameraScreen extends StatefulWidget {
|
||||
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)
|
||||
int _numberOfBullets = 5;
|
||||
String _selectedLanguage = 'English';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -28,9 +30,21 @@ class _CameraScreenState extends State<CameraScreen> {
|
||||
_loadBulletCount();
|
||||
_initCamera();
|
||||
_loadBulletColor(); // 👈
|
||||
_loadLanguage();
|
||||
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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
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
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
'Select the number of bullets',
|
||||
style: TextStyle(
|
||||
_t('Select the number of bullets'),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -245,9 +260,9 @@ Color _getColorFromName(String colorName) {
|
||||
extendBody: true,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.grey[900],
|
||||
title: const Text(
|
||||
"Align the target inside the square",
|
||||
style: TextStyle(
|
||||
title: Text(
|
||||
_t("Align the target inside the square"),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'target_screen.dart';
|
||||
import '../widgets/bottom_nav.dart';
|
||||
//import 'login_screen.dart';
|
||||
import 'camera_screen.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
@@ -17,6 +19,7 @@ class HomeScreen extends StatefulWidget {
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
int _selectedIndex = 1; // Default to Shooting Screen
|
||||
String _selectedLanguage = 'English';
|
||||
|
||||
static final List<Widget> _pages = <Widget>[
|
||||
BookScreen(),
|
||||
@@ -26,7 +29,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
SettingsScreen(),
|
||||
];
|
||||
|
||||
static final List<String> _pageTitles = [
|
||||
final List<String> _pageTitles = [
|
||||
'Book',
|
||||
'Shooting',
|
||||
'Scan',
|
||||
@@ -34,6 +37,23 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
'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) {
|
||||
if (index == 2) {
|
||||
_openCameraScan();
|
||||
@@ -42,6 +62,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
// Reload language when switching screens to pick up any changes from Settings
|
||||
_loadLanguage();
|
||||
}
|
||||
|
||||
//void _logout() {
|
||||
@@ -69,7 +91,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
_pageTitles[_selectedIndex],
|
||||
_t(_pageTitles[_selectedIndex]),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.grey[900],
|
||||
@@ -77,9 +99,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
automaticallyImplyLeading: false,
|
||||
scrolledUnderElevation: 0, // For Material 3 (Flutter 3.7+)
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout, color: Colors.red),
|
||||
onPressed: null, // disables the button
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
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(
|
||||
selectedIndex: _selectedIndex,
|
||||
onItemTapped: _onItemTapped,
|
||||
selectedLanguage: _selectedLanguage,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+112
-34
@@ -8,6 +8,7 @@ import 'yolo_detector.dart';
|
||||
import 'dart:convert'; // For JSON encoding
|
||||
import 'dart:math';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class PreviewScreen extends StatefulWidget {
|
||||
final String imagePath;
|
||||
@@ -56,6 +57,7 @@ class _PreviewScreenState extends State<PreviewScreen> {
|
||||
Color get _activeColor => _isEditMode ? _editColor : _bulletColor;
|
||||
Color get _activeColorWithOpacity => _activeColor.withOpacity(_bulletOpacity);
|
||||
|
||||
String _selectedLanguage = 'English';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -63,9 +65,21 @@ class _PreviewScreenState extends State<PreviewScreen> {
|
||||
_realImageDetections = widget.detections;
|
||||
_targetImageFuture = _loadAndDecodeTargetImage();
|
||||
_loadBulletColor(); // 👈 Add this
|
||||
_loadLanguage();
|
||||
_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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
@@ -241,9 +255,9 @@ void _manualScoreEntry() {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Enter Missing Bullet Scores',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t('Enter Missing Bullet Scores'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -263,7 +277,7 @@ void _manualScoreEntry() {
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(color: Colors.white),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Bullet Score ${_mappedBullets.length + index + 1}',
|
||||
labelText: '${_t('Bullet Score')} ${_mappedBullets.length + index + 1}',
|
||||
labelStyle: const TextStyle(color: Colors.white54),
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
@@ -308,7 +322,7 @@ void _manualScoreEntry() {
|
||||
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
|
||||
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 fileName = "target_${dateString}_$timeString.json";
|
||||
final fileName = "target_${dateStringFile}_$timeString.json";
|
||||
final filePath = '${saveDirectory.path}/$fileName';
|
||||
|
||||
// Prepare target data to save
|
||||
@@ -500,20 +515,25 @@ void _moveBullet(Offset offsetDelta) {
|
||||
int trainingCount = 1;
|
||||
int tournamentCount = 1;
|
||||
|
||||
// Get localized folder names
|
||||
final trainingWord = _t('Training');
|
||||
final tournamentWord = _t('Tournament');
|
||||
|
||||
for (var folder in folders) {
|
||||
final name = folder.path.split('/').last;
|
||||
if (name.startsWith('Training')) {
|
||||
final num = int.tryParse(name.replaceAll('Training ', ''));
|
||||
// Check for both English and Slovenian names
|
||||
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 (name.startsWith('Tournament')) {
|
||||
final num = int.tryParse(name.replaceAll('Tournament ', ''));
|
||||
if (name.startsWith('Tournament') || name.startsWith('Tekmovanje')) {
|
||||
final num = int.tryParse(name.replaceAll('Tournament ', '').replaceAll('Tekmovanje ', ''));
|
||||
if (num != null && num >= tournamentCount) tournamentCount = num + 1;
|
||||
}
|
||||
}
|
||||
|
||||
final String nextTraining = 'Training $trainingCount';
|
||||
final String nextTournament = 'Tournament $tournamentCount';
|
||||
final String nextTraining = '$trainingWord $trainingCount';
|
||||
final String nextTournament = '$tournamentWord $tournamentCount';
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
@@ -526,9 +546,9 @@ void _moveBullet(Offset offsetDelta) {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Save Target Data',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t('Save Target Data'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -536,7 +556,7 @@ void _moveBullet(Offset offsetDelta) {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 🎯 Training Button
|
||||
// 🎯 Training Button (New folder)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
@@ -554,12 +574,24 @@ void _moveBullet(Offset offsetDelta) {
|
||||
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),
|
||||
|
||||
// 🏆 Tournament Button
|
||||
// 🏆 Tournament Button (New folder)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
@@ -577,7 +609,19 @@ void _moveBullet(Offset offsetDelta) {
|
||||
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) ...[
|
||||
@@ -613,13 +657,13 @@ void _moveBullet(Offset offsetDelta) {
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'${lastIsTournament ? "🏆" : "🎯"} Save to "$lastFolder"',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
'${lastIsTournament ? "🏆" : "🎯"} $lastFolder',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Last used folder',
|
||||
style: TextStyle(fontSize: 12, color: Colors.white70),
|
||||
Text(
|
||||
_t('Last used folder'),
|
||||
style: const TextStyle(fontSize: 12, color: Colors.white70),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -715,9 +759,9 @@ Widget build(BuildContext context) {
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.grey[900],
|
||||
iconTheme: const IconThemeData(color: Colors.white),
|
||||
title: const Text(
|
||||
'Detection Preview',
|
||||
style: TextStyle(color: Colors.white),
|
||||
title: Text(
|
||||
_t('Detection Preview'),
|
||||
style: const TextStyle(color: Colors.white),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
@@ -735,14 +779,14 @@ Widget build(BuildContext context) {
|
||||
});
|
||||
},
|
||||
tooltip: _mode == PreviewMode.mappedTarget
|
||||
? "Show Camera Detections"
|
||||
: "Show Mapped Target",
|
||||
? _t("Show Camera Detections")
|
||||
: _t("Show Mapped Target"),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
color: Colors.white,
|
||||
onPressed: _saveTargetData,
|
||||
tooltip: "Save Target Data",
|
||||
tooltip: _t("Save Target Data"),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1045,11 +1089,42 @@ Widget build(BuildContext context) {
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
color: Colors.grey[900],
|
||||
padding: EdgeInsets.zero,
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: Center(
|
||||
height: 150,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Left side - Label (darker grey)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 16),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
"🎯 Total Score: $_totalScore",
|
||||
"🎯 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,
|
||||
@@ -1058,6 +1133,9 @@ Widget build(BuildContext context) {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({super.key});
|
||||
@@ -40,16 +41,26 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
'Grey',
|
||||
];
|
||||
|
||||
String _selectedLanguage = 'English';
|
||||
final List<Map<String, String>> _languages = [
|
||||
{'code': 'en', 'name': 'English', 'flag': '🇬🇧'},
|
||||
{'code': 'sl', 'name': 'Slovenian', 'flag': '🇸🇮'},
|
||||
];
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadCameraIPs();
|
||||
_loadBulletColor();
|
||||
_loadBulletColor();
|
||||
_loadLanguage();
|
||||
}
|
||||
|
||||
|
||||
String _t(String key) {
|
||||
return AppTranslations.translate(key, _selectedLanguage);
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
@@ -108,6 +119,21 @@ Future<void> _saveBulletColor(String color) async {
|
||||
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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -164,7 +190,7 @@ Future<void> _saveBulletColor(String color) async {
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Camera ${index + 1}",
|
||||
"${_t('Camera')} ${index + 1}",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
@@ -183,7 +209,7 @@ Future<void> _saveBulletColor(String color) async {
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Colors.grey[800],
|
||||
hintText: "Enter Camera IP",
|
||||
hintText: _t("Enter Camera IP"),
|
||||
hintStyle: const TextStyle(color: Colors.white54),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
@@ -207,8 +233,10 @@ Future<void> _saveBulletColor(String color) async {
|
||||
String newIP = controller.text.trim();
|
||||
if (newIP.isNotEmpty) {
|
||||
await _saveCameraIP(index, newIP);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _getColorFromName(_selectedBulletColor),
|
||||
@@ -218,7 +246,7 @@ Future<void> _saveBulletColor(String color) async {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Save"),
|
||||
child: Text(_t("Save")),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -229,7 +257,9 @@ Future<void> _saveBulletColor(String color) async {
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _clearCameraIP(index);
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[700],
|
||||
@@ -239,7 +269,7 @@ Future<void> _saveBulletColor(String color) async {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Reset to Default"),
|
||||
child: Text(_t("Reset to Default")),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -257,7 +287,7 @@ Future<void> _saveBulletColor(String color) async {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Cancel"),
|
||||
child: Text(_t("Cancel")),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -280,6 +310,8 @@ Future<void> _saveBulletColor(String color) async {
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
_buildLanguageSelector(),
|
||||
const SizedBox(height: 16),
|
||||
_buildBulletColorSelector(),
|
||||
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() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Select Bullet Color",
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t("Select Bullet Color"),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
@@ -325,7 +405,7 @@ Future<void> _saveBulletColor(String color) async {
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_selectedBulletColor,
|
||||
_t(_selectedBulletColor),
|
||||
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() {
|
||||
showDialog(
|
||||
context: context,
|
||||
@@ -350,9 +506,9 @@ void _showBulletColorPopup() {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
"Select Bullet Color",
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t("Select Bullet Color"),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
@@ -369,7 +525,9 @@ void _showBulletColorPopup() {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
await _saveBulletColor(colorName);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
width: 48,
|
||||
@@ -389,7 +547,7 @@ void _showBulletColorPopup() {
|
||||
const SizedBox(height: 20),
|
||||
TextButton(
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
"Camera Settings",
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t("Camera Settings"),
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
@@ -429,7 +587,7 @@ void _showBulletColorPopup() {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Camera ${index + 1}",
|
||||
"${_t('Camera')} ${index + 1}",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:image/image.dart' as img;
|
||||
import 'yolo_detector.dart';
|
||||
import 'preview_screen.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
enum ImageFilterType { none, greyscale, invert}
|
||||
|
||||
@@ -38,10 +39,16 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTick
|
||||
Matrix4? _originalMatrix;
|
||||
|
||||
Color _bulletColor = Colors.blue; // default/fallback bullet color
|
||||
String _selectedLanguage = 'English';
|
||||
|
||||
String _t(String key) {
|
||||
return AppTranslations.translate(key, _selectedLanguage);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadLanguage();
|
||||
_loadStreamUrl();
|
||||
_loadBulletCount();
|
||||
_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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
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
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 16.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Text(
|
||||
'Select the number of bullets',
|
||||
style: TextStyle(
|
||||
_t('Select the number of bullets'),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -365,9 +380,9 @@ Color _getColorFromName(String colorName) {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Choose a Filter',
|
||||
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||
Text(
|
||||
_t('Choose a Filter'),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Wrap(
|
||||
@@ -377,6 +392,7 @@ Color _getColorFromName(String colorName) {
|
||||
children: ImageFilterType.values.map((f) {
|
||||
final isSelected = _selectedFilter == f;
|
||||
final label = f.toString().split('.').last;
|
||||
final translatedLabel = _t(label[0].toUpperCase() + label.substring(1));
|
||||
return ElevatedButton(
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
@@ -384,13 +400,15 @@ Color _getColorFromName(String colorName) {
|
||||
});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt('selectedFilter', f.index); // Save filter index
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isSelected ? _bulletColor : Colors.grey[800],
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Text(label[0].toUpperCase() + label.substring(1)),
|
||||
child: Text(translatedLabel),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
@@ -455,7 +473,7 @@ void dispose() {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Target ${widget.cameraIndex}"),
|
||||
title: Text("${_t('Camera')} ${widget.cameraIndex}"),
|
||||
backgroundColor: Colors.grey[900],
|
||||
foregroundColor: Colors.white,
|
||||
leading: IconButton(
|
||||
@@ -523,14 +541,14 @@ void dispose() {
|
||||
Positioned.fill(
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.4),
|
||||
child: const Column(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: Colors.white),
|
||||
SizedBox(height: 16),
|
||||
const CircularProgressIndicator(color: Colors.white),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"Processing image...",
|
||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||
_t("Processing image..."),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -571,14 +589,14 @@ void dispose() {
|
||||
children: [
|
||||
const Icon(Icons.error, color: Colors.red, size: 100),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
"Unable to load camera stream",
|
||||
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
Text(
|
||||
_t("Unable to load camera stream"),
|
||||
style: const TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: _loadStreamUrl,
|
||||
child: const Text("Retry"),
|
||||
child: Text(_t("Retry")),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'stream_screen.dart'; // Import the camera stream screen
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class TargetScreen extends StatefulWidget {
|
||||
const TargetScreen({super.key});
|
||||
@@ -10,14 +11,28 @@ class TargetScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
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
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadLanguage();
|
||||
_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
|
||||
Future<void> _loadCameraData() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -46,7 +61,7 @@ class _TargetScreenState extends State<TargetScreen> {
|
||||
setState(() {
|
||||
_cameraButtonsEnabled = cameraDataStatus;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -72,7 +87,8 @@ class _TargetScreenState extends State<TargetScreen> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => CameraStreamScreen(cameraIndex: index + 1),
|
||||
builder: (context) =>
|
||||
CameraStreamScreen(cameraIndex: index + 1),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -94,10 +110,11 @@ class _TargetScreenState extends State<TargetScreen> {
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
"Target ${index + 1}",
|
||||
"${_t('Camera')} ${index + 1}",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: isEnabled ? Colors.white : Colors.grey[600],
|
||||
color:
|
||||
isEnabled ? Colors.white : Colors.grey[600],
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
@@ -113,6 +130,5 @@ class _TargetScreenState extends State<TargetScreen> {
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'dart:convert';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class TargetViewScreen extends StatefulWidget {
|
||||
final List<File> jsonFiles;
|
||||
@@ -38,15 +39,28 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
final double bulletSize = 19;
|
||||
|
||||
Color _bulletColor = Colors.blue; // default/fallback bullet color
|
||||
String _selectedLanguage = 'English';
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBulletColor();
|
||||
_loadLanguage();
|
||||
_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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
@@ -116,7 +130,25 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
final jsonString = await currentFile.readAsString();
|
||||
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;
|
||||
_bullets = bulletsJson.map((b) => Offset(b['x'], b['y'])).toList();
|
||||
|
||||
@@ -185,9 +217,9 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Delete Target',
|
||||
style: TextStyle(
|
||||
Text(
|
||||
_t('Delete Target'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -195,9 +227,9 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
"Are you sure you want to delete this target?",
|
||||
style: TextStyle(color: Colors.white70, fontSize: 16),
|
||||
Text(
|
||||
_t("Are you sure you want to delete this target?"),
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 16),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
@@ -218,7 +250,7 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: const Text("Yes, delete"),
|
||||
child: Text(_t("Yes, delete")),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
@@ -339,19 +371,38 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
color: Colors.grey[900],
|
||||
padding: EdgeInsets.zero,
|
||||
child: SizedBox(
|
||||
height: 56,
|
||||
child: Center(
|
||||
height: 150,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Combined badge with text and score
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
||||
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(
|
||||
"🎯 Total Score: $_totalScore",
|
||||
"🎯 ${_t('Total score')} $_totalScore",
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../utils/translations.dart';
|
||||
|
||||
class BottomNavBar extends StatelessWidget {
|
||||
final int selectedIndex;
|
||||
final Function(int) onItemTapped;
|
||||
final String selectedLanguage;
|
||||
|
||||
const BottomNavBar({
|
||||
super.key,
|
||||
required this.selectedIndex,
|
||||
required this.onItemTapped,
|
||||
required this.selectedLanguage,
|
||||
});
|
||||
|
||||
String _t(String key) {
|
||||
return AppTranslations.translate(key, selectedLanguage);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
@@ -26,12 +33,12 @@ Widget build(BuildContext context) {
|
||||
unselectedFontSize: 12,
|
||||
selectedItemColor: Colors.white,
|
||||
unselectedItemColor: Colors.white70,
|
||||
items: const [
|
||||
BottomNavigationBarItem(icon: Icon(Icons.book), label: "Book"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.my_location), label: "Shooting"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.camera_alt), label: "Camera"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.calendar_today), label: "Calendar"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.settings), label: "Settings"),
|
||||
items: [
|
||||
BottomNavigationBarItem(icon: const Icon(Icons.book), label: _t("Book")),
|
||||
BottomNavigationBarItem(icon: const Icon(Icons.my_location), label: _t("Shooting")),
|
||||
BottomNavigationBarItem(icon: const Icon(Icons.camera_alt), label: _t("Camera")),
|
||||
BottomNavigationBarItem(icon: const Icon(Icons.calendar_today), label: _t("Calendar")),
|
||||
BottomNavigationBarItem(icon: const Icon(Icons.settings), label: _t("Settings")),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user