added some camera changes and included IOS support
This commit is contained in:
+56
@@ -0,0 +1,56 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
|
||||||
|
# Ensure minimum iOS deployment target
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
|
||||||
|
config.build_settings['ENABLE_BITCODE'] = 'NO'
|
||||||
|
|
||||||
|
# Fix for Swift compatibility
|
||||||
|
if config.build_settings['WRAPPER_EXTENSION'] == 'bundle'
|
||||||
|
config.build_settings['DEVELOPMENT_TEAM'] = 'YourTeamID'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -45,5 +45,27 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>This app needs camera access to capture and analyze shooting targets.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>This app needs photo library access to save and load target images.</string>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>This app needs permission to save captured target images to your photo library.</string>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>This app needs microphone access for video recording.</string>
|
||||||
|
<key>NSLocalNetworkUsageDescription</key>
|
||||||
|
<string>This app needs local network access to connect to camera streams.</string>
|
||||||
|
<key>NSBonjourServices</key>
|
||||||
|
<array>
|
||||||
|
<string>_http._tcp</string>
|
||||||
|
<string>_https._tcp</string>
|
||||||
|
</array>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -318,24 +318,6 @@ class _CalendarScreenState extends State<CalendarScreen> {
|
|||||||
color: isToday ? accentColor : Colors.black87,
|
color: isToday ? accentColor : Colors.black87,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (isToday) ...[
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: accentColor,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
_t('NOW'),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+233
-13
@@ -24,6 +24,10 @@ class _CameraScreenState extends State<CameraScreen> {
|
|||||||
int _numberOfBullets = 5;
|
int _numberOfBullets = 5;
|
||||||
String _selectedLanguage = 'English';
|
String _selectedLanguage = 'English';
|
||||||
|
|
||||||
|
// Brightness and contrast controls
|
||||||
|
double _brightness = 0.0; // -1.0 to 1.0
|
||||||
|
double _contrast = 1.0; // 0.0 to 2.0
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -31,9 +35,24 @@ class _CameraScreenState extends State<CameraScreen> {
|
|||||||
_initCamera();
|
_initCamera();
|
||||||
_loadBulletColor(); // 👈
|
_loadBulletColor(); // 👈
|
||||||
_loadLanguage();
|
_loadLanguage();
|
||||||
|
_loadBrightnessContrast();
|
||||||
YOLODetector.create().then((detector) => _yoloDetector = detector);
|
YOLODetector.create().then((detector) => _yoloDetector = detector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadBrightnessContrast() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
setState(() {
|
||||||
|
_brightness = prefs.getDouble('brightness') ?? 0.0;
|
||||||
|
_contrast = prefs.getDouble('contrast') ?? 1.0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveBrightnessContrast() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setDouble('brightness', _brightness);
|
||||||
|
await prefs.setDouble('contrast', _contrast);
|
||||||
|
}
|
||||||
|
|
||||||
String _t(String key) {
|
String _t(String key) {
|
||||||
return AppTranslations.translate(key, _selectedLanguage);
|
return AppTranslations.translate(key, _selectedLanguage);
|
||||||
}
|
}
|
||||||
@@ -240,6 +259,175 @@ Color _getColorFromName(String colorName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _showHelpDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_t('Help'),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildHelpItem(Icons.camera_alt, _t('Tap camera button to capture and detect bullets')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.filter_9_plus, _t('Bottom left number: Set expected bullet count (1-5)')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.brightness_6, _t('Sun icon: Adjust brightness and contrast for better visibility')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.crop_square, _t('Align target inside the square frame')),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _bulletColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text(_t('Got it!')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHelpItem(IconData icon, String text) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: _bulletColor, size: 20),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showBrightnessContrastDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setDialogState) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_t('Brightness & Contrast'),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Brightness slider
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${_t('Brightness')}: ${(_brightness * 100).toInt()}',
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: _brightness,
|
||||||
|
min: -1.0,
|
||||||
|
max: 1.0,
|
||||||
|
divisions: 40,
|
||||||
|
activeColor: _bulletColor,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_brightness = value;
|
||||||
|
});
|
||||||
|
setDialogState(() {});
|
||||||
|
_saveBrightnessContrast();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Contrast slider
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${_t('Contrast')}: ${(_contrast * 100).toInt()}%',
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: _contrast,
|
||||||
|
min: 0.5,
|
||||||
|
max: 2.0,
|
||||||
|
divisions: 30,
|
||||||
|
activeColor: _bulletColor,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_contrast = value;
|
||||||
|
});
|
||||||
|
setDialogState(() {});
|
||||||
|
_saveBrightnessContrast();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_brightness = 0.0;
|
||||||
|
_contrast = 1.0;
|
||||||
|
});
|
||||||
|
setDialogState(() {});
|
||||||
|
_saveBrightnessContrast();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[800],
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text(_t('Reset')),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _bulletColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text(_t('Done')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update the bullet count and save it
|
// Update the bullet count and save it
|
||||||
void _selectBulletCount(int number) {
|
void _selectBulletCount(int number) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -274,26 +462,58 @@ Color _getColorFromName(String colorName) {
|
|||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
actions: [
|
actions: [
|
||||||
// Display the selected bullet count
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Text(
|
icon: const Icon(Icons.help_outline, color: Colors.white),
|
||||||
'$_numberOfBullets',
|
onPressed: _showHelpDialog,
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: _showBulletDialog, // Show dialog on tap
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16), // Space after the number
|
const SizedBox(width: 16),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomAppBar(
|
bottomNavigationBar: BottomAppBar(
|
||||||
color: Colors.grey[900],
|
color: Colors.grey[900],
|
||||||
shape: const CircularNotchedRectangle(),
|
shape: const CircularNotchedRectangle(),
|
||||||
notchMargin: 8.0,
|
notchMargin: 8.0,
|
||||||
child: SizedBox(height: 60),
|
child: SizedBox(
|
||||||
|
height: 60,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Left - Bullet count (just number)
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _showBulletDialog,
|
||||||
|
child: Text(
|
||||||
|
'$_numberOfBullets',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Center space for FAB
|
||||||
|
const SizedBox(width: 80),
|
||||||
|
// Right section - Brightness
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.brightness_6, color: Colors.white, size: 24),
|
||||||
|
onPressed: _showBrightnessContrastDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
@@ -327,7 +547,7 @@ Color _getColorFromName(String colorName) {
|
|||||||
height: screenSize.width * 0.9,
|
height: screenSize.width * 0.9,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: _bulletColor.withOpacity(0.9),
|
color: _bulletColor.withValues(alpha: 0.9),
|
||||||
width: 3,
|
width: 3,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
+310
-49
@@ -34,6 +34,10 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTick
|
|||||||
late YOLODetector _yoloDetector;
|
late YOLODetector _yoloDetector;
|
||||||
int _numberOfBullets = 5;
|
int _numberOfBullets = 5;
|
||||||
|
|
||||||
|
// Brightness and contrast controls
|
||||||
|
double _brightness = 0.0; // -1.0 to 1.0
|
||||||
|
double _contrast = 1.0; // 0.0 to 2.0
|
||||||
|
|
||||||
late AnimationController _zoomAnimController;
|
late AnimationController _zoomAnimController;
|
||||||
Animation<Matrix4>? _zoomAnimation;
|
Animation<Matrix4>? _zoomAnimation;
|
||||||
Matrix4? _originalMatrix;
|
Matrix4? _originalMatrix;
|
||||||
@@ -53,6 +57,7 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTick
|
|||||||
_loadBulletCount();
|
_loadBulletCount();
|
||||||
_loadSelectedFilter();
|
_loadSelectedFilter();
|
||||||
_loadBulletColor(); // 👈 add this to actually load the saved bullet color
|
_loadBulletColor(); // 👈 add this to actually load the saved bullet color
|
||||||
|
_loadBrightnessContrast();
|
||||||
|
|
||||||
|
|
||||||
YOLODetector.create().then((detector) => _yoloDetector = detector);
|
YOLODetector.create().then((detector) => _yoloDetector = detector);
|
||||||
@@ -68,6 +73,20 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> with SingleTick
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadBrightnessContrast() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
setState(() {
|
||||||
|
_brightness = prefs.getDouble('brightness') ?? 0.0;
|
||||||
|
_contrast = prefs.getDouble('contrast') ?? 1.0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveBrightnessContrast() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setDouble('brightness', _brightness);
|
||||||
|
await prefs.setDouble('contrast', _contrast);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadLanguage() async {
|
Future<void> _loadLanguage() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -428,6 +447,177 @@ Color _getColorFromName(String colorName) {
|
|||||||
await prefs.setInt('bulletCount', number);
|
await prefs.setInt('bulletCount', number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showHelpDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_t('Help'),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
_buildHelpItem(Icons.camera_alt, _t('Tap camera button to capture and detect bullets')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.filter_9_plus, _t('Bottom left number: Set expected bullet count (1-5)')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.brightness_6, _t('Sun icon: Adjust brightness and contrast for better visibility')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.filter_alt, _t('Filter icon: Apply color filters (None, Greyscale, Invert)')),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildHelpItem(Icons.pinch, _t('Pinch to zoom on the camera stream')),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _bulletColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text(_t('Got it!')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHelpItem(IconData icon, String text) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Icon(icon, color: _bulletColor, size: 20),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showBrightnessContrastDialog() {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setDialogState) {
|
||||||
|
return Dialog(
|
||||||
|
backgroundColor: Colors.grey[900],
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_t('Brightness & Contrast'),
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// Brightness slider
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${_t('Brightness')}: ${(_brightness * 100).toInt()}',
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: _brightness,
|
||||||
|
min: -1.0,
|
||||||
|
max: 1.0,
|
||||||
|
divisions: 40,
|
||||||
|
activeColor: _bulletColor,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_brightness = value;
|
||||||
|
});
|
||||||
|
setDialogState(() {});
|
||||||
|
_saveBrightnessContrast();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Contrast slider
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${_t('Contrast')}: ${(_contrast * 100).toInt()}%',
|
||||||
|
style: const TextStyle(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
value: _contrast,
|
||||||
|
min: 0.5,
|
||||||
|
max: 2.0,
|
||||||
|
divisions: 30,
|
||||||
|
activeColor: _bulletColor,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_contrast = value;
|
||||||
|
});
|
||||||
|
setDialogState(() {});
|
||||||
|
_saveBrightnessContrast();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_brightness = 0.0;
|
||||||
|
_contrast = 1.0;
|
||||||
|
});
|
||||||
|
setDialogState(() {});
|
||||||
|
_saveBrightnessContrast();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.grey[800],
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text(_t('Reset')),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: _bulletColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
child: Text(_t('Done')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_zoomAnimController.dispose();
|
_zoomAnimController.dispose();
|
||||||
@@ -449,24 +639,70 @@ void dispose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColorFilter _buildColorFilter() {
|
ColorFilter _buildColorFilter() {
|
||||||
|
// Base filter matrix
|
||||||
|
List<double> baseMatrix;
|
||||||
|
|
||||||
switch (_selectedFilter) {
|
switch (_selectedFilter) {
|
||||||
case ImageFilterType.greyscale:
|
case ImageFilterType.greyscale:
|
||||||
return const ColorFilter.matrix([
|
baseMatrix = [
|
||||||
0.2126, 0.7152, 0.0722, 0, 0,
|
0.2126, 0.7152, 0.0722, 0, 0,
|
||||||
0.2126, 0.7152, 0.0722, 0, 0,
|
0.2126, 0.7152, 0.0722, 0, 0,
|
||||||
0.2126, 0.7152, 0.0722, 0, 0,
|
0.2126, 0.7152, 0.0722, 0, 0,
|
||||||
0, 0, 0, 1, 0,
|
0, 0, 0, 1, 0,
|
||||||
]);
|
];
|
||||||
|
break;
|
||||||
case ImageFilterType.invert:
|
case ImageFilterType.invert:
|
||||||
return const ColorFilter.matrix([
|
baseMatrix = [
|
||||||
-1, 0, 0, 0, 255,
|
-1.0, 0.0, 0.0, 0.0, 255.0,
|
||||||
0, -1, 0, 0, 255,
|
0.0, -1.0, 0.0, 0.0, 255.0,
|
||||||
0, 0, -1, 0, 255,
|
0.0, 0.0, -1.0, 0.0, 255.0,
|
||||||
0, 0, 0, 1, 0,
|
0.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
]);
|
];
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return const ColorFilter.mode(Colors.transparent, BlendMode.dst);
|
baseMatrix = [
|
||||||
|
1.0, 0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply brightness and contrast adjustments
|
||||||
|
final b = _brightness * 255.0; // Convert to 0-255 range
|
||||||
|
final c = _contrast;
|
||||||
|
|
||||||
|
// Combine filter with brightness/contrast
|
||||||
|
final adjustedMatrix = <double>[
|
||||||
|
c, 0.0, 0.0, 0.0, b,
|
||||||
|
0.0, c, 0.0, 0.0, b,
|
||||||
|
0.0, 0.0, c, 0.0, b,
|
||||||
|
0.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Multiply base matrix with adjustment matrix
|
||||||
|
return ColorFilter.matrix(_multiplyMatrices(baseMatrix, adjustedMatrix));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> _multiplyMatrices(List<double> a, List<double> b) {
|
||||||
|
// For simplicity, just apply brightness/contrast on top of base filter
|
||||||
|
final result = <double>[];
|
||||||
|
|
||||||
|
for (int i = 0; i < a.length; i++) {
|
||||||
|
result.add(a[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply contrast to RGB components
|
||||||
|
result[0] = result[0] * b[0]; // R
|
||||||
|
result[6] = result[6] * b[6]; // G
|
||||||
|
result[12] = result[12] * b[12]; // B
|
||||||
|
|
||||||
|
// Apply brightness
|
||||||
|
result[4] = result[4] + b[4]; // R offset
|
||||||
|
result[9] = result[9] + b[9]; // G offset
|
||||||
|
result[14] = result[14] + b[14]; // B offset
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -482,19 +718,8 @@ void dispose() {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Text(
|
icon: const Icon(Icons.help_outline, color: Colors.white),
|
||||||
'$_numberOfBullets',
|
onPressed: _showHelpDialog,
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: _showBulletDialog,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.filter_alt, color: Colors.white),
|
|
||||||
onPressed: _showFilterDialog,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
],
|
],
|
||||||
@@ -507,31 +732,33 @@ void dispose() {
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return Center(
|
return Center(
|
||||||
child: InteractiveViewer(
|
child: (_isError || _streamUrl == null)
|
||||||
clipBehavior: Clip.hardEdge,
|
? _buildErrorContent()
|
||||||
constrained: false,
|
: InteractiveViewer(
|
||||||
boundaryMargin: EdgeInsets.zero,
|
clipBehavior: Clip.hardEdge,
|
||||||
minScale: 1.0,
|
constrained: false,
|
||||||
maxScale: 5.0,
|
boundaryMargin: EdgeInsets.zero,
|
||||||
transformationController: _transformationController,
|
minScale: 1.0,
|
||||||
child: SizedBox(
|
maxScale: 5.0,
|
||||||
width: constraints.maxWidth,
|
transformationController: _transformationController,
|
||||||
height: constraints.maxHeight,
|
child: SizedBox(
|
||||||
child: (_isError || _streamUrl == null)
|
width: constraints.maxWidth,
|
||||||
? _buildErrorContent()
|
height: constraints.maxHeight,
|
||||||
: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: ColorFiltered(
|
child: ColorFiltered(
|
||||||
colorFilter: _buildColorFilter(),
|
colorFilter: _buildColorFilter(),
|
||||||
child: Mjpeg(
|
child: Mjpeg(
|
||||||
stream: _streamUrl!,
|
stream: _streamUrl!,
|
||||||
isLive: true,
|
isLive: true,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
error: (context, error, stackTrace) => _buildErrorContent(),
|
error: (context, error, stackTrace) {
|
||||||
|
return _buildErrorContent();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -540,7 +767,7 @@ void dispose() {
|
|||||||
if (_isCapturing)
|
if (_isCapturing)
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black.withOpacity(0.4),
|
color: Colors.black.withValues(alpha: 0.4),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@@ -570,11 +797,45 @@ void dispose() {
|
|||||||
notchMargin: 8.0,
|
notchMargin: 8.0,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 60,
|
height: 60,
|
||||||
child: Stack(
|
child: Row(
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
Align(
|
// Left - Bullet count (just number)
|
||||||
alignment: Alignment.center,
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 16.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _showBulletDialog,
|
||||||
|
child: Text(
|
||||||
|
'$_numberOfBullets',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Center space for FAB
|
||||||
|
const SizedBox(width: 80),
|
||||||
|
// Right section - Brightness and Filter
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.brightness_6, color: Colors.white, size: 24),
|
||||||
|
onPressed: _showBrightnessContrastDialog,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.filter_alt, color: Colors.white, size: 24),
|
||||||
|
onPressed: _showFilterDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class _TargetScreenState extends State<TargetScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -342,6 +342,94 @@ class AppTranslations {
|
|||||||
'Slovenian': 'Ni mogoče naložiti kamere',
|
'Slovenian': 'Ni mogoče naložiti kamere',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Mode Selection
|
||||||
|
'Select Detection Mode': {
|
||||||
|
'English': 'Select Detection Mode',
|
||||||
|
'Slovenian': 'Izberi način zaznavanja',
|
||||||
|
},
|
||||||
|
'Capture & Shoot': {
|
||||||
|
'English': 'Capture & Shoot',
|
||||||
|
'Slovenian': 'Zajemi in streljaj',
|
||||||
|
},
|
||||||
|
'Take single photo and detect bullets': {
|
||||||
|
'English': 'Take single photo and detect bullets',
|
||||||
|
'Slovenian': 'Zajemi fotografijo in zazna zadetke',
|
||||||
|
},
|
||||||
|
'Live Detection': {
|
||||||
|
'English': 'Live Detection',
|
||||||
|
'Slovenian': 'Živa zaznava',
|
||||||
|
},
|
||||||
|
'Continuous monitoring with real-time scoring': {
|
||||||
|
'English': 'Continuous monitoring with real-time scoring',
|
||||||
|
'Slovenian': 'Neprekinjeno spremljanje z ocenjevanjem v živo',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Live Detection Screen
|
||||||
|
'Capture Baseline': {
|
||||||
|
'English': 'Capture Baseline',
|
||||||
|
'Slovenian': 'Zajemi osnovno sliko',
|
||||||
|
},
|
||||||
|
'Position target and capture empty baseline': {
|
||||||
|
'English': 'Position target and capture empty baseline',
|
||||||
|
'Slovenian': 'Postavi tarčo in zajemi prazno osnovno sliko',
|
||||||
|
},
|
||||||
|
'Start Detection': {
|
||||||
|
'English': 'Start Detection',
|
||||||
|
'Slovenian': 'Začni zaznavanje',
|
||||||
|
},
|
||||||
|
'Stop Detection': {
|
||||||
|
'English': 'Stop Detection',
|
||||||
|
'Slovenian': 'Ustavi zaznavanje',
|
||||||
|
},
|
||||||
|
'Bullets Detected': {
|
||||||
|
'English': 'Bullets Detected',
|
||||||
|
'Slovenian': 'Zaznani zadetki',
|
||||||
|
},
|
||||||
|
'Current Score': {
|
||||||
|
'English': 'Current Score',
|
||||||
|
'Slovenian': 'Trenutna ocena',
|
||||||
|
},
|
||||||
|
'Monitoring...': {
|
||||||
|
'English': 'Monitoring...',
|
||||||
|
'Slovenian': 'Spremljanje...',
|
||||||
|
},
|
||||||
|
'Save & Exit': {
|
||||||
|
'English': 'Save & Exit',
|
||||||
|
'Slovenian': 'Shrani in zapri',
|
||||||
|
},
|
||||||
|
'Capturing baseline...': {
|
||||||
|
'English': 'Capturing baseline...',
|
||||||
|
'Slovenian': 'Zajemanje osnove...',
|
||||||
|
},
|
||||||
|
'Baseline captured!': {
|
||||||
|
'English': 'Baseline captured!',
|
||||||
|
'Slovenian': 'Osnova zajeta!',
|
||||||
|
},
|
||||||
|
'Analyzing frame...': {
|
||||||
|
'English': 'Analyzing frame...',
|
||||||
|
'Slovenian': 'Analiza slike...',
|
||||||
|
},
|
||||||
|
'New bullet detected!': {
|
||||||
|
'English': 'New bullet detected!',
|
||||||
|
'Slovenian': 'Nov zadetek zaznan!',
|
||||||
|
},
|
||||||
|
'Calculate Score': {
|
||||||
|
'English': 'Calculate Score',
|
||||||
|
'Slovenian': 'Izračunaj rezultat',
|
||||||
|
},
|
||||||
|
'Target reached': {
|
||||||
|
'English': 'Target reached',
|
||||||
|
'Slovenian': 'Cilj dosežen',
|
||||||
|
},
|
||||||
|
'bullets': {
|
||||||
|
'English': 'bullets',
|
||||||
|
'Slovenian': 'zadetkov',
|
||||||
|
},
|
||||||
|
'Click Calculate Score to continue': {
|
||||||
|
'English': 'Click Calculate Score to continue',
|
||||||
|
'Slovenian': 'Kliknite Izračunaj rezultat za nadaljevanje',
|
||||||
|
},
|
||||||
|
|
||||||
// Delete confirmations
|
// Delete confirmations
|
||||||
'Are you sure you want to delete this file?': {
|
'Are you sure you want to delete this file?': {
|
||||||
'English': 'Are you sure you want to delete this file?',
|
'English': 'Are you sure you want to delete this file?',
|
||||||
@@ -367,6 +455,62 @@ class AppTranslations {
|
|||||||
'English': 'and all of its contents?',
|
'English': 'and all of its contents?',
|
||||||
'Slovenian': 'in vso njeno vsebino?',
|
'Slovenian': 'in vso njeno vsebino?',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Help dialog translations
|
||||||
|
'Help': {
|
||||||
|
'English': 'Help',
|
||||||
|
'Slovenian': 'Pomoč',
|
||||||
|
},
|
||||||
|
'Tap camera button to capture and detect bullets': {
|
||||||
|
'English': 'Tap camera button to capture and detect bullets',
|
||||||
|
'Slovenian': 'Tapnite gumb kamere za zajem in zaznavanje zadetkov',
|
||||||
|
},
|
||||||
|
'Bottom left number: Set expected bullet count (1-5)': {
|
||||||
|
'English': 'Bottom left number: Set expected bullet count (1-5)',
|
||||||
|
'Slovenian': 'Spodaj levo številka: Nastavite število zadetkov (1-5)',
|
||||||
|
},
|
||||||
|
'Sun icon: Adjust brightness and contrast for better visibility': {
|
||||||
|
'English': 'Sun icon: Adjust brightness and contrast for better visibility',
|
||||||
|
'Slovenian': 'Ikona sonca: Prilagodite svetlost in kontrast za boljšo vidljivost',
|
||||||
|
},
|
||||||
|
'Filter icon: Apply color filters (None, Greyscale, Invert)': {
|
||||||
|
'English': 'Filter icon: Apply color filters (None, Greyscale, Invert)',
|
||||||
|
'Slovenian': 'Ikona filtra: Uporabite barvne filtre (Brez, Sivine, Inverzno)',
|
||||||
|
},
|
||||||
|
'Pinch to zoom on the camera stream': {
|
||||||
|
'English': 'Pinch to zoom on the camera stream',
|
||||||
|
'Slovenian': 'Povečajte s prstoma na video pretoku',
|
||||||
|
},
|
||||||
|
'Align target inside the square frame': {
|
||||||
|
'English': 'Align target inside the square frame',
|
||||||
|
'Slovenian': 'Poravnajte tarčo znotraj kvadratnega okvirja',
|
||||||
|
},
|
||||||
|
'Got it!': {
|
||||||
|
'English': 'Got it!',
|
||||||
|
'Slovenian': 'Razumem!',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Brightness & Contrast dialog translations
|
||||||
|
'Brightness & Contrast': {
|
||||||
|
'English': 'Brightness & Contrast',
|
||||||
|
'Slovenian': 'Svetlost in kontrast',
|
||||||
|
},
|
||||||
|
'Brightness': {
|
||||||
|
'English': 'Brightness',
|
||||||
|
'Slovenian': 'Svetlost',
|
||||||
|
},
|
||||||
|
'Contrast': {
|
||||||
|
'English': 'Contrast',
|
||||||
|
'Slovenian': 'Kontrast',
|
||||||
|
},
|
||||||
|
'Reset': {
|
||||||
|
'English': 'Reset',
|
||||||
|
'Slovenian': 'Ponastavi',
|
||||||
|
},
|
||||||
|
'Done': {
|
||||||
|
'English': 'Done',
|
||||||
|
'Slovenian': 'Končano',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static String translate(String key, String language) {
|
static String translate(String key, String language) {
|
||||||
|
|||||||
Reference in New Issue
Block a user