added new tragets... fixed some issues.

This commit is contained in:
2025-12-24 11:25:56 +01:00
parent b727e65d14
commit f3dc1e7654
21 changed files with 2408 additions and 1732 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

+4 -14
View File
@@ -227,7 +227,7 @@ Color _getColorFromName(String colorName) {
await file.delete();
_loadTargetDataFiles(); // Refresh after deleting the file
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Error deleting file: $e")));
debugPrint("Error deleting file: $e");
}
}
}
@@ -380,7 +380,7 @@ Future<void> _renameFolder(String oldFolderName) async {
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: isTournament ? _bulletColor.withOpacity(0.2) : Colors.grey[850],
color: isTournament ? _bulletColor.withValues(alpha: 0.2) : Colors.grey[850],
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isTournament ? _bulletColor : Colors.grey[700]!,
@@ -469,9 +469,7 @@ Future<void> _renameFolder(String oldFolderName) async {
if (targetFolderName != oldFolderName) {
if (await newFolderPath.exists()) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('A folder named "$targetFolderName" already exists.')),
);
debugPrint('A folder named "$targetFolderName" already exists.');
return;
} else {
await oldFolderPath.rename(newFolderPath.path);
@@ -522,16 +520,8 @@ Future<void> _renameFolder(String oldFolderName) async {
}
_loadTargetDataFiles(); // Refresh file/folder list
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Folder "$folderName" deleted successfully.'),
),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Error deleting folder: $e")),
);
debugPrint("Error deleting folder: $e");
}
}
}
+3 -3
View File
@@ -957,7 +957,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
time = '18:00 - 19:00';
break;
case EventType.regular:
cardAccentColor = Colors.blue;
cardAccentColor = accentColor;
eventIcon = Icons.event;
title = _t('Duty Shift');
time = '14:00 - 16:00';
@@ -1112,7 +1112,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
time = '18:00 - 19:00';
break;
case EventType.regular:
cardAccentColor = Colors.blue;
cardAccentColor = accentColor;
eventIcon = Icons.event;
title = _t('Duty Shift');
time = '14:00 - 16:00';
@@ -1253,7 +1253,7 @@ class _CalendarScreenState extends State<CalendarScreen> {
eventIcon = Icons.groups; // People icon for meetings
break;
case EventType.regular:
cardAccentColor = Colors.blue;
cardAccentColor = accentColor;
backgroundColor = Colors.grey[300]!;
eventIcon = Icons.event;
break;
+2 -3
View File
@@ -10,7 +10,6 @@ import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
import '../utils/target_colors.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@@ -62,7 +61,8 @@ class _HomeScreenState extends State<HomeScreen> {
Future<void> _loadSelectedTarget() async {
final prefs = await SharedPreferences.getInstance();
final selectedTarget = prefs.getString('selectedTarget') ?? 'assets/target.png';
final selectedTarget =
prefs.getString('selectedTarget') ?? 'assets/target.png';
setState(() {
_selectedTarget = selectedTarget;
_backgroundColor = TargetColors.getBackgroundColor(selectedTarget);
@@ -139,4 +139,3 @@ class _HomeScreenState extends State<HomeScreen> {
);
}
}
File diff suppressed because it is too large Load Diff
+268 -201
View File
@@ -76,6 +76,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
'assets/targets/Target22.jpg',
'assets/targets/Target23.jpg',
'assets/targets/Target24.jpg',
'assets/targets/Target25.jpg',
'assets/targets/Target26.jpg',
'assets/targets/Target27.jpg',
'assets/targets/Target28.jpg',
'assets/targets/Target29.jpg',
'assets/targets/Target30.jpg',
];
@override
@@ -374,9 +380,7 @@ String _getTargetDisplayName(String targetPath) {
const SizedBox(height: 8),
_buildLanguageSelector(),
const SizedBox(height: 16),
_buildBulletColorSelector(),
const SizedBox(height: 16),
_buildTargetSelector(),
_buildAppearanceSelector(),
const SizedBox(height: 16),
_buildCameraList(),
@@ -397,13 +401,6 @@ String _getTargetDisplayName(String targetPath) {
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -477,19 +474,12 @@ String _getTargetDisplayName(String targetPath) {
);
}
Widget _buildBulletColorSelector() {
Widget _buildAppearanceSelector() {
return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 0),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -517,7 +507,7 @@ String _getTargetDisplayName(String targetPath) {
),
const SizedBox(width: 12),
Text(
_t("Select Bullet Color"),
_t("Appearance"),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
@@ -529,10 +519,10 @@ String _getTargetDisplayName(String targetPath) {
),
// Content
GestureDetector(
onTap: _showBulletColorPopup,
onTap: _showAppearancePopup,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: const BorderRadius.only(
@@ -542,104 +532,95 @@ String _getTargetDisplayName(String targetPath) {
),
child: Row(
children: [
// Preview box with target
Container(
width: 70,
height: 70,
decoration: BoxDecoration(
border: Border.all(color: Colors.black12),
borderRadius: BorderRadius.circular(8),
color: TargetColors.getBackgroundColor(_selectedTarget),
),
child: Image.asset(
_selectedTarget,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.image_not_supported, color: Colors.grey[400]);
},
),
),
const SizedBox(width: 16),
// Details section
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
_t("Bullet:"),
style: const TextStyle(
color: Colors.black54,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 8),
Container(
width: 16,
height: 16,
margin: const EdgeInsets.only(right: 12),
decoration: BoxDecoration(
color: _getColorFromName(_selectedBulletColor),
shape: BoxShape.circle,
border: Border.all(color: Colors.black26),
border: Border.all(color: Colors.black26, width: 1),
),
),
const SizedBox(width: 6),
Expanded(
child: Text(
_t(_selectedBulletColor),
style: const TextStyle(color: Colors.black87, fontSize: 14),
),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
),
],
),
);
}
Widget _buildTargetSelector() {
return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 0),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: Colors.grey[900],
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
child: Row(
children: [
Container(
width: 4,
height: 16,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(2),
),
),
const SizedBox(width: 12),
Text(
_t("Select Target"),
_selectedBulletColor,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.white,
color: Colors.black87,
fontSize: 12,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
// Content
GestureDetector(
onTap: _showTargetPopup,
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(12),
bottomRight: Radius.circular(12),
),
),
child: Row(
const SizedBox(height: 8),
Row(
children: [
Text(
_t("Target:"),
style: const TextStyle(
color: Colors.black54,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
_getTargetDisplayName(_selectedTarget),
style: const TextStyle(color: Colors.black87, fontSize: 14),
style: const TextStyle(
color: Colors.black87,
fontSize: 12,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const Icon(Icons.chevron_right, color: Colors.black54),
],
),
],
),
),
const SizedBox(width: 8),
Icon(Icons.chevron_right, color: Colors.black54),
],
),
),
@@ -725,88 +706,29 @@ void _showLanguagePopup() {
);
}
void _showBulletColorPopup() {
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 Bullet Color"),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 20),
Wrap(
spacing: 12,
runSpacing: 12,
children: _bulletColors.map((colorName) {
final color = _getColorFromName(colorName);
final isSelected = colorName == _selectedBulletColor;
return GestureDetector(
onTap: () async {
await _saveBulletColor(colorName);
if (context.mounted) {
Navigator.of(context).pop();
}
},
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? Colors.white : Colors.white24,
width: isSelected ? 3 : 1,
),
),
),
);
}).toList(),
),
const SizedBox(height: 20),
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(_t("Cancel"), style: const TextStyle(color: Colors.white70)),
),
],
),
),
);
},
);
}
void _showTargetPopup() {
void _showAppearancePopup() {
String previewBulletColor = _selectedBulletColor;
String previewTarget = _selectedTarget;
int selectedTab = 0; // 0 for Bullet, 1 for Target
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, innerSetState) {
return Dialog(
backgroundColor: Colors.grey[900],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
insetPadding: const EdgeInsets.all(24),
child: SizedBox(
height: 700,
width: 400,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_t("Select Target"),
_t("Appearance Settings"),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
@@ -814,14 +736,21 @@ void _showTargetPopup() {
),
),
const SizedBox(height: 20),
// Image Preview
Container(
width: 200,
height: 200,
// Preview with multiple bullets on target - ALWAYS ON TOP
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Container(
width: double.infinity,
height: 280,
decoration: BoxDecoration(
border: Border.all(color: Colors.white24),
borderRadius: BorderRadius.circular(8),
color: TargetColors.getBackgroundColor(previewTarget),
),
child: Stack(
alignment: Alignment.center,
children: [
Center(
child: Image.asset(
previewTarget,
fit: BoxFit.contain,
@@ -835,60 +764,188 @@ void _showTargetPopup() {
},
),
),
const SizedBox(height: 20),
// Target List
SizedBox(
width: double.maxFinite,
height: 300,
child: ListView(
// Multiple preview bullets scattered around
Positioned(
left: 30,
top: 40,
child: _buildPreviewBullet(previewBulletColor),
),
Positioned(
right: 25,
top: 50,
child: _buildPreviewBullet(previewBulletColor),
),
Positioned(
left: 40,
bottom: 30,
child: _buildPreviewBullet(previewBulletColor),
),
Positioned(
right: 35,
bottom: 40,
child: _buildPreviewBullet(previewBulletColor),
),
Positioned(
left: 50,
top: 100,
child: _buildPreviewBullet(previewBulletColor),
),
Positioned(
right: 50,
bottom: 80,
child: _buildPreviewBullet(previewBulletColor),
),
],
),
),
),
const SizedBox(height: 24),
// Tab Buttons and Content as one unit
Expanded(
child: Container(
decoration: BoxDecoration(
color: Colors.grey[800],
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
// Selection Buttons (Tabs)
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => innerSetState(() => selectedTab = 0),
style: ElevatedButton.styleFrom(
backgroundColor: selectedTab == 0
? _getColorFromName(previewBulletColor)
: Colors.grey[700],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8),
),
),
),
child: Text(
_t("Bullet"),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
),
Expanded(
child: ElevatedButton(
onPressed: () => innerSetState(() => selectedTab = 1),
style: ElevatedButton.styleFrom(
backgroundColor: selectedTab == 1
? _getColorFromName(previewBulletColor)
: Colors.grey[700],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(8),
),
),
),
child: Text(
_t("Target"),
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
// Tab Content
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: selectedTab == 0
? Center(
child: Wrap(
spacing: 20,
runSpacing: 20,
alignment: WrapAlignment.center,
children: _bulletColors.map((colorName) {
final color = _getColorFromName(colorName);
final isSelected = colorName == previewBulletColor;
return GestureDetector(
onTap: () => innerSetState(() => previewBulletColor = colorName),
child: Container(
width: 62,
height: 62,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? Colors.white : Colors.white24,
width: isSelected ? 4 : 2,
),
),
),
);
}).toList(),
),
)
: Column(
mainAxisSize: MainAxisSize.min,
children: _availableTargets.map((targetPath) {
final isSelected = targetPath == previewTarget;
final displayName = _getTargetDisplayName(targetPath);
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
padding: const EdgeInsets.only(bottom: 8.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
innerSetState(() => previewTarget = targetPath);
},
onPressed: () => innerSetState(() => previewTarget = targetPath),
style: ElevatedButton.styleFrom(
backgroundColor: isSelected
? _getColorFromName(_selectedBulletColor)
: Colors.grey[800],
backgroundColor: isSelected ? _getColorFromName(previewBulletColor) : Colors.grey[700],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text(
displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
child: Text(displayName),
),
),
);
}).toList(),
),
),
const SizedBox(height: 12),
),
],
),
),
),
const SizedBox(height: 16),
// Action Buttons
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () async {
await _saveBulletColor(previewBulletColor);
await _saveSelectedTarget(previewTarget);
if (context.mounted) {
Navigator.of(context).pop();
}
},
style: ElevatedButton.styleFrom(
backgroundColor: _getColorFromName(_selectedBulletColor),
backgroundColor: _getColorFromName(previewBulletColor),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text(_t("Apply")),
),
@@ -905,6 +962,7 @@ void _showTargetPopup() {
],
),
),
),
);
},
);
@@ -912,19 +970,28 @@ void _showTargetPopup() {
);
}
Widget _buildPreviewBullet(String bulletColor) {
const double bulletOpacity = 0.8;
return Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: _getColorFromName(bulletColor).withValues(alpha: bulletOpacity),
shape: BoxShape.circle,
border: Border.all(
color: Colors.black,
width: 1,
),
),
);
}
Widget _buildCameraList() {
return Container(
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 0),
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withValues(alpha: 0.3),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
+93 -35
View File
@@ -20,22 +20,11 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
List<bool> _enabledCameras = List.generate(6, (_) => false); // Which cameras have IPs configured
List<Map<String, dynamic>> _cameras = [];
Color _bulletColor = Colors.blue; // default/fallback bullet color
@override
void initState() {
super.initState();
_loadCameras();
_loadBulletColor();
_setSystemUIMode();
}
@override
void dispose() {
super.dispose();
}
void _setSystemUIMode() {
// No special system UI mode needed for portrait
}
String _t(String key) {
@@ -94,26 +83,34 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
return Colors.pink;
case 'lime':
return Colors.lime;
case 'mint':
return Colors.greenAccent;
case 'orange':
return Colors.orange;
case 'red':
return Colors.red;
case 'violet':
return Colors.deepPurpleAccent;
case 'amber':
return Colors.amber;
case 'brown':
return Colors.brown;
case 'grey':
return Colors.grey;
default:
return Colors.blue;
}
}
void _showCameraSelection() {
showModalBottomSheet(
showDialog(
context: context,
backgroundColor: Colors.grey[900],
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) => StatefulBuilder(
builder: (context, setModalState) => SingleChildScrollView(
builder: (context, setModalState) => Dialog(
backgroundColor: Colors.grey[900],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Container(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
padding: const EdgeInsets.fromLTRB(24, 20, 24, 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -121,15 +118,15 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
_t('Select Cameras'),
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 16),
// Single row of 6 camera buttons
const SizedBox(height: 24),
// Row 1: Cameras 1-3
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(6, (index) {
children: List.generate(3, (index) {
final cameraNum = index + 1;
final isSelected = _selectedCameras[index];
final isEnabled = _enabledCameras[index];
@@ -137,10 +134,9 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
return GestureDetector(
onTap: isEnabled
? () {
setModalState(() {
// Update both modal and parent state simultaneously
_selectedCameras[index] = !_selectedCameras[index];
});
// Also update the parent state to refresh camera grid
setModalState(() {});
if (mounted) {
setState(() {});
}
@@ -164,8 +160,8 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
child: Center(
child: Text(
'$cameraNum',
style: TextStyle(
color: isEnabled ? Colors.white : Colors.grey[500],
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
@@ -175,13 +171,74 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
);
}),
),
const SizedBox(height: 16),
// Row 2: Cameras 4-6
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(3, (index) {
final cameraNum = index + 4;
final arrayIndex = index + 3;
final isSelected = _selectedCameras[arrayIndex];
final isEnabled = _enabledCameras[arrayIndex];
return GestureDetector(
onTap: isEnabled
? () {
// Update both modal and parent state simultaneously
_selectedCameras[arrayIndex] = !_selectedCameras[arrayIndex];
setModalState(() {});
if (mounted) {
setState(() {});
}
}
: null,
child: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: !isEnabled
? Colors.grey[700]
: (isSelected ? _bulletColor : Colors.grey[800]),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: !isEnabled
? Colors.grey[600]!
: (isSelected ? _bulletColor : Colors.grey[700]!),
width: 2,
),
),
child: Center(
child: Text(
'$cameraNum',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
);
}),
),
const SizedBox(height: 24),
Container(
padding: const EdgeInsets.only(top: 16),
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: Colors.grey[700]!, width: 1),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
height: 40,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
backgroundColor: _bulletColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
@@ -200,12 +257,14 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
],
),
),
],
),
),
),
),
);
}
@override
Widget build(BuildContext context) {
final visibleCameras = <Map<String, dynamic>>[];
@@ -215,6 +274,7 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
}
}
// Portrait list view
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
@@ -280,8 +340,7 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
stream: streamUrl,
isLive: true,
fit: BoxFit.cover,
error: (context, error, stackTrace) {
return Container(
error: (context, error, stackTrace) => Container(
color: Colors.grey[700],
child: Center(
child: Column(
@@ -305,8 +364,7 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
],
),
),
);
},
),
),
// Camera number circle - top right (with bullet color)
Positioned(
+39 -84
View File
@@ -6,6 +6,7 @@ import 'dart:convert';
import 'package:image/image.dart' as img;
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/translations.dart';
import '../utils/target_colors.dart';
class TargetViewScreen extends StatefulWidget {
final List<File> jsonFiles;
@@ -37,7 +38,6 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
late String _targetFilePath;
late String targetAssetPath;
late String _selectedTargetPath; // For display
final double bulletSize = 19;
double _scaleFactorX = 1.0; // Scale factor for X coordinate
double _scaleFactorY = 1.0; // Scale factor for Y coordinate
@@ -45,7 +45,6 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
String _selectedLanguage = 'English';
Color _backgroundColor = Colors.white;
@override
void initState() {
super.initState();
@@ -75,7 +74,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
Future<void> _loadSelectedTarget() async {
final prefs = await SharedPreferences.getInstance();
final selectedTarget = prefs.getString('selectedTarget') ?? 'assets/target.png';
final selectedTarget =
prefs.getString('selectedTarget') ?? 'assets/target.png';
// Always use default target for bullet positioning
targetAssetPath = 'assets/target.png';
// Store selected target for display
@@ -84,61 +84,7 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
}
Color _getBackgroundColorForTarget(String targetPath) {
// Map each target to its background color (extracted from first pixel of each image)
switch (targetPath) {
case 'assets/target.png':
return const Color(0xFFFFFFFF); // White
case 'assets/targets/Target1.jpg':
return const Color(0xFFE8ECD5); // Light beige
case 'assets/targets/Target2.jpg':
return const Color(0xFFC1DF87); // Light green
case 'assets/targets/Target3.jpg':
return const Color(0xFFFEFEFE); // Off-white
case 'assets/targets/Target4.jpg':
return const Color(0xFFD42931); // Red
case 'assets/targets/Target5.jpg':
return const Color(0xFF02050A); // Very dark blue/black
case 'assets/targets/Target6.jpg':
return const Color(0xFFF3E7D1); // Light beige
case 'assets/targets/Target7.jpg':
return const Color(0xFFFFD454); // Bright yellow
case 'assets/targets/Target8.jpg':
return const Color(0xFF1E5E81); // Dark teal blue
case 'assets/targets/Target9.jpg':
return const Color(0xFFFEFEFC); // Off-white
case 'assets/targets/Target10.jpg':
return const Color(0xFF98926E); // Olive green
case 'assets/targets/Target11.jpg':
return const Color(0xFF13132B); // Dark blue
case 'assets/targets/Target12.jpg':
return const Color(0xFFF5F4EF); // Off-white
case 'assets/targets/Target13.jpg':
return const Color(0xFF174AA5); // Blue
case 'assets/targets/Target14.jpg':
return const Color(0xFFFEE05E); // Light yellow
case 'assets/targets/Target15.jpg':
return const Color(0xFFFFFFFF); // White
case 'assets/targets/Target16.jpg':
return const Color(0xFF000000); // Black
case 'assets/targets/Target17.jpg':
return const Color(0xFF13132B); // Dark blue
case 'assets/targets/Target18.jpg':
return const Color(0xFF050608); // Very dark gray
case 'assets/targets/Target19.jpg':
return const Color(0xFFC32534); // Dark red
case 'assets/targets/Target20.jpg':
return const Color(0xFFFCF3EE); // Light beige
case 'assets/targets/Target21.jpg':
return const Color(0xFFDA6868); // Muted red
case 'assets/targets/Target22.jpg':
return const Color(0xFFFDFAF3); // Off-white
case 'assets/targets/Target23.jpg':
return const Color(0xFFFFFFFD); // White
case 'assets/targets/Target24.jpg':
return const Color(0xFFFFC4C6); // Light pink
default:
return Colors.white; // Fallback to white
}
return TargetColors.getBackgroundColor(targetPath);
}
Future<void> _loadBulletColor() async {
@@ -187,8 +133,6 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
}
}
Future<void> _loadJsonAndImage() async {
try {
// Load default target for bullet positioning reference
@@ -217,7 +161,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
return;
}
_targetImageBytes = Uint8List.fromList(img.encodePng(_targetImageDecoded!));
_targetImageBytes =
Uint8List.fromList(img.encodePng(_targetImageDecoded!));
final currentFile = widget.jsonFiles[_currentIndex];
final jsonString = await currentFile.readAsString();
@@ -322,7 +267,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
builder: (BuildContext context) {
return Dialog(
backgroundColor: Colors.grey[900],
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
@@ -341,7 +287,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
const SizedBox(height: 16),
Text(
_t("Are you sure you want to delete this target?"),
style: const TextStyle(color: Colors.white70, fontSize: 16),
style: const TextStyle(
color: Colors.white70, fontSize: 16),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
@@ -357,7 +304,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
padding:
const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
@@ -375,7 +323,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[800],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
padding:
const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
@@ -389,7 +338,6 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
);
},
);
},
),
],
@@ -425,6 +373,10 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
final scaleX = displayWidth / imgWidth;
final scaleY = displayHeight / imgHeight;
// Scale bullet size by target scale factors and display scale factors
final dynamicBulletSize =
(((_scaleFactorX + _scaleFactorY) / 2) * 53) *
((scaleX + scaleY) / 2);
return Stack(
children: [
@@ -433,7 +385,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
top: offsetY,
width: displayWidth,
height: displayHeight,
child: Image.memory(_targetImageBytes!, fit: BoxFit.fill),
child: Image.memory(_targetImageBytes!,
fit: BoxFit.fill),
),
..._scaledBullets.asMap().entries.map((entry) {
final i = entry.key;
@@ -443,29 +396,31 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
final dy = scaledBullet.dy * scaleY + offsetY;
// Score is calculated using original bullet position and default target center
final originalBullet = _bullets[i];
final score = _scoreForBullet(originalBullet, _targetCenter ?? Offset.zero);
final score = _scoreForBullet(
originalBullet, _targetCenter ?? Offset.zero);
final isHighlighted = widget.isCombinedView
? (_pressedScoreGroup == score)
: (i == _highlightedBulletIndex);
return Positioned(
left: dx - bulletSize / 2,
top: dy - bulletSize / 2,
left: dx - dynamicBulletSize / 2,
top: dy - dynamicBulletSize / 2,
child: Container(
width: bulletSize,
height: bulletSize,
width: dynamicBulletSize,
height: dynamicBulletSize,
decoration: BoxDecoration(
color: _bulletColor.withOpacity(0.8),
color: _bulletColor.withValues(alpha: 0.8),
shape: BoxShape.circle,
border: Border.all(
color: isHighlighted ? Colors.red : Colors.black,
color: isHighlighted
? Colors.red
: Colors.black,
width: isHighlighted ? 3 : 1,
),
),
),
);
})
.toList(),
}).toList(),
],
);
},
@@ -474,7 +429,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
const SizedBox(height: 12),
Container(
color: _backgroundColor,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: Row(
children: widget.isCombinedView
? _buildGroupedScoreButtons()
@@ -497,7 +453,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
fit: BoxFit.scaleDown,
child: Row(
children: [
const Icon(Icons.track_changes, size: 28, color: Colors.white),
const Icon(Icons.track_changes,
size: 28, color: Colors.white),
const SizedBox(width: 8),
Text(
"${_t('Total score')} $_totalScore",
@@ -580,7 +537,7 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
decoration: BoxDecoration(
color: _pressedScoreGroup == score
? Colors.red[400]
: _bulletColor.withOpacity(0.85),
: _bulletColor.withValues(alpha: 0.85),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
@@ -609,8 +566,6 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
}).toList();
}
List<Widget> _buildIndividualScoreButtons() {
final scoredBullets = _bullets.asMap().entries.map((entry) {
final index = entry.key;
@@ -620,7 +575,8 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
}).toList();
// Sort descending by score
scoredBullets.sort((a, b) => (b['score'] as int).compareTo(a['score'] as int));
scoredBullets
.sort((a, b) => (b['score'] as int).compareTo(a['score'] as int));
return scoredBullets.map((entry) {
final int index = entry['index'] as int;
@@ -671,7 +627,7 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
decoration: BoxDecoration(
color: _highlightedBulletIndex == index
? Colors.red[400]
: _bulletColor.withOpacity(0.85),
: _bulletColor.withValues(alpha: 0.85),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16),
bottomRight: Radius.circular(16),
@@ -696,5 +652,4 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
);
}).toList();
}
}
+14 -4
View File
@@ -83,11 +83,21 @@ class YOLODetector {
if (score < confidenceThreshold) continue;
// The model outputs coordinates normalized to [0, 1] relative to the input tensor (inputWidth x inputHeight)
// We need to scale back to the original cropped image coordinates
// The cropped image (cropWidth x cropHeight) was resized to (inputWidth x inputHeight)
final scaledX1 = x1 * croppedResult.cropWidth;
final scaledY1 = y1 * croppedResult.cropHeight;
final scaledX2 = x2 * croppedResult.cropWidth;
final scaledY2 = y2 * croppedResult.cropHeight;
// Add offset to get coordinates in the original image space
results.add(DetectionResult(
x1 * croppedResult.cropWidth + croppedResult.offsetX,
y1 * croppedResult.cropHeight + croppedResult.offsetY,
x2 * croppedResult.cropWidth + croppedResult.offsetX,
y2 * croppedResult.cropHeight + croppedResult.offsetY,
scaledX1 + croppedResult.offsetX,
scaledY1 + croppedResult.offsetY,
scaledX2 + croppedResult.offsetX,
scaledY2 + croppedResult.offsetY,
score,
classId,
));
+12 -6
View File
@@ -19,14 +19,20 @@ class TargetColors {
'assets/targets/Target14.jpg': Color(0xFFFEE05E), // Light yellow
'assets/targets/Target15.jpg': Color(0xFFFFFFFF), // White
'assets/targets/Target16.jpg': Color(0xFF000000), // Black
'assets/targets/Target17.jpg': Color(0xFF13132B), // Dark blue
'assets/targets/Target17.jpg': Color(0xFF21304D), // Dark blue
'assets/targets/Target18.jpg': Color(0xFF050608), // Very dark gray
'assets/targets/Target19.jpg': Color(0xFFC32534), // Dark red
'assets/targets/Target20.jpg': Color(0xFFFCF3EE), // Light beige
'assets/targets/Target19.jpg': Color(0xFF09080D), // Dark
'assets/targets/Target20.jpg': Color(0xFFA9D9EF), // Light blue
'assets/targets/Target21.jpg': Color(0xFFDA6868), // Muted red
'assets/targets/Target22.jpg': Color(0xFFFDFAF3), // Off-white
'assets/targets/Target23.jpg': Color(0xFFFFFFFD), // White
'assets/targets/Target24.jpg': Color(0xFFFFC4C6), // Light pink
'assets/targets/Target22.jpg': Color(0xFFFBFAF6), // Off-white
'assets/targets/Target23.jpg': Color(0xFF000000), // Black
'assets/targets/Target24.jpg': Color(0xFF000000), // Black
'assets/targets/Target25.jpg': Color(0xFF040308), // Very dark gray
'assets/targets/Target26.jpg': Color(0xFF161C2C), // Dark blue-gray
'assets/targets/Target27.jpg': Color(0xFF28A5DF), // Blue
'assets/targets/Target28.jpg': Color(0xFFFFFFFF), // White
'assets/targets/Target29.jpg': Color(0xFF180E27), // Dark purple
'assets/targets/Target30.jpg': Color(0xFFFDFDFB), // Off-white
};
static Color getBackgroundColor(String targetPath) {
+14
View File
@@ -18,6 +18,20 @@ class AppTranslations {
'Slovenian': 'Slovenščina',
},
// Appearance Settings
'Appearance': {
'English': 'Appearance',
'Slovenian': 'Videz',
},
'Appearance Settings': {
'English': 'Appearance Settings',
'Slovenian': 'Nastavitve videza',
},
'Bullet': {
'English': 'Bullet',
'Slovenian': 'Zadetek',
},
// Bullet color
'Select Bullet Color': {
'English': 'Select Bullet Color',