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