proxybuy-flutter/lib/screens/mains/hunt/widgets/hint_camera_widget.dart

421 lines
10 KiB
Dart

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:lba/res/colors.dart';
import '../services/location_service.dart';
class HintCameraWidget extends StatefulWidget {
final double targetLatitude;
final double targetLongitude;
final String hintDescription;
final VoidCallback onHintFound;
final VoidCallback onClose;
const HintCameraWidget({
super.key,
required this.targetLatitude,
required this.targetLongitude,
required this.hintDescription,
required this.onHintFound,
required this.onClose,
});
@override
State<HintCameraWidget> createState() => _HintCameraWidgetState();
}
class _HintCameraWidgetState extends State<HintCameraWidget>
with TickerProviderStateMixin {
MobileScannerController? _controller;
bool _isScanning = true;
bool _hintFound = false;
late AnimationController _pulseController;
late AnimationController _scanController;
late Animation<double> _pulseAnimation;
late Animation<double> _scanAnimation;
@override
void initState() {
super.initState();
_initializeCamera();
_setupAnimations();
_checkLocationPeriodically();
}
void _setupAnimations() {
_pulseController = AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
);
_scanController = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
_pulseAnimation = Tween<double>(
begin: 0.8,
end: 1.2,
).animate(CurvedAnimation(
parent: _pulseController,
curve: Curves.easeInOut,
));
_scanAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(
parent: _scanController,
curve: Curves.linear,
));
_pulseController.repeat(reverse: true);
_scanController.repeat();
}
void _initializeCamera() async {
try {
_controller = MobileScannerController(
detectionSpeed: DetectionSpeed.noDuplicates,
facing: CameraFacing.back,
);
} catch (e) {
// Handle camera initialization error
}
}
void _checkLocationPeriodically() async {
while (_isScanning && !_hintFound) {
await Future.delayed(const Duration(seconds: 2));
if (!mounted) break;
final position = await LocationService.getCurrentPosition();
if (position != null) {
final isNearTarget = LocationService.isWithinRange(
position.latitude,
position.longitude,
widget.targetLatitude,
widget.targetLongitude,
rangeInMeters: 100.0, // 100 meters range for hints
);
if (isNearTarget && !_hintFound) {
setState(() {
_hintFound = true;
});
widget.onHintFound();
break;
}
}
}
}
@override
void dispose() {
_controller?.dispose();
_pulseController.dispose();
_scanController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
onPressed: widget.onClose,
icon: const Icon(
Icons.close_rounded,
color: Colors.white,
size: 28,
),
),
title: Text(
'AR Hint Scanner',
style: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
centerTitle: true,
),
body: Stack(
children: [
// Camera view
if (_controller != null)
MobileScanner(
controller: _controller!,
onDetect: (capture) {
// Handle any QR code detection if needed
},
)
else
Container(
color: Colors.black,
child: const Center(
child: CircularProgressIndicator(color: Colors.white),
),
),
// AR Overlay
_buildAROverlay(),
// Hint status
_buildHintStatus(),
// Instructions
_buildInstructions(),
],
),
);
}
Widget _buildAROverlay() {
return CustomPaint(
painter: AROverlayPainter(
scanAnimation: _scanAnimation,
pulseAnimation: _pulseAnimation,
hintFound: _hintFound,
),
size: Size.infinite,
);
}
Widget _buildHintStatus() {
return Positioned(
top: 100,
left: 20,
right: 20,
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Icon(
_hintFound ? Icons.check_circle : Icons.search,
color: _hintFound ? Colors.green : Colors.orange,
size: 32,
),
const SizedBox(height: 8),
Text(
_hintFound ? 'Hint Found!' : 'Searching for AR Marker...',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
if (_hintFound) ...[
const SizedBox(height: 8),
Text(
widget.hintDescription,
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
),
textAlign: TextAlign.center,
),
],
],
),
),
);
}
Widget _buildInstructions() {
return Positioned(
bottom: 50,
left: 20,
right: 20,
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.8),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.center_focus_strong,
color: AppColors.primary,
size: 40,
),
const SizedBox(height: 12),
Text(
'Point your camera at the environment',
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w600,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Move closer to the target location\nto discover the AR hint marker',
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
height: 1.4,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
class AROverlayPainter extends CustomPainter {
final Animation<double> scanAnimation;
final Animation<double> pulseAnimation;
final bool hintFound;
AROverlayPainter({
required this.scanAnimation,
required this.pulseAnimation,
required this.hintFound,
});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = hintFound ? Colors.green : Colors.blue
..style = PaintingStyle.stroke
..strokeWidth = 3.0;
final center = Offset(size.width / 2, size.height / 2);
final radius = 80.0;
// Draw scanning circle
if (!hintFound) {
canvas.drawCircle(
center,
radius * pulseAnimation.value,
paint..color = Colors.blue.withOpacity(0.6),
);
// Draw scanning line
const sweepAngle = math.pi / 3;
final startAngle = scanAnimation.value * 2 * math.pi;
paint
..color = Colors.cyan
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
startAngle,
sweepAngle,
false,
paint,
);
} else {
// Draw found indicator
paint
..color = Colors.green
..style = PaintingStyle.fill;
canvas.drawCircle(
center,
20 * pulseAnimation.value,
paint..color = Colors.green.withOpacity(0.3),
);
canvas.drawCircle(
center,
10,
paint..color = Colors.green,
);
// Draw checkmark
paint
..color = Colors.white
..strokeWidth = 3.0
..style = PaintingStyle.stroke
..strokeCap = StrokeCap.round;
final path = Path();
path.moveTo(center.dx - 5, center.dy);
path.lineTo(center.dx - 2, center.dy + 3);
path.lineTo(center.dx + 5, center.dy - 3);
canvas.drawPath(path, paint);
}
// Draw corner brackets
_drawCornerBrackets(canvas, size);
}
void _drawCornerBrackets(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.8)
..strokeWidth = 2.0
..style = PaintingStyle.stroke;
const bracketSize = 30.0;
const margin = 50.0;
// Top-left
canvas.drawLine(
Offset(margin, margin),
Offset(margin + bracketSize, margin),
paint,
);
canvas.drawLine(
Offset(margin, margin),
Offset(margin, margin + bracketSize),
paint,
);
// Top-right
canvas.drawLine(
Offset(size.width - margin, margin),
Offset(size.width - margin - bracketSize, margin),
paint,
);
canvas.drawLine(
Offset(size.width - margin, margin),
Offset(size.width - margin, margin + bracketSize),
paint,
);
// Bottom-left
canvas.drawLine(
Offset(margin, size.height - margin),
Offset(margin + bracketSize, size.height - margin),
paint,
);
canvas.drawLine(
Offset(margin, size.height - margin),
Offset(margin, size.height - margin - bracketSize),
paint,
);
// Bottom-right
canvas.drawLine(
Offset(size.width - margin, size.height - margin),
Offset(size.width - margin - bracketSize, size.height - margin),
paint,
);
canvas.drawLine(
Offset(size.width - margin, size.height - margin),
Offset(size.width - margin, size.height - margin - bracketSize),
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}