You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
diplomatic-quarter/lib/vital_signs/vital_sign.dart

440 lines
13 KiB
Dart

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: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<VitalSigns> createState() => _VitalSignsState();
}
class _VitalSignsState extends State<VitalSigns> with RouteAware {
late final VitalSignCameraController _vitalSignCameraController;
late Future<CameraDevice?> cameraDevice;
@override
void initState() {
super.initState();
cameraDevice = getFrontCamera();
// Wakelock.enable(); // keep the screen awake
}
Future<CameraDevice?> 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,
)),
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<CircularOverlay> createState() => _CircularOverlayState();
}
class _CircularOverlayState extends State<CircularOverlay>
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;
}
}