import 'dart:math'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lottie/lottie.dart'; import 'package:vital_sign_camera/vital_sign_camera.dart'; // import 'package:wakelock/wakelock.dart'; import '../uitl/translations_delegate_base.dart'; import 'components/scan_condition_checklist.dart'; import 'components/scan_status.dart'; import 'components/start_button.dart'; import 'result_screen.dart'; // import 'package:lottie/lottie.dart'; // final UserInfo userInfo = UserInfo( // age: 30, // gender: Gender.male, // weight: 75, // // kg, Optional // height: 164, // // cm, Optional // waistCircumference: 83, // // cm Optional // userId: 'dbd13e86-47f4-4a43-85f6-cf62fa750117'); final VitalSignCameraConfig config = VitalSignCameraConfig(apiKey: 'nIsZO45woSXSfIxsL1t79MWeIpsnGQr6B941MSF2', serverId: ServerId.awsEnterpriseProd); class VitalSigns extends StatefulWidget { const VitalSigns({super.key, required this.userInfo}); final UserInfo userInfo; @override State createState() => _VitalSignsState(); } class _VitalSignsState extends State with RouteAware { late final VitalSignCameraController _vitalSignCameraController; late Future cameraDevice; @override void initState() { super.initState(); cameraDevice = getFrontCamera(); // Wakelock.enable(); // keep the screen awake } Future getFrontCamera() async { if (CameraPermissionStatus.authorized != await requestCameraPermission()) { return null; } return queryCameraDevice(CameraPosition.front); } bool startedScanning = false; // set true when start button is pressed, set false when health result is tapped or error occurs bool isAllConditionsMet = false; // check for the 6 scan conditions before enabling the start button ScanConditions? _conditions; GetHealthStage? _scanningStage; double? _remainingTime; Health? _healthResult; NormalizedFaceBox? _normalizedFaceBox; VideoFrameInfo? _videoFrameInfo; dynamic _error; int? _errorCode; bool isCameraActive = true; bool _showedHealthResult = false; @override void didChangeDependencies() { super.didChangeDependencies(); // routeObserver.subscribe(this, ModalRoute.of(context)!); } @override void dispose() { // routeObserver.unsubscribe(this); super.dispose(); } @override void didPushNext() { setState(() { isCameraActive = false; }); } @override void didPopNext() { setState(() { isCameraActive = true; startedScanning = false; _healthResult = null; }); } @override Widget build(BuildContext context) { final deviceSize = MediaQuery.of(context).size; return Stack( children: [ VitalSignCamera(onCreated: _onVitalSignCameraCreated, isActive: isCameraActive, userInfo: widget.userInfo, config: config, device: cameraDevice, onVideoFrameProcessed: _onVideoFrameProcessed), if (_scanningStage != GetHealthStage.idle) // show remaining time count down during scan Align( alignment: Alignment.bottomCenter, child: Padding( padding: const EdgeInsets.only(bottom: 16.0), child: ScanStatus(stage: _scanningStage, remainingTime: _remainingTime), )), if (mounted) Column(children: [ Expanded( child: CircularOverlay( state: (_scanningStage == null || _scanningStage == GetHealthStage.idle) ? AnimationState.stop : AnimationState.playing, )), // Lottie.asset('assets/breathing.json', width: 200, height: 200, fit: BoxFit.fill), if (_conditions != null) ScanConditionChecklist(deviceSize: deviceSize, conditions: _conditions!), SizedBox( height: 16, ), if (_scanningStage == GetHealthStage.idle && isCameraActive) // show start button only when it is not scanning ...{ StartButton( onPressed: () { setState(() { _vitalSignCameraController.startScanning(); startedScanning = true; _healthResult = null; _showedHealthResult = false; }); }, disabled: !isAllConditionsMet, ), } else SizedBox( height: 55, ) ]), Padding( padding: const EdgeInsetsDirectional.only(top: 32.0, start: 14), child: GestureDetector( onTap: () { Navigator.pop(context); }, child: SvgPicture.asset( 'assets/images/svg/cross.svg', width: 32, height: 32, )), ) ], ); // return Scaffold( // body: Stack( // children: [ // VitalSignCamera( // onCreated: _onVitalSignCameraCreated, // isActive: isCameraActive, // userInfo: userInfo, // config: config, // device: cameraDevice, // onVideoFrameProcessed: _onVideoFrameProcessed), // // A back button to navigate back to previous screen // if (_scanningStage == GetHealthStage.idle && !startedScanning) // BackHomeButton( // onPressed: () { // setState(() { // isCameraActive = false; // }); // Navigator.pop(context); // }, // deviceSize: deviceSize), // // A button to toggle isCameraActive // if (_scanningStage == GetHealthStage.idle && !startedScanning) // Button( // onPressed: () { // setState(() { // isCameraActive = !isCameraActive; // }); // }, // deviceSize: deviceSize, // title: "Toggle isCameraActive", // alignment: Alignment.topRight, // margin: EdgeInsets.fromLTRB( // 0, deviceSize.height * 0.05, deviceSize.width * 0.05, 0)), // if (!isCameraActive) // const Center( // child: Text( // "Camera is not active.", // textAlign: TextAlign.center, // )) // else // Stack(children: [ // if (_scanningStage == GetHealthStage.idle && // isCameraActive) // show start button only when it is not scanning // StartButton( // onPressed: () { // setState(() { // _vitalSignCameraController.startScanning(); // startedScanning = true; // _healthResult = null; // _showedHealthResult = false; // }); // }, // disabled: !isAllConditionsMet, // ), // if (_conditions != null && // _scanningStage == GetHealthStage.idle && // !startedScanning) // show scan conditions checking only when it is not scanning or showing health result // ScanConditionChecklist( // deviceSize: deviceSize, conditions: _conditions!), // if (_scanningStage != // GetHealthStage // .idle) // show remaining time count down during scan // ScanStatus( // stage: _scanningStage, remainingTime: _remainingTime), // if (!startedScanning || // _healthResult == // null) // show bounding box before and during scan, but not when health result is shown // BoundingBoxWidget( // deviceSize: deviceSize, // facebox: _normalizedFaceBox, // videoFrameInfo: _videoFrameInfo), // if (_error != null) Error(error: _error, errorCode: _errorCode), // ]) // ], // ), // ); } void _onVideoFrameProcessed(VideoFrameProcessedEvent event) { if (!_showedHealthResult && _healthResult == null && event.healthResult?.health != null) { _showedHealthResult = true; Navigator.push( context, MaterialPageRoute( builder: (context) => ResultScreen( healthResult: _healthResult, ), ), ); } setState(() { _conditions = event.scanConditions; isAllConditionsMet = checkIfAllScanConditionsMet(); _scanningStage = event.healthResult?.stage; _remainingTime = event.healthResult?.remainingTime; _healthResult = event.healthResult?.health; _normalizedFaceBox = event.faceBox; _videoFrameInfo = event.videoFrameInfo; _error = event.healthResult?.error; _errorCode = event.healthResult?.errorCode; if (_error != null) { // when error occurs, reshow condition checklist & disabled start button startedScanning = false; isAllConditionsMet = false; } }); } bool checkIfAllScanConditionsMet() { if (_conditions?.centered == true && _conditions?.distance == true && _conditions?.frameRate == true && _conditions?.lighting == true && _conditions?.movement == true && _conditions?.serverReady == true) { return true; } else { return false; } } void _onVitalSignCameraCreated(VitalSignCameraController controller) { _vitalSignCameraController = controller; } } enum AnimationState { playing, stop } class CircularOverlay extends StatefulWidget { final AnimationState state; const CircularOverlay({Key? key, this.state = AnimationState.stop}) : super(key: key); @override State createState() => _CircularOverlayState(); } class _CircularOverlayState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; @override void didChangeDependencies() { if (widget.state == AnimationState.playing) { _controller.forward(from: 0); } else { _controller.stop(); } } @override void didUpdateWidget(CircularOverlay oldWidget) { if (widget.state == AnimationState.playing) { _controller.repeat(); } else { _controller.stop(); } } @override void initState() { super.initState(); _controller = AnimationController( vsync: this, duration: const Duration(seconds: 8), ); _controller.stop(); if (widget.state == AnimationState.playing) { _controller.forward(from: 0); } else { _controller.stop(); } } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return CustomPaint( painter: CircularBarsPainter(animationValue: _controller.value), size: Size(MediaQuery.sizeOf(context).width * .9, MediaQuery.sizeOf(context).height * .8), ); }, ); } } class CircularBarsPainter extends CustomPainter { final double animationValue; CircularBarsPainter({required this.animationValue}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 1.8); final radius = min(size.width / 2, size.height / 2); final barPaint = Paint() ..color = Colors.white ..strokeWidth = 4 ..style = PaintingStyle.stroke; const numberOfBars = 64; final angleIncrement = 2 * pi / numberOfBars; for (int i = 0; i < numberOfBars; i++) { final angle = i * angleIncrement + (animationValue * 2 * pi); final start = Offset( center.dx + radius * cos(angle), center.dy + radius * sin(angle), ); final end = Offset( center.dx + (radius - 20) * cos(angle), center.dy + (radius - 20) * sin(angle), ); canvas.drawLine(start, end, barPaint); } } @override bool shouldRepaint(CircularBarsPainter oldDelegate) { return oldDelegate.animationValue != animationValue; } } class OvalOverlayPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final Paint overlayPaint = Paint() ..color = Colors.white.withOpacity(0.1) ..style = PaintingStyle.fill; canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), overlayPaint, ); final Rect ovalRect = Rect.fromCenter( center: Offset(size.width / 2, size.height / 2), width: size.width * 0.9, height: size.height, ); final Path ovalPath = Path()..addOval(ovalRect); canvas.clipPath(ovalPath, doAntiAlias: true); canvas.drawRect( Rect.fromLTWH(0, 0, size.width, size.height), Paint()..blendMode = BlendMode.clear, ); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } }