diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..4295bea --- /dev/null +++ b/ios/Podfile @@ -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 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 06c9da8..4dd2459 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,27 @@ UIApplicationSupportsIndirectInputEvents + NSCameraUsageDescription + This app needs camera access to capture and analyze shooting targets. + NSPhotoLibraryUsageDescription + This app needs photo library access to save and load target images. + NSPhotoLibraryAddUsageDescription + This app needs permission to save captured target images to your photo library. + NSMicrophoneUsageDescription + This app needs microphone access for video recording. + NSLocalNetworkUsageDescription + This app needs local network access to connect to camera streams. + NSBonjourServices + + _http._tcp + _https._tcp + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + diff --git a/lib/screens/calendar_screen.dart b/lib/screens/calendar_screen.dart index 1e063ee..2ddfd04 100644 --- a/lib/screens/calendar_screen.dart +++ b/lib/screens/calendar_screen.dart @@ -318,24 +318,6 @@ class _CalendarScreenState extends State { 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, - ), - ), - ), - ], ], ), ), diff --git a/lib/screens/camera_screen.dart b/lib/screens/camera_screen.dart index a10cea3..04e9162 100644 --- a/lib/screens/camera_screen.dart +++ b/lib/screens/camera_screen.dart @@ -24,6 +24,10 @@ class _CameraScreenState extends State { int _numberOfBullets = 5; 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 void initState() { super.initState(); @@ -31,9 +35,24 @@ class _CameraScreenState extends State { _initCamera(); _loadBulletColor(); // 👈 _loadLanguage(); + _loadBrightnessContrast(); YOLODetector.create().then((detector) => _yoloDetector = detector); } + Future _loadBrightnessContrast() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + _brightness = prefs.getDouble('brightness') ?? 0.0; + _contrast = prefs.getDouble('contrast') ?? 1.0; + }); + } + + Future _saveBrightnessContrast() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setDouble('brightness', _brightness); + await prefs.setDouble('contrast', _contrast); + } + String _t(String key) { 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 void _selectBulletCount(int number) { setState(() { @@ -274,26 +462,58 @@ Color _getColorFromName(String colorName) { ), centerTitle: true, actions: [ - // Display the selected bullet count IconButton( - icon: Text( - '$_numberOfBullets', - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - onPressed: _showBulletDialog, // Show dialog on tap + icon: const Icon(Icons.help_outline, color: Colors.white), + onPressed: _showHelpDialog, ), - const SizedBox(width: 16), // Space after the number + const SizedBox(width: 16), ], ), bottomNavigationBar: BottomAppBar( color: Colors.grey[900], shape: const CircularNotchedRectangle(), 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, floatingActionButton: FloatingActionButton( @@ -327,7 +547,7 @@ Color _getColorFromName(String colorName) { height: screenSize.width * 0.9, decoration: BoxDecoration( border: Border.all( - color: _bulletColor.withOpacity(0.9), + color: _bulletColor.withValues(alpha: 0.9), width: 3, ), ), diff --git a/lib/screens/stream_screen.dart b/lib/screens/stream_screen.dart index 964d40d..36a7316 100644 --- a/lib/screens/stream_screen.dart +++ b/lib/screens/stream_screen.dart @@ -34,6 +34,10 @@ class _CameraStreamScreenState extends State with SingleTick late YOLODetector _yoloDetector; 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; Animation? _zoomAnimation; Matrix4? _originalMatrix; @@ -53,6 +57,7 @@ class _CameraStreamScreenState extends State with SingleTick _loadBulletCount(); _loadSelectedFilter(); _loadBulletColor(); // 👈 add this to actually load the saved bullet color + _loadBrightnessContrast(); YOLODetector.create().then((detector) => _yoloDetector = detector); @@ -68,6 +73,20 @@ class _CameraStreamScreenState extends State with SingleTick ); } + Future _loadBrightnessContrast() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + _brightness = prefs.getDouble('brightness') ?? 0.0; + _contrast = prefs.getDouble('contrast') ?? 1.0; + }); + } + + Future _saveBrightnessContrast() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setDouble('brightness', _brightness); + await prefs.setDouble('contrast', _contrast); + } + Future _loadLanguage() async { final prefs = await SharedPreferences.getInstance(); setState(() { @@ -428,6 +447,177 @@ Color _getColorFromName(String colorName) { 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 void dispose() { _zoomAnimController.dispose(); @@ -449,24 +639,70 @@ void dispose() { } ColorFilter _buildColorFilter() { + // Base filter matrix + List baseMatrix; + switch (_selectedFilter) { 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, 0, 0, 1, 0, - ]); + ]; + break; case ImageFilterType.invert: - return const ColorFilter.matrix([ - -1, 0, 0, 0, 255, - 0, -1, 0, 0, 255, - 0, 0, -1, 0, 255, - 0, 0, 0, 1, 0, - ]); + baseMatrix = [ + -1.0, 0.0, 0.0, 0.0, 255.0, + 0.0, -1.0, 0.0, 0.0, 255.0, + 0.0, 0.0, -1.0, 0.0, 255.0, + 0.0, 0.0, 0.0, 1.0, 0.0, + ]; + break; 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 = [ + 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 _multiplyMatrices(List a, List b) { + // For simplicity, just apply brightness/contrast on top of base filter + final result = []; + + 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 @@ -482,19 +718,8 @@ void dispose() { ), actions: [ IconButton( - icon: Text( - '$_numberOfBullets', - 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, + icon: const Icon(Icons.help_outline, color: Colors.white), + onPressed: _showHelpDialog, ), const SizedBox(width: 16), ], @@ -507,31 +732,33 @@ void dispose() { child: LayoutBuilder( builder: (context, constraints) { return Center( - child: InteractiveViewer( - clipBehavior: Clip.hardEdge, - constrained: false, - boundaryMargin: EdgeInsets.zero, - minScale: 1.0, - maxScale: 5.0, - transformationController: _transformationController, - child: SizedBox( - width: constraints.maxWidth, - height: constraints.maxHeight, - child: (_isError || _streamUrl == null) - ? _buildErrorContent() - : RepaintBoundary( - child: ColorFiltered( - colorFilter: _buildColorFilter(), - child: Mjpeg( - stream: _streamUrl!, - isLive: true, - fit: BoxFit.contain, - error: (context, error, stackTrace) => _buildErrorContent(), + child: (_isError || _streamUrl == null) + ? _buildErrorContent() + : InteractiveViewer( + clipBehavior: Clip.hardEdge, + constrained: false, + boundaryMargin: EdgeInsets.zero, + minScale: 1.0, + maxScale: 5.0, + transformationController: _transformationController, + child: SizedBox( + width: constraints.maxWidth, + height: constraints.maxHeight, + child: RepaintBoundary( + child: ColorFiltered( + colorFilter: _buildColorFilter(), + child: Mjpeg( + stream: _streamUrl!, + isLive: true, + fit: BoxFit.contain, + error: (context, error, stackTrace) { + return _buildErrorContent(); + }, + ), + ), ), ), ), - ), - ), ); }, ), @@ -540,7 +767,7 @@ void dispose() { if (_isCapturing) Positioned.fill( child: Container( - color: Colors.black.withOpacity(0.4), + color: Colors.black.withValues(alpha: 0.4), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -570,11 +797,45 @@ void dispose() { notchMargin: 8.0, child: SizedBox( height: 60, - child: Stack( - alignment: Alignment.center, + child: Row( children: [ - Align( - alignment: Alignment.center, + // 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 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, + ), + ], + ), ), ], ), diff --git a/lib/screens/target_screen.dart b/lib/screens/target_screen.dart index 15e0b11..1e2452d 100644 --- a/lib/screens/target_screen.dart +++ b/lib/screens/target_screen.dart @@ -63,6 +63,7 @@ class _TargetScreenState extends State { }); } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/utils/translations.dart b/lib/utils/translations.dart index 9124f0f..f0aeb0e 100644 --- a/lib/utils/translations.dart +++ b/lib/utils/translations.dart @@ -342,6 +342,94 @@ class AppTranslations { '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 '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?', '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) {