diff --git a/android/settings.gradle b/android/settings.gradle index d7a696e3..4c1c588e 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,6 +1,6 @@ include ':app' include ':vital-sign-engine' -project(':vital-sign-engine').projectDir = new File('C:\\Users\\taha.alam\\AndroidStudioProjects\\diplomatic-quarter\\packages\\vital_sign_camera\\android\\libs') +project(':vital-sign-engine').projectDir = new File('/Users/mohamedmekawy/Documents/Work/DiplomaticQuarter_3.16/packages/vital_sign_camera/android/libs') def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() diff --git a/assets/images/checkmark.png b/assets/images/checkmark.png new file mode 100644 index 00000000..cec37366 Binary files /dev/null and b/assets/images/checkmark.png differ diff --git a/assets/images/crossmark.png b/assets/images/crossmark.png new file mode 100644 index 00000000..831d9200 Binary files /dev/null and b/assets/images/crossmark.png differ diff --git a/ios/Podfile b/ios/Podfile index 7721bb87..20d442dc 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -28,7 +28,8 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do - use_frameworks! +# use_frameworks! + use_frameworks! :linkage => :static use_modular_headers! pod 'OpenTok', '~> 2.22.0' diff --git a/lib/config/config.dart b/lib/config/config.dart index 1568c906..1c7cd0eb 100644 --- a/lib/config/config.dart +++ b/lib/config/config.dart @@ -21,8 +21,8 @@ var PACKAGES_ORDERS = '/api/orders'; var PACKAGES_ORDER_HISTORY = '/api/orders/items'; var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; // var BASE_URL = 'http://10.50.100.198:2018/'; - // var BASE_URL = 'https://uat.hmgwebservices.com/'; -var BASE_URL = 'https://hmgwebservices.com/'; + var BASE_URL = 'https://uat.hmgwebservices.com/'; +// var BASE_URL = 'https://hmgwebservices.com/'; // var BASE_URL = 'https://orash.cloudsolutions.com.sa/'; // var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/'; // var BASE_URL = 'https://vidamergeuat.cloudsolutions.com.sa/'; diff --git a/lib/pages/landing/widgets/services_view.dart b/lib/pages/landing/widgets/services_view.dart index b76cf82c..1ca31655 100644 --- a/lib/pages/landing/widgets/services_view.dart +++ b/lib/pages/landing/widgets/services_view.dart @@ -230,10 +230,12 @@ class ServicesView extends StatelessWidget { } else if (hmgServices.action == 1) { openLiveCare(context); } else if (hmgServices.action == 2) { + //todo for temporary basis Navigator.push(context, FadePage(page: VitalSigns())); // initPenguinSDK(); + // if (projectViewModel.isIndoorNavigationEnabled) { // if (!isLocked) openNavigationProjectSelection(context); // } else { diff --git a/lib/vital_signs/components/back_button.dart b/lib/vital_signs/components/back_button.dart new file mode 100644 index 00000000..08253e87 --- /dev/null +++ b/lib/vital_signs/components/back_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class BackHomeButton extends StatelessWidget { + final void Function() onPressed; + final Size deviceSize; + + const BackHomeButton({ + Key? key, + required this.onPressed, + required this.deviceSize, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topLeft, + child: Container( + margin: EdgeInsets.fromLTRB(15, deviceSize.height * 0.05, 0, 0), + child: IconButton( + icon: const Icon(Icons.arrow_back), + color: Colors.white, + iconSize: 25, + onPressed: onPressed, + ), + ), + ); + } +} diff --git a/lib/vital_signs/components/bounding_box_widget.dart b/lib/vital_signs/components/bounding_box_widget.dart new file mode 100644 index 00000000..d98c64c3 --- /dev/null +++ b/lib/vital_signs/components/bounding_box_widget.dart @@ -0,0 +1,87 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:vital_sign_camera/vital_sign_camera.dart'; + +class BoundingBoxWidget extends StatelessWidget { + final Size deviceSize; + final NormalizedFaceBox? facebox; + final VideoFrameInfo? videoFrameInfo; + + const BoundingBoxWidget({ + Key? key, + required this.deviceSize, + required this.facebox, + required this.videoFrameInfo, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (facebox == null || videoFrameInfo == null) { + return Container(); + } + + // Get a map with left, top, width, and height of the bounding box for the face + Map boundingBox = computeBoundingBox(); + + return Positioned( + left: boundingBox['left'], + top: boundingBox['top'], + child: Container( + width: boundingBox['width'], + height: boundingBox['height'], + decoration: BoxDecoration( + border: Border.all( + color: Colors.red, + width: 2, + )), + ), + ); + } + + // Helper function to compute the left, top, width, and height of the bounding box + Map computeBoundingBox() { + double width = videoFrameInfo!.width; + double height = videoFrameInfo!.height; + double windowWidth = deviceSize.width; + double windowHeight = deviceSize.height; + + double screenAspectRatio = deviceSize.aspectRatio; + double frameAspectRatio = width / height; + + int xTransform = 1; + if (Platform.isAndroid) { + xTransform = -1; + } + + double scale = 1; + if (screenAspectRatio < frameAspectRatio) { + scale = windowHeight / height; + } else { + scale = windowWidth / width; + } + + Map scaledFrameBounds = { + 'x': (windowWidth - scale * width) / 2, + 'y': (windowHeight - scale * height) / 2, + 'width': scale * width, + 'height': scale * height, + }; + + double x = facebox!.xCenter - facebox!.width / 2; + double y = facebox!.yCenter - facebox!.height / 2; + + if (xTransform < 0) { + x = 1 - x - facebox!.width; + } + + Map result = { + 'left': scaledFrameBounds['x'] + x * scaledFrameBounds['width'], + 'top': scaledFrameBounds['y'] + y * scaledFrameBounds['height'], + 'width': facebox!.width * scaledFrameBounds['width'], + 'height': facebox!.height * scaledFrameBounds['height'], + }; + + return result; + } +} diff --git a/lib/vital_signs/components/button.dart b/lib/vital_signs/components/button.dart new file mode 100644 index 00000000..f4a20105 --- /dev/null +++ b/lib/vital_signs/components/button.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class Button extends StatelessWidget { + final void Function() onPressed; + final Size deviceSize; + final String title; + final Alignment alignment; + final EdgeInsets margin; + + const Button({ + Key? key, + required this.onPressed, + required this.deviceSize, + required this.title, + required this.alignment, + required this.margin, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: alignment, + child: Container( + margin: margin, + child: ElevatedButton( + onPressed: onPressed, + child: Text( + title, + style: const TextStyle(fontSize: 18), + ), + ), + )); + } +} diff --git a/lib/vital_signs/components/error.dart b/lib/vital_signs/components/error.dart new file mode 100644 index 00000000..1434b370 --- /dev/null +++ b/lib/vital_signs/components/error.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class Error extends StatelessWidget { + final dynamic error; + final int? errorCode; + + const Error({ + Key? key, + this.error, + this.errorCode, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + "$error $errorCode", + style: const TextStyle(color: Colors.white), + textAlign: TextAlign.center, + ), + ); + } +} diff --git a/lib/vital_signs/components/health_result_widget.dart b/lib/vital_signs/components/health_result_widget.dart new file mode 100644 index 00000000..ce198e50 --- /dev/null +++ b/lib/vital_signs/components/health_result_widget.dart @@ -0,0 +1,222 @@ +import 'package:flutter/material.dart'; +import 'package:vital_sign_camera/vital_sign_camera.dart'; +import 'vital_sign_widget.dart'; + +class HealthResultWidget extends StatelessWidget { + final Size deviceSize; + final void Function() onTap; + final Health? healthResult; + + late final VitalSign? _vitalSign; + late final HolisticAnalysis? _holisticAnalysis; + late final CardiovascularRisks? _cardiovascularRisks; + late final CovidRisk? _covidRisk; + late final ScanParameters? _scanParameters; + + HealthResultWidget({ + Key? key, + required this.deviceSize, + required this.onTap, + required this.healthResult, + }) : super(key: key) { + _vitalSign = healthResult?.vitalSigns; + _holisticAnalysis = healthResult?.holisticHealth; + _cardiovascularRisks = healthResult?.risks?.cardiovascularRisks; + _covidRisk = healthResult?.risks?.covidRisk; + _scanParameters = healthResult?.scanParameters; + } + + @override + Widget build(BuildContext context) { + return Positioned( + top: deviceSize.height * 0.07, + left: deviceSize.width * 0.05, + child: GestureDetector( + onTap: onTap, + child: SizedBox( + width: deviceSize.width * 0.9, + child: Container( + color: Colors.black38, + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.fromLTRB(0, 0, 0, 10), + child: const Text( + "Vital Signs", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + VitalSignWidget( + vitalSignName: "Heart Rate", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.heartRate)), + VitalSignWidget( + vitalSignName: "SPO2", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.spo2)), + VitalSignWidget( + vitalSignName: "IBI", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.ibi)), + if (_vitalSign?.stress != null) + VitalSignWidget( + vitalSignName: "Stress", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.stress)), + if (_vitalSign?.respiratoryRate != null) + VitalSignWidget( + vitalSignName: "Respiratory Rate", + vitalSignValue: formatValueToTwoDp( + healthResult + ?.vitalSigns.respiratoryRate)), + if (_vitalSign?.hrvRmssd != null) + VitalSignWidget( + vitalSignName: "HRV RMSSD", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.hrvRmssd)), + if (_vitalSign?.hrvSdnn != null) + VitalSignWidget( + vitalSignName: "HRV SDNN", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.hrvSdnn)), + if (_vitalSign?.temperature != null) + VitalSignWidget( + vitalSignName: "Temperature", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.temperature)), + if (_vitalSign?.bloodPressure != null) + VitalSignWidget( + vitalSignName: "Blood Pressure", + vitalSignValue: + healthResult?.vitalSigns.bloodPressure ?? + ""), + if (_vitalSign?.bloodPressureSystolic != null) + VitalSignWidget( + vitalSignName: "Blood Pressure Systolic", + vitalSignValue: formatValueToTwoDp( + healthResult + ?.vitalSigns.bloodPressureSystolic)), + if (_vitalSign?.bloodPressureDiastolic != null) + VitalSignWidget( + vitalSignName: "Blood Pressure Diastolic", + vitalSignValue: formatValueToTwoDp( + healthResult + ?.vitalSigns.bloodPressureDiastolic)), + if (_holisticAnalysis != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Holistic Analysis", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_holisticAnalysis != null && + _holisticAnalysis?.bmi != null) + VitalSignWidget( + vitalSignName: "BMI", + vitalSignValue: formatValueToTwoDp( + healthResult?.holisticHealth?.bmi)), + if (_holisticAnalysis != null && + _holisticAnalysis?.generalWellness != null) + VitalSignWidget( + vitalSignName: "General Wellness", + vitalSignValue: formatValueToTwoDp( + healthResult + ?.holisticHealth?.generalWellness)), + if (_cardiovascularRisks != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Cardiovascular Risks", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_cardiovascularRisks != null) + VitalSignWidget( + vitalSignName: "General", + vitalSignValue: formatValueToTwoDp( + healthResult?.risks?.cardiovascularRisks + ?.generalRisk)), + if (_cardiovascularRisks != null) + VitalSignWidget( + vitalSignName: "Congestive Heart Failure", + vitalSignValue: formatValueToTwoDp( + healthResult?.risks?.cardiovascularRisks + ?.congestiveHeartFailure)), + if (_cardiovascularRisks != null) + VitalSignWidget( + vitalSignName: "Coronary Heart Disease", + vitalSignValue: formatValueToTwoDp( + healthResult?.risks?.cardiovascularRisks + ?.coronaryHeartDisease)), + if (_cardiovascularRisks != null) + VitalSignWidget( + vitalSignName: "Intermittent Claudication", + vitalSignValue: formatValueToTwoDp( + healthResult?.risks?.cardiovascularRisks + ?.intermittentClaudication)), + if (_cardiovascularRisks != null) + VitalSignWidget( + vitalSignName: "Stroke", + vitalSignValue: formatValueToTwoDp( + healthResult?.risks?.cardiovascularRisks + ?.stroke)), + if (_covidRisk != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Covid Risks", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_covidRisk != null) + VitalSignWidget( + vitalSignName: "Risk", + vitalSignValue: formatValueToTwoDp( + healthResult + ?.risks?.covidRisk?.covidRisk)), + if (_scanParameters != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Scan Parameters", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_scanParameters != null) + VitalSignWidget( + vitalSignName: "Signal Quality", + vitalSignValue: formatValueToTwoDp( + healthResult + ?.scanParameters?.signalQuality)), + ])))), + )); + } + + String formatValueToTwoDp(double? value) { + return (value != null) ? value.toStringAsFixed(2) : ""; + } +} diff --git a/lib/vital_signs/components/scan_condition.dart b/lib/vital_signs/components/scan_condition.dart new file mode 100644 index 00000000..59fca530 --- /dev/null +++ b/lib/vital_signs/components/scan_condition.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class ScanCondition extends StatelessWidget { + final String scanConditionName; + final bool isConditionSatisfied; + + const ScanCondition({ + Key? key, + required this.scanConditionName, // condition + required this.isConditionSatisfied, // status + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(0, 5, 0, 0), + child: Row( + children: [ + (isConditionSatisfied) + ? Image.asset('assets/images/checkmark.png', width: 14) + : Image.asset('assets/images/crossmark.png', width: 14), + Padding( + padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), + child: Text( + scanConditionName, + style: const TextStyle( + fontSize: 14, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/vital_signs/components/scan_condition_checklist.dart b/lib/vital_signs/components/scan_condition_checklist.dart new file mode 100644 index 00000000..043430ca --- /dev/null +++ b/lib/vital_signs/components/scan_condition_checklist.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:vital_sign_camera/vital_sign_camera.dart'; +import 'scan_condition.dart'; + +class ScanConditionChecklist extends StatelessWidget { + final ScanConditions conditions; // ScanCondition + + const ScanConditionChecklist({ + Key? key, + required this.deviceSize, + required this.conditions, // ScanCondition + }) : super(key: key); + + final Size deviceSize; + + @override + Widget build(BuildContext context) { + return Positioned( + top: deviceSize.height * 0.12, + left: deviceSize.width * 0.05, + child: SizedBox( + width: deviceSize.width * 0.9, + child: Container( + color: Colors.black38, + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Scanning Conditions", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Column( + children: [ + ScanCondition( + scanConditionName: "Lighting", + isConditionSatisfied: conditions.lighting, + ), + ScanCondition( + scanConditionName: "Distance", + isConditionSatisfied: conditions.distance, + ), + ScanCondition( + scanConditionName: "Centered", + isConditionSatisfied: conditions.centered, + ), + ScanCondition( + scanConditionName: "Movement", + isConditionSatisfied: conditions.movement, + ), + ScanCondition( + scanConditionName: "FrameRate", + isConditionSatisfied: conditions.frameRate, + ), + ScanCondition( + scanConditionName: "Server Ready", + isConditionSatisfied: conditions.serverReady, + ), + ], + ), + ), + ], + ), + )), + ), + ); + } +} diff --git a/lib/vital_signs/components/scan_status.dart b/lib/vital_signs/components/scan_status.dart new file mode 100644 index 00000000..007063dd --- /dev/null +++ b/lib/vital_signs/components/scan_status.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:vital_sign_camera/vital_sign_camera.dart'; + +class ScanStatus extends StatelessWidget { + final GetHealthStage? stage; + final double? remainingTime; + + const ScanStatus({ + Key? key, + required this.stage, + required this.remainingTime, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + getRemainingTime(), + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + Text( + getScanStage(), + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + Container(height: 100), + ], + ), + ); + } + + String getScanStage() { + if (stage == GetHealthStage.waitingData) { + return 'Waiting Data...'; + } else if (stage == GetHealthStage.collectingData) { + return 'Collecting Data...'; + } else if (stage == GetHealthStage.analyzingData) { + return 'Analyzing Data...'; + } + return ''; // idle + } + + String getRemainingTime() { + return (remainingTime != null && remainingTime != double.infinity) + ? remainingTime!.toStringAsFixed(0) + : ""; + } +} diff --git a/lib/vital_signs/components/start_button.dart b/lib/vital_signs/components/start_button.dart new file mode 100644 index 00000000..8cf577f5 --- /dev/null +++ b/lib/vital_signs/components/start_button.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class StartButton extends StatelessWidget { + final void Function() onPressed; + final bool disabled; + + const StartButton({ + Key? key, + required this.onPressed, + required this.disabled, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: const EdgeInsets.fromLTRB(0, 0, 0, 50), + child: ElevatedButton( + onPressed: (disabled) ? () {} : onPressed, + style: ElevatedButton.styleFrom( + fixedSize: const Size(100, 100), + shape: const CircleBorder(), + backgroundColor: (disabled) ? Colors.grey : Colors.orangeAccent, + elevation: 8, + ), + child: const Text( + "START", + style: TextStyle( + fontSize: 16, + ), + ), + ), + ), + ); + } +} diff --git a/lib/vital_signs/components/vital_sign_widget.dart b/lib/vital_signs/components/vital_sign_widget.dart new file mode 100644 index 00000000..7174323c --- /dev/null +++ b/lib/vital_signs/components/vital_sign_widget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class VitalSignWidget extends StatelessWidget { + final String vitalSignName; + final String vitalSignValue; + final Color? textColor; + + const VitalSignWidget({ + Key? key, + required this.vitalSignName, + required this.vitalSignValue, + this.textColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.fromLTRB(0, 0, 0, 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + vitalSignName, + style: TextStyle( + fontSize: 14, + color: textColor ?? Colors.white, + ), + ), + Text( + vitalSignValue, + style: TextStyle( + fontSize: 14, + color: textColor ?? Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + } +} diff --git a/lib/vital_signs/result_screen.dart b/lib/vital_signs/result_screen.dart new file mode 100644 index 00000000..2a101e06 --- /dev/null +++ b/lib/vital_signs/result_screen.dart @@ -0,0 +1,270 @@ +import 'package:flutter/material.dart'; +import 'package:vital_sign_camera/vital_sign_camera.dart'; + +import 'components/vital_sign_widget.dart'; + +class ResultScreen extends StatelessWidget { + final Health? healthResult; + + late final VitalSign? _vitalSign; + late final HolisticAnalysis? _holisticAnalysis; + late final CardiovascularRisks? _cardiovascularRisks; + late final CovidRisk? _covidRisk; + late final ScanParameters? _scanParameters; + + ResultScreen({ + Key? key, + required this.healthResult, + }) : super(key: key) { + _vitalSign = healthResult?.vitalSigns; + _holisticAnalysis = healthResult?.holisticHealth; + _cardiovascularRisks = healthResult?.risks?.cardiovascularRisks; + _covidRisk = healthResult?.risks?.covidRisk; + _scanParameters = healthResult?.scanParameters; + } + + @override + Widget build(BuildContext context) { + final deviceSize = MediaQuery.of(context).size; + const textColor = Color.fromARGB(255, 0, 0, 0); + + return Scaffold( + appBar: AppBar( + title: const Text('Result'), + ), + body: SingleChildScrollView( + child: Center( + child: SizedBox( + width: deviceSize.width * 0.9, + child: Container( + color: const Color.fromARGB(0, 255, 255, 255), + child: Padding( + padding: const EdgeInsets.all(15), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsets.fromLTRB(0, 0, 0, 10), + child: const Text( + "Vital Signs", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + VitalSignWidget( + textColor: textColor, + vitalSignName: "Heart Rate", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.heartRate)), + VitalSignWidget( + textColor: textColor, + vitalSignName: "SPO2", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.spo2)), + VitalSignWidget( + textColor: textColor, + vitalSignName: "IBI", + vitalSignValue: + formatValueToTwoDp(healthResult?.vitalSigns.ibi)), + if (_vitalSign?.stress != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Stress", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.stress)), + if (_vitalSign?.respiratoryRate != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Respiratory Rate", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.respiratoryRate)), + if (_vitalSign?.hrvRmssd != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "HRV RMSSD", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.hrvRmssd)), + if (_vitalSign?.hrvSdnn != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "HRV SDNN", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.hrvSdnn)), + if (_vitalSign?.temperature != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Temperature", + vitalSignValue: formatValueToTwoDp( + healthResult?.vitalSigns.temperature)), + if (_vitalSign?.bloodPressure != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Blood Pressure", + vitalSignValue: + healthResult?.vitalSigns.bloodPressure ?? ""), + if (_vitalSign?.bloodPressureSystolic != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Blood Pressure Systolic", + vitalSignValue: formatValueToTwoDp(healthResult + ?.vitalSigns.bloodPressureSystolic)), + if (_vitalSign?.bloodPressureDiastolic != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Blood Pressure Diastolic", + vitalSignValue: formatValueToTwoDp(healthResult + ?.vitalSigns.bloodPressureDiastolic)), + if (_holisticAnalysis != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Holistic Analysis", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_holisticAnalysis != null && + _holisticAnalysis?.bmi != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "BMI", + vitalSignValue: formatValueToTwoDp( + healthResult?.holisticHealth?.bmi)), + if (_holisticAnalysis != null && + _holisticAnalysis?.generalWellness != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "General Wellness", + vitalSignValue: formatValueToTwoDp( + healthResult?.holisticHealth?.generalWellness)), + if (_holisticAnalysis != null && + _holisticAnalysis?.absi != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "ABSI", + vitalSignValue: formatValueToTwoDp( + healthResult?.holisticHealth?.absi)), + if (_holisticAnalysis != null && + _holisticAnalysis?.cardiacWorkload != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Cardiac Workload", + vitalSignValue: formatValueToTwoDp( + healthResult?.holisticHealth?.cardiacWorkload)), + if (_holisticAnalysis != null && + _holisticAnalysis?.pulseRespiratoryQuotient != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Pulse Respiratory Quotient", + vitalSignValue: formatValueToTwoDp(healthResult + ?.holisticHealth?.pulseRespiratoryQuotient)), + if (_holisticAnalysis != null && + _holisticAnalysis?.waistToHeightRatio != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Waist to Height Ratio", + vitalSignValue: formatValueToTwoDp(healthResult + ?.holisticHealth?.waistToHeightRatio)), + if (_cardiovascularRisks != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Cardiovascular Risks", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_cardiovascularRisks != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "General", + vitalSignValue: formatValueToTwoDp(healthResult + ?.risks?.cardiovascularRisks?.generalRisk)), + if (_cardiovascularRisks != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Congestive Heart Failure", + vitalSignValue: formatValueToTwoDp(healthResult + ?.risks + ?.cardiovascularRisks + ?.congestiveHeartFailure)), + if (_cardiovascularRisks != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Coronary Heart Disease", + vitalSignValue: formatValueToTwoDp(healthResult + ?.risks + ?.cardiovascularRisks + ?.coronaryHeartDisease)), + if (_cardiovascularRisks != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Intermittent Claudication", + vitalSignValue: formatValueToTwoDp(healthResult + ?.risks + ?.cardiovascularRisks + ?.intermittentClaudication)), + if (_cardiovascularRisks != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Stroke", + vitalSignValue: formatValueToTwoDp(healthResult + ?.risks?.cardiovascularRisks?.stroke)), + if (_covidRisk != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Covid Risks", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_covidRisk != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Risk", + vitalSignValue: formatValueToTwoDp( + healthResult?.risks?.covidRisk?.covidRisk)), + if (_scanParameters != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 25, 0, 10), + child: const Text( + "Scan Parameters", + style: TextStyle( + fontSize: 15, + color: Colors.amber, + fontWeight: FontWeight.bold, + ), + ), + ), + if (_scanParameters != null) + VitalSignWidget( + textColor: textColor, + vitalSignName: "Signal Quality", + vitalSignValue: formatValueToTwoDp( + healthResult?.scanParameters?.signalQuality)), + ]), + ), + ), + ), + ), + ), + ); + } + + String formatValueToTwoDp(double? value) { + return (value != null) ? value.toStringAsFixed(2) : ""; + } +} diff --git a/lib/vital_signs/vital_sign.dart b/lib/vital_signs/vital_sign.dart index a108889b..9d62a6b2 100644 --- a/lib/vital_signs/vital_sign.dart +++ b/lib/vital_signs/vital_sign.dart @@ -1,14 +1,40 @@ +import 'package:diplomaticquarterapp/vital_signs/result_screen.dart'; import 'package:flutter/material.dart'; import 'package:vital_sign_camera/vital_sign_camera.dart'; +// import 'package:wakelock/wakelock.dart'; + +import 'components/bounding_box_widget.dart'; +import 'components/error.dart'; +import 'components/scan_condition_checklist.dart'; +import 'components/scan_status.dart'; +import 'components/start_button.dart'; +import 'components/back_button.dart'; +import 'components/button.dart'; + +// import 'result_screen.dart'; +// import 'main.dart'; + +final UserInfo userInfo = UserInfo( + age: 30, + gender: Gender.male, + weight: 60, + // kg, Optional + height: 170, + // cm, Optional + waistCircumference: 71, + // 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}); @override - State createState() => _VitalSignState(); + State createState() => _VitalSignsState(); } -class _VitalSignState extends State { +class _VitalSignsState extends State with RouteAware { late final VitalSignCameraController _vitalSignCameraController; late Future cameraDevice; @@ -16,10 +42,9 @@ class _VitalSignState extends State { void initState() { super.initState(); cameraDevice = getFrontCamera(); + // Wakelock.enable(); // keep the screen awake } - double? _heartRate; - Future getFrontCamera() async { if (CameraPermissionStatus.authorized != await requestCameraPermission()) { return null; @@ -27,44 +52,162 @@ class _VitalSignState extends State { 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 _showingResultPage = false; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // routeObserver.subscribe(this, ModalRoute.of(context)!); + } + + @override + void dispose() { + // routeObserver.unsubscribe(this); + super.dispose(); + } + + @override + void didPushNext() { + setState(() { + _showingResultPage = true; + isCameraActive = false; + }); + } + + @override + void didPopNext() { + setState(() { + isCameraActive = true; + startedScanning = false; + _healthResult = null; + _showingResultPage = false; + }); + } + @override Widget build(BuildContext context) { + final deviceSize = MediaQuery.of(context).size; + return Scaffold( - body: Stack(children: [ - VitalSignCamera( - onCreated: _onVitalSignCameraCreated, - isActive: true, - userInfo: UserInfo( - age: 30, gender: Gender.male, userId: '__YOUR_USER_ID__'), - config: VitalSignCameraConfig(apiKey: '__YOUR_API_KEY__'), - device: cameraDevice, - onVideoFrameProcessed: _onVideoFrameProcessed), - Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - setState(() { - _vitalSignCameraController.startScanning(); - }); - }, - child: const Text('start')), - Text('Heart rate: $_heartRate'), - ], - ), - ), - ]), + 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; + }); + }, + 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) { - // setState(() { - // _heartRate = event.healthResult?.health?.vitalSigns.heartRate; - // }); + if (_showingResultPage) { + return; + } + + if (_healthResult == null && event.healthResult?.health != null && event.healthResult?.stage == GetHealthStage.analyzingData) { + 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; + } + } + + // load default void _onVitalSignCameraCreated(VitalSignCameraController controller) { _vitalSignCameraController = controller; } -} \ No newline at end of file +} diff --git a/packages/vital_sign_camera/LICENSE b/packages/vital_sign_camera/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/vital_sign_camera/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/vital_sign_camera/analysis_options.yaml b/packages/vital_sign_camera/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/packages/vital_sign_camera/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/vital_sign_camera/android/gradlew b/packages/vital_sign_camera/android/gradlew old mode 100644 new mode 100755 diff --git a/packages/vital_sign_camera/android/gradlew.bat b/packages/vital_sign_camera/android/gradlew.bat index 107acd32..ac1b06f9 100644 --- a/packages/vital_sign_camera/android/gradlew.bat +++ b/packages/vital_sign_camera/android/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/vital_sign_camera/ios/frameworks/VitalSignEngineCore.xcframework/ios-x86_64-simulator/VitalSignEngineCore.framework/VitalSignEngineCore b/packages/vital_sign_camera/ios/frameworks/VitalSignEngineCore.xcframework/ios-x86_64-simulator/VitalSignEngineCore.framework/VitalSignEngineCore old mode 100644 new mode 100755 diff --git a/packages/vital_sign_camera/ios/frameworks/VitalSignObjCFramework.xcframework/ios-arm64/VitalSignObjCFramework.framework/VitalSignObjCFramework b/packages/vital_sign_camera/ios/frameworks/VitalSignObjCFramework.xcframework/ios-arm64/VitalSignObjCFramework.framework/VitalSignObjCFramework old mode 100644 new mode 100755 diff --git a/packages/vital_sign_camera/ios/frameworks/VitalSignObjCFramework.xcframework/ios-x86_64-simulator/VitalSignObjCFramework.framework/VitalSignObjCFramework b/packages/vital_sign_camera/ios/frameworks/VitalSignObjCFramework.xcframework/ios-x86_64-simulator/VitalSignObjCFramework.framework/VitalSignObjCFramework old mode 100644 new mode 100755 diff --git a/packages/vital_sign_camera/lib/src/pixel_format.dart b/packages/vital_sign_camera/lib/src/pixel_format.dart new file mode 100644 index 00000000..bbc2401d --- /dev/null +++ b/packages/vital_sign_camera/lib/src/pixel_format.dart @@ -0,0 +1,19 @@ +/// Represents the pixel format of a `Frame`. +/// * `v420`: 420 YpCbCr 8 Bi-Planar Video Range +/// * `f420`: 420 YpCbCr 8 Bi-Planar Full Range +/// * `x420`: 420 YpCbCr 10 Bi-Planar Video Range +enum PixelFormat { f420, v420, x420 } + +PixelFormat pixelFormatFromString(String string) { + switch (string) { + case '420f': + return PixelFormat.f420; + case '420v': + return PixelFormat.v420; + case 'x420': + return PixelFormat.x420; + default: + // ignore: fixme + return PixelFormat.f420; + } +} diff --git a/packages/vital_sign_camera/lib/src/vital_sign.dart b/packages/vital_sign_camera/lib/src/vital_sign.dart new file mode 100644 index 00000000..9bc60b47 --- /dev/null +++ b/packages/vital_sign_camera/lib/src/vital_sign.dart @@ -0,0 +1,138 @@ +/// The vital signs result of a scan. +class VitalSign { + final double heartRate; + final double spo2; + final double ibi; + final double? stress; + final double? stressScore; + final double? respiratoryRate; + final double? hrvSdnn; + final double? hrvRmssd; + final double? temperature; + final String? bloodPressure; + final double? bloodPressureSystolic; + final double? bloodPressureDiastolic; + final double? facialSkinAge; + final double? bloodAlcohol; + final double? bloodSugar; + final String? version; + + const VitalSign( + this.heartRate, + this.spo2, + this.ibi, + this.stress, + this.stressScore, + this.respiratoryRate, + this.hrvSdnn, + this.hrvRmssd, + this.temperature, + this.bloodPressure, + this.bloodPressureSystolic, + this.bloodPressureDiastolic, + this.facialSkinAge, + this.bloodAlcohol, + this.bloodSugar, + this.version); + + factory VitalSign.fromMap(Map map) { + return VitalSign( + map['heartRate'], + map['spo2'], + map['ibi'], + map['stress'], + map['stressScore'], + map['respiratoryRate'], + map['hrvSdnn'], + map['hrvRmssd'], + map['temperature'], + map['bloodPressure'], + map['bloodPressureSystolic'], + map['bloodPressureDiastolic'], + map['facialSkinAge'], + map['bloodAlcohol'], + map['bloodSugar'], + map['version']); + } +} + +/// The cardiovascular risks result of a scan. +class CardiovascularRisks { + final double generalRisk; + final double coronaryHeartDisease; + final double congestiveHeartFailure; + final double intermittentClaudication; + final double stroke; + + const CardiovascularRisks(this.generalRisk, this.coronaryHeartDisease, + this.congestiveHeartFailure, this.intermittentClaudication, this.stroke); + + factory CardiovascularRisks.fromMap(Map map) { + return CardiovascularRisks( + map['generalRisk'], + map['coronaryHeartDisease'], + map['congestiveHeartFailure'], + map['intermittentClaudication'], + map['stroke']); + } +} + +/// The covid risk result of a scan. +class CovidRisk { + final double covidRisk; + + const CovidRisk(this.covidRisk); + + factory CovidRisk.fromMap(Map map) { + return CovidRisk(map['covidRisk']); + } +} + +/// The health risks result of a scan. +class HealthRisks { + final CardiovascularRisks? cardiovascularRisks; + final CovidRisk? covidRisk; + final String version; + + const HealthRisks(this.cardiovascularRisks, this.covidRisk, this.version); + + factory HealthRisks.fromMap(Map map) { + return HealthRisks( + map['cardiovascularRisks'] != null + ? CardiovascularRisks.fromMap(map['cardiovascularRisks']) + : null, + map['covidRisk'] != null ? CovidRisk.fromMap(map['covidRisk']) : null, + map['version']); + } +} + +/// The holistic analysis result of a scan. +class HolisticAnalysis { + final double? generalWellness; + final double? bmi; + final double? absi; + final double? cardiacWorkload; + final double? pulseRespiratoryQuotient; + final double? waistToHeightRatio; + final String? version; + + const HolisticAnalysis( + this.generalWellness, + this.bmi, + this.absi, + this.cardiacWorkload, + this.pulseRespiratoryQuotient, + this.waistToHeightRatio, + this.version); + + factory HolisticAnalysis.fromMap(Map map) { + return HolisticAnalysis( + map['generalWellness'], + map['bmi'], + map['absi'], + map['cardiacWorkload'], + map['pulseRespiratoryQuotient'], + map['waistToHeightRatio'], + map['version']); + } +} diff --git a/packages/vital_sign_camera/lib/src/vital_sign_camera_platform_interface.dart b/packages/vital_sign_camera/lib/src/vital_sign_camera_platform_interface.dart new file mode 100644 index 00000000..4e9601fb --- /dev/null +++ b/packages/vital_sign_camera/lib/src/vital_sign_camera_platform_interface.dart @@ -0,0 +1,42 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +import 'camera_permission.dart'; + +import 'camera_device.dart'; +import 'vital_sign_camera_method_channel.dart'; + +abstract class VitalSignCameraPlatform extends PlatformInterface { + /// Constructs a VitalSignCameraPlatform. + VitalSignCameraPlatform() : super(token: _token); + + static final Object _token = Object(); + + static VitalSignCameraPlatform _instance = MethodChannelVitalSignCamera(); + + /// The default instance of [VitalSignCameraPlatform] to use. + /// + /// Defaults to [MethodChannelVitalSignCamera]. + static VitalSignCameraPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [VitalSignCameraPlatform] when + /// they register themselves. + static set instance(VitalSignCameraPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future> availableCameraDevices() { + throw UnimplementedError( + 'availableCameraDevices() has not been implemented.'); + } + + Future requestCameraPermission() { + throw UnimplementedError( + 'availableCameraDevices() has not been implemented.'); + } + + Future getCameraPermissionStatus() { + throw UnimplementedError( + 'getCameraPermissionStatus() has not been implemented.'); + } +}