added new tragets... fixed some issues.
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 457 KiB |
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 260 KiB |
|
Before Width: | Height: | Size: 456 KiB After Width: | Height: | Size: 577 KiB |
|
Before Width: | Height: | Size: 461 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 268 KiB After Width: | Height: | Size: 222 KiB |
|
After Width: | Height: | Size: 491 KiB |
|
After Width: | Height: | Size: 337 KiB |
|
After Width: | Height: | Size: 342 KiB |
|
After Width: | Height: | Size: 315 KiB |
|
After Width: | Height: | Size: 425 KiB |
|
After Width: | Height: | Size: 347 KiB |
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,177 +474,161 @@ String _getTargetDisplayName(String targetPath) {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBulletColorSelector() {
|
||||
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 Bullet Color"),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Content
|
||||
GestureDetector(
|
||||
onTap: _showBulletColorPopup,
|
||||
child: Container(
|
||||
Widget _buildAppearanceSelector() {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
color: Colors.grey[900],
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
topLeft: Radius.circular(12),
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
width: 4,
|
||||
height: 16,
|
||||
margin: const EdgeInsets.only(right: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: _getColorFromName(_selectedBulletColor),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black26),
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_t(_selectedBulletColor),
|
||||
style: const TextStyle(color: Colors.black87, fontSize: 14),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
_t("Appearance"),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
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),
|
||||
// Content
|
||||
GestureDetector(
|
||||
onTap: _showAppearancePopup,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
_t("Select Target"),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// 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(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
_getTargetDisplayName(_selectedTarget),
|
||||
style: const TextStyle(color: Colors.black87, fontSize: 14),
|
||||
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 Icon(Icons.chevron_right, color: Colors.black54),
|
||||
],
|
||||
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,
|
||||
decoration: BoxDecoration(
|
||||
color: _getColorFromName(_selectedBulletColor),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.black26, width: 1),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_selectedBulletColor,
|
||||
style: const TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Icon(Icons.chevron_right, color: Colors.black54),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showLanguagePopup() {
|
||||
showDialog(
|
||||
@@ -725,192 +706,285 @@ 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,
|
||||
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(
|
||||
children: [
|
||||
Text(
|
||||
_t("Appearance Settings"),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// 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),
|
||||
color: TargetColors.getBackgroundColor(previewTarget),
|
||||
),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Center(
|
||||
child: Image.asset(
|
||||
previewTarget,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey[800],
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_not_supported, color: Colors.white54),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// 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: 8.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () => innerSetState(() => previewTarget = targetPath),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: isSelected ? _getColorFromName(previewBulletColor) : Colors.grey[700],
|
||||
foregroundColor: Colors.white,
|
||||
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),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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(previewBulletColor),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: Text(_t("Apply")),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(_t("Cancel"), style: const TextStyle(color: Colors.white70)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showTargetPopup() {
|
||||
String previewTarget = _selectedTarget;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return StatefulBuilder(
|
||||
builder: (context, innerSetState) {
|
||||
|
||||
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 Target"),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Image Preview
|
||||
Container(
|
||||
width: 200,
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white24),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Image.asset(
|
||||
previewTarget,
|
||||
fit: BoxFit.contain,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey[800],
|
||||
child: const Center(
|
||||
child: Icon(Icons.image_not_supported, color: Colors.white54),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// Target List
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
height: 300,
|
||||
child: ListView(
|
||||
children: _availableTargets.map((targetPath) {
|
||||
final isSelected = targetPath == previewTarget;
|
||||
final displayName = _getTargetDisplayName(targetPath);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
innerSetState(() => previewTarget = targetPath);
|
||||
},
|
||||
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: Text(displayName),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Action Buttons
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () async {
|
||||
await _saveSelectedTarget(previewTarget);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _getColorFromName(_selectedBulletColor),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
),
|
||||
child: Text(_t("Apply")),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(_t("Cancel"), style: const TextStyle(color: Colors.white70)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCameraList() {
|
||||
return Container(
|
||||
@@ -918,13 +992,6 @@ void _showTargetPopup() {
|
||||
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,
|
||||
|
||||
@@ -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,110 +83,181 @@ 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: [
|
||||
Text(
|
||||
_t('Select Cameras'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
Text(
|
||||
_t('Select Cameras'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Single row of 6 camera buttons
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(6, (index) {
|
||||
final cameraNum = index + 1;
|
||||
final isSelected = _selectedCameras[index];
|
||||
final isEnabled = _enabledCameras[index];
|
||||
const SizedBox(height: 24),
|
||||
// Row 1: Cameras 1-3
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(3, (index) {
|
||||
final cameraNum = index + 1;
|
||||
final isSelected = _selectedCameras[index];
|
||||
final isEnabled = _enabledCameras[index];
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isEnabled
|
||||
? () {
|
||||
setModalState(() {
|
||||
return GestureDetector(
|
||||
onTap: isEnabled
|
||||
? () {
|
||||
// Update both modal and parent state simultaneously
|
||||
_selectedCameras[index] = !_selectedCameras[index];
|
||||
});
|
||||
// Also update the parent state to refresh camera grid
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
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(
|
||||
: null,
|
||||
child: Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: !isEnabled
|
||||
? Colors.grey[600]!
|
||||
: (isSelected ? _bulletColor : Colors.grey[700]!),
|
||||
width: 2,
|
||||
? 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: TextStyle(
|
||||
color: isEnabled ? Colors.white : Colors.grey[500],
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'$cameraNum',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 40,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[800],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
);
|
||||
}),
|
||||
),
|
||||
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),
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
_t('Done'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 40,
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _bulletColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
_t('Done'),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -205,7 +265,6 @@ 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,33 +340,31 @@ class _SpectatorScreenState extends State<SpectatorScreen> {
|
||||
stream: streamUrl,
|
||||
isLive: true,
|
||||
fit: BoxFit.cover,
|
||||
error: (context, error, stackTrace) {
|
||||
return Container(
|
||||
color: Colors.grey[700],
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.videocam_off,
|
||||
error: (context, error, stackTrace) => Container(
|
||||
color: Colors.grey[700],
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.videocam_off,
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"${_t('Camera')} $cameraIndex",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
size: 32,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"${_t('Camera')} $cameraIndex",
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// Camera number circle - top right (with bullet color)
|
||||
Positioned(
|
||||
|
||||
@@ -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,110 +84,54 @@ 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 {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final selectedColor = prefs.getString('bulletColor') ?? 'Blue';
|
||||
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selectedColor);
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
_bulletColor = _getColorFromName(selectedColor);
|
||||
});
|
||||
}
|
||||
|
||||
Color _getColorFromName(String colorName) {
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'orange':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
case 'amber':
|
||||
return Colors.amber;
|
||||
case 'brown':
|
||||
return Colors.brown;
|
||||
case 'grey':
|
||||
return Colors.grey;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
switch (colorName.toLowerCase()) {
|
||||
case 'blue':
|
||||
return Colors.blue;
|
||||
case 'green':
|
||||
return Colors.green;
|
||||
case 'yellow':
|
||||
return Colors.yellow;
|
||||
case 'purple':
|
||||
return Colors.purple;
|
||||
case 'cyan':
|
||||
return Colors.cyan;
|
||||
case 'teal':
|
||||
return Colors.teal;
|
||||
case 'indigo':
|
||||
return Colors.indigo;
|
||||
case 'pink':
|
||||
return Colors.pink;
|
||||
case 'lime':
|
||||
return Colors.lime;
|
||||
case 'mint':
|
||||
return Colors.greenAccent;
|
||||
case 'orange':
|
||||
return Colors.orange;
|
||||
case 'violet':
|
||||
return Colors.deepPurpleAccent;
|
||||
case 'amber':
|
||||
return Colors.amber;
|
||||
case 'brown':
|
||||
return Colors.brown;
|
||||
case 'grey':
|
||||
return Colors.grey;
|
||||
default:
|
||||
return Colors.blue; // fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Future<void> _loadJsonAndImage() async {
|
||||
try {
|
||||
@@ -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,39 +385,42 @@ 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;
|
||||
final scaledBullet = entry.value;
|
||||
// Scaled bullets are already scaled to display target dimensions
|
||||
final dx = scaledBullet.dx * scaleX + offsetX;
|
||||
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 isHighlighted = widget.isCombinedView
|
||||
? (_pressedScoreGroup == score)
|
||||
: (i == _highlightedBulletIndex);
|
||||
final i = entry.key;
|
||||
final scaledBullet = entry.value;
|
||||
// Scaled bullets are already scaled to display target dimensions
|
||||
final dx = scaledBullet.dx * scaleX + offsetX;
|
||||
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 isHighlighted = widget.isCombinedView
|
||||
? (_pressedScoreGroup == score)
|
||||
: (i == _highlightedBulletIndex);
|
||||
|
||||
return Positioned(
|
||||
left: dx - bulletSize / 2,
|
||||
top: dy - bulletSize / 2,
|
||||
child: Container(
|
||||
width: bulletSize,
|
||||
height: bulletSize,
|
||||
decoration: BoxDecoration(
|
||||
color: _bulletColor.withOpacity(0.8),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: isHighlighted ? Colors.red : Colors.black,
|
||||
width: isHighlighted ? 3 : 1,
|
||||
return Positioned(
|
||||
left: dx - dynamicBulletSize / 2,
|
||||
top: dy - dynamicBulletSize / 2,
|
||||
child: Container(
|
||||
width: dynamicBulletSize,
|
||||
height: dynamicBulletSize,
|
||||
decoration: BoxDecoration(
|
||||
color: _bulletColor.withValues(alpha: 0.8),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
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",
|
||||
@@ -518,55 +475,146 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
}
|
||||
|
||||
List<Widget> _buildGroupedScoreButtons() {
|
||||
final Map<int, int> scoreGroups = {};
|
||||
for (final bullet in _bullets) {
|
||||
final score = _scoreForBullet(bullet, _targetCenter!);
|
||||
scoreGroups[score] = (scoreGroups[score] ?? 0) + 1;
|
||||
}
|
||||
final Map<int, int> scoreGroups = {};
|
||||
for (final bullet in _bullets) {
|
||||
final score = _scoreForBullet(bullet, _targetCenter!);
|
||||
scoreGroups[score] = (scoreGroups[score] ?? 0) + 1;
|
||||
}
|
||||
|
||||
// Sort scores descending (10 → 0)
|
||||
final sortedEntries = scoreGroups.entries.toList()
|
||||
..sort((a, b) => b.key.compareTo(a.key));
|
||||
// Sort scores descending (10 → 0)
|
||||
final sortedEntries = scoreGroups.entries.toList()
|
||||
..sort((a, b) => b.key.compareTo(a.key));
|
||||
|
||||
return sortedEntries.map((entry) {
|
||||
final score = entry.key;
|
||||
final count = entry.value;
|
||||
return sortedEntries.map((entry) {
|
||||
final score = entry.key;
|
||||
final count = entry.value;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _pressedScoreGroup = score),
|
||||
onTapUp: (_) => setState(() => _pressedScoreGroup = null),
|
||||
onTapCancel: () => setState(() => _pressedScoreGroup = null),
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _pressedScoreGroup = score),
|
||||
onTapUp: (_) => setState(() => _pressedScoreGroup = null),
|
||||
onTapCancel: () => setState(() => _pressedScoreGroup = null),
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'${count}x',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _pressedScoreGroup == score
|
||||
? Colors.red[400]
|
||||
: _bulletColor.withValues(alpha: 0.85),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
'$score',
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<Widget> _buildIndividualScoreButtons() {
|
||||
final scoredBullets = _bullets.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final bullet = entry.value;
|
||||
final score = _scoreForBullet(bullet, _targetCenter ?? Offset.zero);
|
||||
return {'index': index, 'score': score, 'bullet': bullet};
|
||||
}).toList();
|
||||
|
||||
// Sort descending by score
|
||||
scoredBullets
|
||||
.sort((a, b) => (b['score'] as int).compareTo(a['score'] as int));
|
||||
|
||||
return scoredBullets.map((entry) {
|
||||
final int index = entry['index'] as int;
|
||||
final int score = entry['score'] as int;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _highlightedBulletIndex = index),
|
||||
onTapUp: (_) => setState(() => _highlightedBulletIndex = null),
|
||||
onTapCancel: () => setState(() => _highlightedBulletIndex = null),
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${count}x',
|
||||
'${index + 1}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
@@ -574,21 +622,18 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _pressedScoreGroup == score
|
||||
? Colors.red[400]
|
||||
: _bulletColor.withOpacity(0.85),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _highlightedBulletIndex == index
|
||||
? Colors.red[400]
|
||||
: _bulletColor.withValues(alpha: 0.85),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'$score',
|
||||
style: const TextStyle(
|
||||
@@ -599,102 +644,12 @@ class _TargetViewScreenState extends State<TargetViewScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildIndividualScoreButtons() {
|
||||
final scoredBullets = _bullets.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final bullet = entry.value;
|
||||
final score = _scoreForBullet(bullet, _targetCenter ?? Offset.zero);
|
||||
return {'index': index, 'score': score, 'bullet': bullet};
|
||||
}).toList();
|
||||
|
||||
// Sort descending by score
|
||||
scoredBullets.sort((a, b) => (b['score'] as int).compareTo(a['score'] as int));
|
||||
|
||||
return scoredBullets.map((entry) {
|
||||
final int index = entry['index'] as int;
|
||||
final int score = entry['score'] as int;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: GestureDetector(
|
||||
onTapDown: (_) => setState(() => _highlightedBulletIndex = index),
|
||||
onTapUp: (_) => setState(() => _highlightedBulletIndex = null),
|
||||
onTapCancel: () => setState(() => _highlightedBulletIndex = null),
|
||||
child: Container(
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
offset: Offset(0, 2),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[300],
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'${index + 1}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: _highlightedBulletIndex == index
|
||||
? Colors.red[400]
|
||||
: _bulletColor.withOpacity(0.85),
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'$score',
|
||||
style: const TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||