added some camera changes and included IOS support
This commit is contained in:
+310
-49
@@ -34,6 +34,10 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> 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<Matrix4>? _zoomAnimation;
|
||||
Matrix4? _originalMatrix;
|
||||
@@ -53,6 +57,7 @@ class _CameraStreamScreenState extends State<CameraStreamScreen> 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<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 {
|
||||
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<double> 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 = <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
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user