V1.0.0
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"idf.pythonInstallPath": "/usr/bin/python3",
|
||||||
|
"cmake.ignoreCMakeListsMissing": true
|
||||||
|
}
|
||||||
+314
-131
@@ -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
@@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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 '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")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user