Merge branch 'merge-update-with-lab-changes' into update-to-3.32-sdk

# Conflicts:
#	android/app/local.properties
#	android/app/src/main/AndroidManifest.xml
#	android/gradle.properties
#	android/gradle/wrapper/gradle-wrapper.properties
#	ios/Podfile
#	ios/Runner.xcodeproj/project.pbxproj
#	ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
#	ios/Runner/Info.plist
#	ios/Runner/Runner.entitlements
#	lib/config/localized_values.dart
#	lib/core/model/labs/LabOrderResult.dart
#	lib/core/model/labs/lab_result.dart
#	lib/core/service/client/base_app_client.dart
#	lib/core/service/medical/labs_service.dart
#	lib/core/viewModels/medical/labs_view_model.dart
#	lib/core/viewModels/pharmacyModule/PharmacyAddressesViewModel.dart
#	lib/core/viewModels/project_view_model.dart
#	lib/new_ui/exception_widget/ExceptionBottomSheet.dart
#	lib/pages/AlHabibMedicalService/ComprehensiveMedicalCheckup/NewCMC/cmc_location_page.dart
#	lib/pages/AlHabibMedicalService/ComprehensiveMedicalCheckup/NewCMC/new_cmc_step_tow_page.dart
#	lib/pages/AlHabibMedicalService/HomeHealthCare/NewHomeHealthCare/location_page.dart
#	lib/pages/AlHabibMedicalService/HomeHealthCare/NewHomeHealthCare/new_Home_health_care_step_one_page.dart
#	lib/pages/AlHabibMedicalService/HomeHealthCare/NewHomeHealthCare/new_Home_health_care_step_tow_page.dart
#	lib/pages/landing/landing_page.dart
#	lib/pages/login/forgot-password.dart
#	lib/pages/login/register_new.dart
#	lib/pages/login/saved_login.dart
#	lib/pages/login/welcome.dart
#	lib/pages/medical/labs/laboratory_result_page.dart
#	lib/uitl/app_toast.dart
#	lib/uitl/translations_delegate_base.dart
#	lib/uitl/utils.dart
#	lib/widgets/Loader/gif_loader_container.dart
#	lib/widgets/data_display/medical/LabResult/FlowChartPage.dart
#	lib/widgets/data_display/medical/LabResult/Lab_Result_details_wideget.dart
#	lib/widgets/data_display/medical/LabResult/laboratory_result_widget.dart
#	lib/widgets/in_app_browser/InAppBrowser.dart
#	lib/widgets/pickupLocation/PickupLocationFromMap.dart
#	pubspec.yaml
pull/2/head
haroon amjad 7 months ago
commit f265c605ca

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f7a6a7906be96d2288f5d63a5a54c515a6e987fe
channel: stable
project_type: app

@ -0,0 +1,13 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart"
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICgjCCAWoCAQAwPTEjMCEGCSqGSIb3DQEJARYUSGFyb29uNjEzOEBnbWFpbC5j
b20xCTAHBgNVBAMMADELMAkGA1UEBhMCQUUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDHadAER62mSf+wOxPu9rF1EvifDxbAEv6znZIHvwimgShdPLLS
rkFdN4VpSHMzrASqpHnlSL3wxO6eym9pATH3WfLvKIQxnn9OkO5fScjQi0RkFbe0
JqTqN2LaFxrSFrhqQ/1p/izr690HqjrfquRI8t8evatyTku67/xcCj6uSIVLfJoS
8YCDVn0Y+w9n2Vw/+kHiNY6P96qp/CfXZgMLFpUu0mqcmZl97htfmB9AH2tC45yh
mKxDJiKEnk69zVCh/bkc9tFE1tUDkkKblJTCEyWfdDUUYM4ET3t3fBqWB78F4J3m
XglByGZmHgY2+7nsSQBiq7k9tFN2c7FZ9BD3AgMBAAGgADANBgkqhkiG9w0BAQsF
AAOCAQEAJxqoyfEJ7V83ffCAqkYNNEcCQMIRi1Y4cTTm/5KZGA/UPeDeX48/bm4K
XIf2zbv88OGASInBrvMDp0x0zs13Dx2yXxofGN+iQSBwN6jPV/upmm0DjgTmgUGh
BJste70LaU228ADzWDw8UJm/GKjK8WvE0XOGpPKYq9cuVW3M+6QRoRHXy0eJc2PX
VgopsuhHUyA5trxM7k4yc6GgYVx+fAwMaplovTwn5U8XK6bDEIvCjImfat/u4Wp8
R31p0BQzQaeyWfpp9Ucn/fvQSeB+wtjhHuA1ukcyWY1kBYOyCMIHymFrJLaTpUj7
aQFYZDV28HYv0yWhWIAD9Pu+aDHWpA==
-----END CERTIFICATE REQUEST-----

@ -0,0 +1,74 @@
Bag Attributes
friendlyName: VoIP Services: com.HMG.HMG-Smartphone
localKeyID: 70 4A 99 03 F8 D7 92 FA F7 2F DD 3B D8 BF 2C F0 BD CE 21 C8
subject=/UID=com.HMG.HMG-Smartphone.voip/CN=VoIP Services: com.HMG.HMG-Smartphone/OU=3A359E86ZF/O=Dr Sulaiman Al Habib Medical Center/C=SA
issuer=/CN=Apple Worldwide Developer Relations Certification Authority/OU=G4/O=Apple Inc./C=US
-----BEGIN CERTIFICATE-----
MIIGkTCCBXmgAwIBAgIQFB+/PYFlrZ6SCzSX9cqOBDANBgkqhkiG9w0BAQsFADB1
MUQwQgYDVQQDDDtBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTELMAkGA1UECwwCRzQxEzARBgNVBAoMCkFw
cGxlIEluYy4xCzAJBgNVBAYTAlVTMB4XDTI0MDYwOTEyMDc1MVoXDTI1MDcwOTEy
MDc1MFowga0xKzApBgoJkiaJk/IsZAEBDBtjb20uSE1HLkhNRy1TbWFydHBob25l
LnZvaXAxLjAsBgNVBAMMJVZvSVAgU2VydmljZXM6IGNvbS5ITUcuSE1HLVNtYXJ0
cGhvbmUxEzARBgNVBAsMCjNBMzU5RTg2WkYxLDAqBgNVBAoMI0RyIFN1bGFpbWFu
IEFsIEhhYmliIE1lZGljYWwgQ2VudGVyMQswCQYDVQQGEwJTQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMdp0ARHraZJ/7A7E+72sXUS+J8PFsAS/rOd
kge/CKaBKF08stKuQV03hWlIczOsBKqkeeVIvfDE7p7Kb2kBMfdZ8u8ohDGef06Q
7l9JyNCLRGQVt7QmpOo3YtoXGtIWuGpD/Wn+LOvr3QeqOt+q5Ejy3x69q3JOS7rv
/FwKPq5IhUt8mhLxgINWfRj7D2fZXD/6QeI1jo/3qqn8J9dmAwsWlS7SapyZmX3u
G1+YH0Afa0LjnKGYrEMmIoSeTr3NUKH9uRz20UTW1QOSQpuUlMITJZ90NRRgzgRP
e3d8GpYHvwXgneZeCUHIZmYeBjb7uexJAGKruT20U3ZzsVn0EPcCAwEAAaOCAuIw
ggLeMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUW9n6HeeaGgujmXYiUIY+kchb
d6gwcAYIKwYBBQUHAQEEZDBiMC0GCCsGAQUFBzAChiFodHRwOi8vY2VydHMuYXBw
bGUuY29tL3d3ZHJnNC5kZXIwMQYIKwYBBQUHMAGGJWh0dHA6Ly9vY3NwLmFwcGxl
LmNvbS9vY3NwMDMtd3dkcmc0MDUwggEeBgNVHSAEggEVMIIBETCCAQ0GCSqGSIb3
Y2QFATCB/zCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRp
ZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRo
ZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1
c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGlj
ZSBzdGF0ZW1lbnRzLjA3BggrBgEFBQcCARYraHR0cHM6Ly93d3cuYXBwbGUuY29t
L2NlcnRpZmljYXRlYXV0aG9yaXR5LzATBgNVHSUEDDAKBggrBgEFBQcDAjAyBgNV
HR8EKzApMCegJaAjhiFodHRwOi8vY3JsLmFwcGxlLmNvbS93d2RyZzQtNi5jcmww
HQYDVR0OBBYEFHBKmQP415L69y/dO9i/LPC9ziHIMA4GA1UdDwEB/wQEAwIHgDAQ
BgoqhkiG92NkBgMBBAIFADAQBgoqhkiG92NkBgMCBAIFADBDBgoqhkiG92NkBgME
BDUMM2NvbS5ITUcuSE1HLVNtYXJ0cGhvbmUsIGNvbS5ITUcuSE1HLVNtYXJ0cGhv
bmUudm9pcDAQBgoqhkiG92NkBgMFBAIFADAmBgoqhkiG92NkBgMDBBgMFmNvbS5I
TUcuSE1HLVNtYXJ0cGhvbmUwDQYJKoZIhvcNAQELBQADggEBAJAcIzSskk8/GbxQ
7nGUPPEWFswL66xTt3GusHmeXkKDXG3ac+4jLk8yU7gunos77CNDm+9TkShdJ2Rg
tTRopK4ockqwoPrCeEVrooHR4e42YvmkB6AfYKJRnQZQVJIpW0OES7vrtoWVGoNj
ktHOV+wFIuAQPI9P4z0RFyRw85MHr+jFqxeLU/1500PwCxIiNHkIcUtzZgrnr22R
7rgkxav+RrjLjXVQvC4VJispKCU7yR69Xv1rsC5zc7OTCuCkbQ9FvnfsiUz5ZDzp
3D0gGojU9wmJ1yVk9HiZsuiVwmg/LbDHI3pSYbxz3n1UHB6Pu0jX3c3Uxkx674HD
/0uvHzg=
-----END CERTIFICATE-----
Bag Attributes
localKeyID: 70 4A 99 03 F8 D7 92 FA F7 2F DD 3B D8 BF 2C F0 BD CE 21 C8
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDHadAER62mSf+w
OxPu9rF1EvifDxbAEv6znZIHvwimgShdPLLSrkFdN4VpSHMzrASqpHnlSL3wxO6e
ym9pATH3WfLvKIQxnn9OkO5fScjQi0RkFbe0JqTqN2LaFxrSFrhqQ/1p/izr690H
qjrfquRI8t8evatyTku67/xcCj6uSIVLfJoS8YCDVn0Y+w9n2Vw/+kHiNY6P96qp
/CfXZgMLFpUu0mqcmZl97htfmB9AH2tC45yhmKxDJiKEnk69zVCh/bkc9tFE1tUD
kkKblJTCEyWfdDUUYM4ET3t3fBqWB78F4J3mXglByGZmHgY2+7nsSQBiq7k9tFN2
c7FZ9BD3AgMBAAECggEBALTXH58SBcd86hS+rsgSDkJ2ruNIQsBzOWpYJjtCIi0l
ih5r0lu/TWWk2SbuXhLpF95MhILLIf18Ck4UlU5rmA2SE/6RBrkc3KS6SII/no6J
Db+Z9lu4g+wnyEyxkVAe7KTp/aGmsMROxMaTuQpnptkE8BSnuxhRKtpfksNBQzC2
19OkEy4JAO7xWbm9w/ASiCtYf0Vj26DQo982Fe44cpG08iB2v1uUJINQkGzmg+85
vvn5A1ECpSpgd/eOa68ojxeNSU7iFGZPbpjuincLPBFlC5zp9KD1Bt6p2KwIizBl
GPYTp6a1DnWly6n0PuLN0fl/nRmI6ar48Ja1Qzk8NvECgYEA5iQo9lnjNOHjT6bk
hj5QXMz3MDMCPdJo6jQyADtmsC/F4RjufujfXPhMv2v8JLAjQjNMgfhTSF68zYHf
i1qW8e/CgWu7mGQEIhWtxiTkLou8zW3VjxwpgfeUsGI5L+PxoN53ZUVcetLz4Y58
8ATEhlxh02uGmG2k3HbvFu9NBT8CgYEA3dHJfFX2xv53ZD1E2IE1rM3j7a32s7VB
k/YX8ODrAJxwNmy/vm4gd5h6ofqVS1/IV4NHFbmQ5c1tzB44YGOEZH5Ta9hhHgHU
xoa3q6t+kjR9KrQvxjTSe9mW5x/jAgU0Wp/XrAfMZxc2eukb4/sJAGKG0kyFeX5O
5abVixWj7kkCgYAafd8ZPI+i7r2z1YpIybOGezGZIsYHR8k77HY1AKF5doZYuv35
LeVIVmy/PDbyIjRs0mrkKymrEvy4y1JoQC1P/EFvlG8rw5eWNNikDmsV0JATNal3
p84/X7iF9UiP8rVFx1YHGkzi9pWMSVKobmQQ82FvbNk5gqWPm0E6aqF7EQKBgQDB
mE0PlT9SIdk1xTuG8g5UiUV6tS/xXCql5kyy0LqJx/8voO4gV6FCf/PX1wnhxPDn
YA30agaKaiZ/1YhCp9hulMpd/3WTaOm5cu5uh7hqBiGP2b8wb/2cViWcBnpr0vsE
dSkYXZ4KA3pq/3R2HyyWB2e/t0Vu6cwWJuuM/hAa8QKBgGNRStNVSMPvzx2X7mB6
EqdlWqVbfSTHJiZKyNPWHlSuZgdQ14BRwq7KZ0ysZy+kOX4rh1rIfqzrKc62xEXB
E+fmIpu+DUOM5ojEPCf9Om87r3fMJ54SjM7z1dtVc+Muy4TnFZ4UQcN9txWbnkAo
++H5gC9DBedOEXkwoCYjoeuM
-----END PRIVATE KEY-----

Binary file not shown.

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.864393916058-ekeb4s8tgfo58dutv0l54399t7ivr06r</string>
<key>API_KEY</key>
<string>AIzaSyA_6ayGCk4fly7o7eTVBrj9kuHBYHMAOfs</string>
<key>GCM_SENDER_ID</key>
<string>864393916058</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.cloud.diplomaticquarterapp</string>
<key>PROJECT_ID</key>
<string>diplomaticquarter-d2385</string>
<key>STORAGE_BUCKET</key>
<string>diplomaticquarter-d2385.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:864393916058:ios:13f787bbfe6051f8b97923</string>
<key>DATABASE_URL</key>
<string>https://diplomaticquarter-d2385.firebaseio.com</string>
</dict>
</plist>

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.815750722565-da8p56le8bd6apsbm9eft0jjl1rtpgkt</string>
<key>ANDROID_CLIENT_ID</key>
<string>815750722565-m14h8mkosm7cnq6uh6rhqr54dn02d705.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>AIzaSyDiXnCO00li4V7Ioa2YZ_M4ECxRsu_P9tA</string>
<key>GCM_SENDER_ID</key>
<string>815750722565</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.HMG.Smartphone</string>
<key>PROJECT_ID</key>
<string>api-project-815750722565</string>
<key>STORAGE_BUCKET</key>
<string>api-project-815750722565.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:815750722565:ios:328ec247a81a2ca23c186c</string>
<key>DATABASE_URL</key>
<string>https://api-project-815750722565.firebaseio.com</string>
</dict>
</plist>

@ -5,4 +5,4 @@
# For customization when using a Version Control System, please read the
# header note.
#Sun Sep 20 09:53:03 EEST 2020
sdk.dir=/Users/erababah/Library/Android/sdk
sdk.dir=/Users/amir/Library/Android/sdk

@ -111,12 +111,13 @@
<application
android:name=".Application"
android:allowBackup="false"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_launcher_local"
android:label="Dr. Alhabib"
android:screenOrientation="sensorPortrait"
android:showOnLockScreen="true"
android:usesCleartextTraffic="true"
tools:replace="android:label">
tools:replace="android:extractNativeLibs,android:label">
<activity
android:name="com.cloud.hmg_patient_app.whatsapp.WhatsAppCodeActivity"
android:exported="true"
@ -143,6 +144,9 @@
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"
tools:node="merge">
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="true" />
<!--
Specifies an Android theme to apply to this Activity as soon as
@ -170,6 +174,20 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- <intent-filter android:autoVerify="true">-->
<!-- <action android:name="android.intent.action.VIEW" />-->
<!-- <category android:name="android.intent.category.DEFAULT" />-->
<!-- <category android:name="android.intent.category.BROWSABLE" />-->
<!-- <data-->
<!-- android:host="hmg.com"-->
<!-- android:scheme="http" />-->
<!-- <data-->
<!-- android:host="www.hmg.com"-->
<!-- android:scheme="http" />-->
<!-- <data android:scheme="https" />-->
<!-- </intent-filter>-->
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK" />

@ -1,7 +1,10 @@
org.gradle.jvmargs=-Xmx4096m
kotlin.daemon.jvmargs=-Xmx4096m
#android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
android.defaults.buildfeatures.buildconfig=true
android.suppressUnsupportedCompileSdk=33
MAPBOX_USER_NAME = "mapbox"
MAPBOX_DOWNLOADS_TOKEN="sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg"
#MAPBOX_DOWNLOADS_TOKEN="pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ"
kotlin.jvm.target.validation.mode=IGNORE

@ -1,6 +1,6 @@
#Wed Jul 02 15:26:33 AST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -0,0 +1,4 @@
<svg height="1792" id="download" width="1792" xmlns="http://www.w3.org/2000/svg">
<path
d="M1344 1344q0-26-19-45t-45-19-45 19-19 45 19 45 45 19 45-19 19-45zm256 0q0-26-19-45t-45-19-45 19-19 45 19 45 45 19 45-19 19-45zm128-224v320q0 40-28 68t-68 28H160q-40 0-68-28t-28-68v-320q0-40 28-68t68-28h465l135 136q58 56 136 56t136-56l136-136h464q40 0 68 28t28 68zm-325-569q17 41-14 70l-448 448q-18 19-45 19t-45-19L403 621q-31-29-14-70 17-39 59-39h256V64q0-26 19-45t45-19h256q26 0 45 19t19 45v448h256q42 0 59 39z"></path>
</svg>

After

Width:  |  Height:  |  Size: 531 B

@ -0,0 +1,5 @@
<svg id="Download" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
<path
class="color000000 svgShape"
d="M1344 1344q0-26-19-45t-45-19-45 19-19 45 19 45 45 19 45-19 19-45zm256 0q0-26-19-45t-45-19-45 19-19 45 19 45 45 19 45-19 19-45zm128-224v320q0 40-28 68t-68 28H160q-40 0-68-28t-28-68v-320q0-40 28-68t68-28h465l135 136q58 56 136 56t136-56l136-136h464q40 0 68 28t28 68zm-325-569q17 41-14 70l-448 448q-18 19-45 19t-45-19L403 621q-31-29-14-70 17-39 59-39h256V64q0-26 19-45t45-19h256q26 0 45 19t19 45v448h256q42 0 59 39z" fill="#ffffff"></path>
</svg>

After

Width:  |  Height:  |  Size: 580 B

@ -0,0 +1 @@
84018bae6147d7170a2e3bd8d9e051aa

@ -3,7 +3,7 @@ platform :ios, '14.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
# use_frameworks! :linkage => :static
use_frameworks! :linkage => :static
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,

@ -122,5 +122,7 @@
<true/>
<key>MBXAccessToken</key>
<string>pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ</string>
<key>FlutterDeepLinkingEnabled</key>
<false/>
</dict>
</plist>

@ -4,6 +4,11 @@
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:www.hmg.com</string>
<string>webcredentials:example.com</string>
</array>
<key>com.apple.developer.healthkit</key>
<true/>
<key>com.apple.developer.healthkit.access</key>
@ -26,5 +31,9 @@
<array>
<string>$(TeamIdentifierPrefix)*</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:hmg.com</string>
</array>
</dict>
</plist>

@ -2141,6 +2141,15 @@ const Map localizedValues = {
"EROnlineCheckInTapOnCheckIn": {"en": "Tap on the check-in button within the app", "ar": "اضغط على زر تسجيل الدخول داخل التطبيق"},
"download": {"en": "Download", "ar": "تحميل"},
"share": {"en": "Share", "ar": "يشارك"},
"readMore": {"en": "Read More", "ar": "اقرأ أكثر"},
"showLess": {"en": "Show Less", "ar": "عرض أقل"},
"change": {"en": "Change", "ar": "فرق"},
"referenceRange": {"en": "Reference Range", "ar": "النطاق المرجعي"},
"criticalLow": {"en": "Critical Low", "ar": "النطاق المرجعي"},
"criticalHigh": {"en": "Critical High", "ar": "حرج مرتفع"},
"high": {"en": "High", "ar": "مرتفع"},
"normal": {"en": "Normal", "ar": "عادي"},
"low": {"en": "Low", "ar": "منخفض"},
"nearest": {"en": "Nearest", "ar": "الأقرب"},
"selectRegion": {"en": "Select Region", "ar": "اختر المنطقة"},
"selectFacitlity": {"en": "Select Facility", "ar": "اختر المنشأة"},
@ -2368,7 +2377,6 @@ const Map localizedValues = {
"pleaseEnterMobile": {"en": "Please enter phone number", "ar":"الرجاء إدخال رقم الهاتف"},
"pleaseEnterValidMobile": {"en": "Please enter valid phone number", "ar":"الرجاء إدخال رقم هاتف صالح"},
"pleaseEnterNationalIdOrFileNo": {"en": "Please enter National id or File no", "ar":"الرجاء إدخال رقم الهوية الوطنية أو رقم الملف"},
"thisResult" : {"en": "This Result", "ar": "هذه النتيجة"},
"seeAllGraphValues" : {"en": "See all values", "ar": "شاهد جميع القيم"},
};

@ -18,6 +18,12 @@ class LabOrderResult {
String? uOM;
String? verifiedOn;
String? verifiedOnDateTime;
String? changeResult;
String? calculatedResultFlag;
String? criticalHigh;
String? referenceHigh;
String? criticalLow;
String? referenceLow;
LabOrderResult(
{this.description,
@ -38,7 +44,13 @@ class LabOrderResult {
this.testCode,
this.uOM,
this.verifiedOn,
this.verifiedOnDateTime});
this.calculatedResultFlag,
this.verifiedOnDateTime,
this.criticalHigh,
this.referenceHigh,
this.criticalLow,
this.referenceLow,
});
LabOrderResult.fromJson(Map<String, dynamic> json) {
description = json['Description'];
@ -60,6 +72,12 @@ class LabOrderResult {
uOM = json['UOM'];
verifiedOn = json['VerifiedOn'];
verifiedOnDateTime = json['VerifiedOnDateTime'];
changeResult = json['ChangeResult'];
calculatedResultFlag = json['CalculatedResultFlag'];
criticalHigh = json['CriticalHigh'];
referenceHigh = json['ReferenceHigh'];
criticalLow = json['CriticalLow'];
referenceLow = json['ReferenceLow'];
}
Map<String, dynamic> toJson() {
@ -83,6 +101,16 @@ class LabOrderResult {
data['UOM'] = this.uOM;
data['VerifiedOn'] = this.verifiedOn;
data['VerifiedOnDateTime'] = this.verifiedOnDateTime;
data['ChangeResult'] = this.changeResult;
data['CriticalHigh'] = this.criticalHigh;
data['ReferenceHigh'] = this.referenceHigh;
data['CriticalLow'] = this.criticalLow;
data['ReferenceLow'] = this.referenceLow;
return data;
}
@override
String toString() {
return 'LabOrderResult(flag: $calculatedResultFlag, value: $resultValue, verifiedOn: $verifiedOnDateTime)';
}
}

@ -1,12 +1,99 @@
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/newUI/labWidgets.dart';
class LabResultList {
String? filterName = "";
String? description = "";
List<LabResult>? patientLabResultList = [];
LabResultList({this.filterName, LabResult? lab}) {
LabResultList({this.filterName, this.description, LabResult? lab}) {
patientLabResultList!.add(lab!);
}
}
enum ResultFlag {
N('normal'),
H('high'),
L('low'),
CL('criticalLow'),
CH('criticalHigh'),
HCH('highCriticalHigh'),
LCL('lowCriticalLow'),
IRR('interpretive');
final String value;
const ResultFlag(this.value);
static ResultFlag? fromJson(String key, num? resultValueFlag) {
///these lines are added cause the result flag is coupled with two values
///if resultvalueflag is empty or line carriage then it has to be interpretive
// if (resultValueFlag == null) return ResultFlag.IRR;
// if(resultValueFlag == 5) return ResultFlag.IRR;
switch (key) {
case 'N':
return ResultFlag.N;
case 'H':
return ResultFlag.H;
case 'L':
return ResultFlag.L;
case 'CL':
return ResultFlag.CL;
case 'CH':
return ResultFlag.CH;
case 'HCH':
return ResultFlag.HCH;
case 'LCL':
return ResultFlag.LCL;
case '':
return ResultFlag.IRR;
default:
return null;
}
}
String toJson() {
switch (this) {
case ResultFlag.N:
return 'N';
case ResultFlag.H:
return 'H';
case ResultFlag.L:
return 'L';
case ResultFlag.CL:
return 'CL';
case ResultFlag.CH:
return 'CH';
case ResultFlag.HCH:
return 'HCH';
case ResultFlag.LCL:
return 'LCL';
case ResultFlag.IRR:
return '';
}
}
ResultTypes getType() {
switch (this) {
case ResultFlag.N:
return ResultTypes.normal;
case ResultFlag.H:
return ResultTypes.high;
case ResultFlag.L:
return ResultTypes.low;
case ResultFlag.CL:
return ResultTypes.criticalLow;
case ResultFlag.CH:
return ResultTypes.criticalHigh;
case ResultFlag.HCH:
return ResultTypes.highCriticalHigh;
case ResultFlag.LCL:
return ResultTypes.lowCriticalLow;
case ResultFlag.IRR:
return ResultTypes.IRR;
}
}
}
class LabResult {
String? description;
dynamic femaleInterpretativeData;
@ -31,7 +118,13 @@ class LabResult {
String? testCode;
String? uOM;
String? verifiedOn;
String? packageShortDescription;
String? testShortDescription;
ResultFlag? calculatedResultFlag;
dynamic verifiedOnDateTime;
num? percentage;
num? width;
num? resultTypeID;
LabResult(
{this.description,
@ -59,7 +152,7 @@ class LabResult {
this.verifiedOn,
this.verifiedOnDateTime});
LabResult.fromJson(Map<String, dynamic> json) {
LabResult.fromJson(Map<String, dynamic> json, {String? flag}) {
description = json['Description'];
femaleInterpretativeData = json['FemaleInterpretativeData'];
gender = json['Gender'];
@ -84,6 +177,10 @@ class LabResult {
uOM = json['UOM'];
verifiedOn = json['VerifiedOn'];
verifiedOnDateTime = json['VerifiedOnDateTime'];
packageShortDescription = json['PackageShortDescription'];
testShortDescription = json['TestShortDescription'];
resultTypeID = json['ResultTypeID'];
calculatedResultFlag = ResultFlag.fromJson(flag ?? json['CalculatedResultFlag'], resultTypeID);
}
Map<String, dynamic> toJson() {
@ -112,6 +209,17 @@ class LabResult {
data['UOM'] = this.uOM;
data['VerifiedOn'] = this.verifiedOn;
data['VerifiedOnDateTime'] = this.verifiedOnDateTime;
data['PackageShortDescription'] = this.packageShortDescription;
data['TestShortDescription'] = this.testShortDescription;
data['CalculatedResultFlag'] = this.calculatedResultFlag?.toJson() ?? '';
return data;
}
bool shouldShowResultBarAndGraph() {
if (resultTypeID == null) return false;
if (resultTypeID == 6) return false;
return true;
}
}

@ -187,7 +187,7 @@ class BaseAppClient {
// body['IdentificationNo'] = 1023854217;
// body['MobileNo'] = "531940021"; //0560717232
// body['PatientID'] = 2266239; //4609100
// body['PatientID'] = 53320; //4609100
// body['TokenID'] = "@dm!n";
// Patient ID: 3027574
@ -207,7 +207,8 @@ class BaseAppClient {
debugPrint(jsonBody);
// }
// return;
if (await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck)) {
// if (await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck)) {
if (await Utils.checkConnection(bypassConnectionCheck: true)) {
final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers);
final int statusCode = response.statusCode;
if (statusCode < 200 || statusCode >= 400 || json == null) {
@ -303,10 +304,10 @@ class BaseAppClient {
print(e);
print(e);
if(e.toString().contains("ClientException")) {
if (e.toString().contains("ClientException")) {
onFailure('Something went wrong, plase try again', -1);
_analytics.errorTracking.log("internet_connectivity", error: "no internet available");
}else{
} else {
onFailure(e.toString(), -1);
}
_analytics.errorTracking.log(endPoint, error: "api exception: $e - API Path: $url");

@ -1,3 +1,5 @@
import 'dart:ui';
import 'package:hmg_patient_app/config/config.dart';
import 'package:hmg_patient_app/core/model/labs/LabOrderResult.dart';
import 'package:hmg_patient_app/core/model/labs/lab_result.dart';
@ -7,6 +9,8 @@ import 'package:hmg_patient_app/core/model/labs/request_patient_lab_special_resu
import 'package:hmg_patient_app/core/model/labs/request_send_lab_report_email.dart';
import 'package:hmg_patient_app/core/service/base_service.dart';
import 'package:hmg_patient_app/models/Authentication/authenticated_user.dart';
import 'package:hmg_patient_app/uitl/date_uitl.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/lab_result_graph.dart';
class LabsService extends BaseService {
List<PatientLabOrders> patientLabOrdersList = [];
@ -201,7 +205,9 @@ class LabsService extends BaseService {
response['ListPLR'].forEach((lab) {
labOrdersResultsList.add(LabOrderResult.fromJson(lab));
});
labOrdersResultsList = labOrdersResultsList.reversed.toList();
}, onFailure: (String error, int statusCode) {
labOrdersResultsList.clear();
hasError = true;
super.error = error;
}, body: body);
@ -240,4 +246,516 @@ class LabsService extends BaseService {
super.error = error;
}, body: _requestSendLabReportEmail.toJson());
}
List<LabOrderResult> sortByFlagAndValue(List<LabOrderResult> original) {
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
int getFlagPriority(String? flag) {
if (flag == null) return priorityOrder.length;
final index = priorityOrder.indexOf(flag);
return index == -1 ? priorityOrder.length : index;
}
double parseResultValue(String? value) {
if (value == null) return double.nan;
return double.tryParse(value) ?? double.nan;
}
final copy = List<LabOrderResult>.from(original);
copy.sort((a, b) {
final aFlagPriority = getFlagPriority(a.calculatedResultFlag);
final bFlagPriority = getFlagPriority(b.calculatedResultFlag);
if (aFlagPriority != bFlagPriority) {
return aFlagPriority.compareTo(bFlagPriority);
}
final aValue = parseResultValue(a.resultValue);
final bValue = parseResultValue(b.resultValue);
return aValue.compareTo(bValue);
});
return copy;
}
Map<String, LabOrderResult> mapFirstItemByPriority(
List<LabOrderResult> sortedResults) {
final Map<String, LabOrderResult> priorityMap = {};
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
for (final result in sortedResults) {
final priority = result.calculatedResultFlag?.trim();
if (priority != null &&
priorityOrder.contains(priority) &&
!priorityMap.containsKey(priority)) {
priorityMap[priority] = result;
}
// Early exit if all priorities are found
if (priorityMap.length == priorityOrder.length) break;
}
print("the map of priority is :\n $priorityMap");
return priorityMap;
}
List<LabOrderResult> getMostRecentThree(List<LabOrderResult> original) {
DateTime? parseVerifiedDate(String? raw) {
if (raw == null) return null;
final regex = RegExp(r'\/Date\((\d+)\)\/');
final match = regex.firstMatch(raw);
if (match != null) {
print("the match iss not null");
final millis = int.tryParse(match.group(1)!);
if (millis != null) {
print(
"the data and time is ${DateTime.fromMillisecondsSinceEpoch(millis)}");
return DateTime.fromMillisecondsSinceEpoch(millis);
}
}
return null;
}
final copy = List<LabOrderResult>.from(original);
copy.sort((a, b) {
final aDate = DateUtil.convertStringToDate(a.verifiedOnDateTime);
final bDate = DateUtil.convertStringToDate(b.verifiedOnDateTime);
if (aDate == null && bDate == null) return 0;
if (aDate == null) return 1;
if (bDate == null) return -1;
return bDate.compareTo(aDate); // descending
});
print("the copied item are $copy");
labOrdersResultsList = copy;
return copy.take(3).toList();
}
List<ThresholdRange> buildThresholdList(List<LabOrderResult> topResults) {
var mapOfPriority = mapFirstItemByPriority(topResults);
return mapResultToThreshold(topResults, mapOfPriority);
}
List<ThresholdRange> mapResultToThreshold(
List<LabOrderResult> results, Map<String, LabOrderResult> mapOfPriority) {
// Extract valid numeric results
List<double> actualValues = results
.map((e) => double.tryParse(e.resultValue ?? ''))
.where((v) => v != null)
.cast<double>()
.toList();
if (actualValues.isEmpty) return [];
actualValues.sort();
double min = actualValues.first;
double max = actualValues.last;
double baseRange = max - min;
// Handle single value or equal values
if (baseRange == 0) {
baseRange = max * 0.2; // ±10%
min = max - baseRange / 2;
max = max + baseRange / 2;
}
// Adjust range scale based on number of actual values
// >5 = compress thresholds; <5 = expand thresholds
int valueCount = actualValues.length;
double scalingFactor = valueCount >= 5 ? 1.0 : (5 / valueCount);
double adjustedRange = baseRange * scalingFactor;
// Recalculate thresholds based on adjusted range
// double criticalLow = min;
// double low = min + 0.25 * adjustedRange;
// double normal = min + 0.5 * adjustedRange;
// double high = min + 0.75 * adjustedRange;
// double criticalHigh = min + adjustedRange;
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
var mapOfValues = inferThresholds(mapOfPriority);
var item = results.first;
String? realCriticalLow =
(item.criticalLow == "0") ? null : item.criticalLow;
String? realReferenceHigh =
(item.referenceHigh == "0") ? null : item.referenceHigh;
String? realCriticalHigh =
(item.criticalHigh == "0") ? null : item.criticalHigh;
String? realReferenceLow =
(item.referenceLow == "0") ? null : item.referenceLow;
final adjustedValues = adjustValues(
criticalLow: mapOfValues['criticalLow'],
low: mapOfValues['low'],
normal: mapOfValues['normal'],
high: mapOfValues['high'],
criticalHigh: mapOfValues['criticalHigh'],
);
return [
ThresholdRange(
label: 'Critical Low',
value: adjustedValues["criticalLow"]!,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4),
actualValue: realCriticalLow),
ThresholdRange(
label: 'Low',
value: adjustedValues['low']!,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFFefc481),
actualValue: realReferenceLow),
ThresholdRange(
label: 'Normal',
value: adjustedValues['normal']!,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFF5dc36b)),
ThresholdRange(
label: 'High',
value: adjustedValues['high']!,
color: Color(0xffffffff),
lineColor: Color(0xFFefc481),
actualValue: realReferenceHigh),
ThresholdRange(
label: 'Critical High',
value: adjustedValues['criticalHigh']!,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4),
actualValue: realCriticalHigh),
];
}
Map<String, double> inferThresholds(
Map<String, LabOrderResult> mapOfPriority) {
double? parse(String? v) {
final parsed = double.tryParse(v ?? '');
return (parsed == null || parsed < 0) ? null : parsed;
}
// Parse inputs
double? criticalLow = parse(
mapOfPriority['LCL']?.resultValue ?? mapOfPriority['CL']?.resultValue);
double? low = parse(mapOfPriority['L']?.resultValue);
double? normal = parse(mapOfPriority['N']?.resultValue);
double? high = parse(mapOfPriority['H']?.resultValue);
double? criticalHigh = parse(
mapOfPriority['CH']?.resultValue ?? mapOfPriority['HCH']?.resultValue);
const step = 5.0;
List<double?> values = [criticalLow, low, normal, high, criticalHigh];
// Find the index of the known value (priority: central -> left -> right)
int anchorIndex = values.indexWhere((v) => v != null);
double anchorValue = values[anchorIndex] ?? 50;
// Infer all values around the anchor
List<double> inferred = List.generate(5, (i) {
double v = anchorValue + (i - anchorIndex) * step;
return v < 0 ? 0 : v;
});
var mapresult = {
'criticalLow': values[0]??-1,
'low': values[1]??-1,
'normal': values[2]??-1,
'high': values[3]??-1,
'criticalHigh': values[4]??-1,
};
print("the result is $mapresult");
return mapresult;
}
Map<String, double> adjustValues({
double? criticalLow,
double? low,
double? normal,
double? high,
double? criticalHigh,
double step = 10,
}) {
bool criticalLowHasValue = true;
bool lowHasValue = true;
bool normaHasValue = true;
bool highHasValue = true;
bool criticalHighHasValue = true;
if (criticalLow == null || criticalLow == -1) criticalLowHasValue = false;
if (low == null || low == -1) lowHasValue = false;
if (normal == null ||normal ==-1) normaHasValue = false;
if (high == null ||high ==-1) highHasValue = false;
if (criticalHigh == null ||criticalHigh ==-1) criticalHighHasValue = false;
print("the values arre $criticalLowHasValue $lowHasValue $normaHasValue $highHasValue $criticalHighHasValue");
if (!criticalLowHasValue) {
criticalLow = 0;
if (lowHasValue) {
low = low! - step;
} if (normaHasValue && (criticalLow!= 0 || criticalLow != -1)) {
low = normal! - step * 2;
} if (highHasValue && (criticalLow!= 0 || criticalLow != -1)) {
low = high! - step * 3;
} if (criticalHighHasValue && (criticalLow!= 0 || criticalLow != -1)) {
low = criticalHigh! - step * 4;
}
}
if (!lowHasValue) {
print("the low value is not set");
low = 0;
if (criticalLowHasValue && (low != 0 || low != -1)) {
low = criticalLow! + step;
} if (normaHasValue && (low != 0 || low != -1)) {
low = normal! - step;
} if (highHasValue && (low != 0 || low != -1)) {
low = high! - step * 2;
} if (criticalHighHasValue && (low != 0 || low != -1)) {
low = criticalHigh! - step * 3;
}
}
if (!normaHasValue) {
normal = 0;
if (criticalLowHasValue && (normal != 0 || normal != -1)) {
normal = criticalLow! + step * 2;
} if (lowHasValue) {
normal = low! + step;
} if (highHasValue) {
normal = high! - step;
} if (criticalHighHasValue) {
normal = criticalHigh! - step * 2;
}
}
if (!highHasValue) {
high = 0;
if (criticalLowHasValue) {
high = criticalLow! + step * 3;
} if (lowHasValue) {
high = low! + step * 2;
} if (normaHasValue) {
high = normal! + step;
} if (criticalHighHasValue) {
high = criticalHigh! - step;
}
}
if (!criticalHighHasValue) {
criticalHigh = 0;
if (criticalLowHasValue) {
criticalHigh = criticalLow! + step * 4;
} if (lowHasValue) {
criticalHigh = low! + step * 3;
} if (normaHasValue) {
criticalHigh = normal! + step * 2;
} if (highHasValue) {
criticalHigh = high! + step;
}
}
if(((criticalLow??0)<0) == true){
var mod = ((low ?? 0) + (normal??0) )/2;
criticalLow = mod;
}
if(((low??0)<0) == true){
var mod = ((criticalLow ?? 0) + (normal??0) )/2;
low = mod;
}
if(((normal??0)<0) == true){
var mod = ((low ?? 0) + (high??0) )/2;
normal = mod;
}
if(((high??0)<0) == true){
var mod = ((normal ?? 0) + (criticalHigh??0) )/2;
high = mod;
}
if(((criticalHigh??0)<0) == true){
criticalHigh = (high??0)+step;
}
Map<String, double?> values = {
'criticalLow':criticalLow ,
'low': low,
'normal': normal ,
'high': high ,
'criticalHigh': criticalHigh,
};
print("thee adjusted values `are $values");
// // Find the first known value
// int firstKnownIndex = values.values.toList().indexWhere((v) => v != null);
//
// if (firstKnownIndex == -1) {
// // No values at all, start from 0
// values = {
// 'criticalLow': 0,
// 'low': step,
// 'normal': step * 2,
// 'high': step * 3,
// 'criticalHigh': step * 4,
// };
// } else {
// // Fill backward
// for (int i = firstKnownIndex - 1; i >= 0; i--) {
// values[values.keys.elementAt(i)] =
// (values.values.elementAt(i + 1)! - step).clamp(0, double.infinity);
// }
//
// // Fill forward
// for (int i = firstKnownIndex + 1; i < values.length; i++) {
// values[values.keys.elementAt(i)] =
// values.values.elementAt(i - 1)! + step;
// }
// }
//
// // Ensure strictly increasing sequence
// double prev = -double.infinity;
// values.forEach((key, val) {
// if (val! <= prev) {
// values[key] = prev + step;
// }
// prev = values[key]!;
// });
return values.map((k, v) => MapEntry(k, v!));
}
void adjustLabOrderResults(List<LabOrderResult> results) {
const priorityOrder = ['LCL', 'CL', 'L', 'N', 'H', 'CH', 'HCH'];
// Sort results by priority order
results.sort((a, b) => priorityOrder
.indexOf(a.calculatedResultFlag ?? '')
.compareTo(priorityOrder.indexOf(b.calculatedResultFlag ?? '')));
// Extract values
List<double?> values = results.map((r) {
double? v = double.tryParse(r.resultValue ?? '');
return (v != null && v >= 0) ? v : null;
}).toList();
// Find first non-null index
int? firstIdx = values.indexWhere((v) => v != null);
if (firstIdx == -1) return; // All values missing
// Fill before first known value (decreasing with step 5, not less than 0)
for (int i = firstIdx - 1; i >= 0; i--) {
values[i] = (values[i + 1]! - 5).clamp(0, double.infinity);
}
// Fill after first known value (increasing with step 5)
for (int i = firstIdx + 1; i < values.length; i++) {
if (values[i] == null) {
values[i] = values[i - 1]! + 5;
}
}
// For gaps in the middle, interpolate
for (int i = 0; i < values.length; i++) {
if (values[i] == null) {
// find next non-null
int j = i + 1;
while (j < values.length && values[j] == null) j++;
if (j < values.length) {
double start = values[i - 1]!;
double end = values[j]!;
double step = (end - start) / (j - i + 1);
for (int k = i; k < j; k++) {
values[k] = start + step * (k - i + 1);
}
}
}
}
// Update results list directly
for (int i = 0; i < results.length; i++) {
results[i].resultValue = values[i]?.toStringAsFixed(2) ?? '0.00';
}
}
double transformValueInRange(double inputValue, String flag) {
// Define range boundaries
double rangeStart, rangeEnd;
switch (flag) {
case 'CL':
rangeStart = 0.0;
rangeEnd = 19.0;
break;
case 'L':
rangeStart = 20.0;
rangeEnd = 39.0;
break;
case 'N':
rangeStart = 40.0;
rangeEnd = 59.0;
break;
case 'H':
rangeStart = 60.0;
rangeEnd = 79.0;
break;
case 'CH':
rangeStart = 80.0;
rangeEnd = 100.0;
break;
default:
throw ArgumentError('Invalid flag: $flag');
}
// Clamp input value to 0-100 and map it to the range bounds
final clampedValue = inputValue.clamp(0.0, 100.0);
final normalizedValue = clampedValue / 100.0; // Normalize input to 0-1
// Map the normalized value to the target range bounds
final transformedValue = rangeStart + (normalizedValue * (rangeEnd - rangeStart));
return transformedValue;
}
List<ThresholdRange> getThresholdValue() {
return [
ThresholdRange(
label: 'criticalLow',
value: 0,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4)),
ThresholdRange(
label: 'low',
value: 20,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'normal',
value: 40,
color: Color(0xFFf2fbf5),
lineColor: Color(0xFF5dc36b)),
ThresholdRange(
label: 'high',
value: 60,
color: Color(0xffffffff),
lineColor: Color(0xFFeecd94)),
ThresholdRange(
label: 'criticalHigh',
value: 80,
color: Color(0xffffffff),
lineColor: Color(0xFFe9a2a4)),
];
}
}

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:hmg_patient_app/core/enum/filter_type.dart';
import 'package:hmg_patient_app/core/enum/viewstate.dart';
@ -13,6 +14,7 @@ import 'package:hmg_patient_app/models/Authentication/authenticated_user.dart';
import 'package:hmg_patient_app/uitl/app_toast.dart';
import 'package:hmg_patient_app/uitl/date_uitl.dart';
import 'package:hmg_patient_app/widgets/charts/app_time_series_chart.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
@ -24,7 +26,11 @@ class LabsViewModel extends BaseViewModel {
LabsService _labsService = locator<LabsService>();
List<LabOrderResult> get labOrdersResultsList => _labsService.labOrdersResultsList;
List<TimeSeriesSales2> timeSeries = [];
List<DataPoint> threePointGraphValue = [];
List<DataPoint> completeeGraphValues = [];
List<ThresholdRange> threshold = [];
List months = ['Jan','Feb','Mar','April','May','Jun','July','Aug','Sep','Oct','Nov','Dec'];
String get labReportPDF => _labsService.labReportPDF;
@ -100,24 +106,53 @@ class LabsViewModel extends BaseViewModel {
} else {
_labsService.labResultList.forEach((element) {
List<LabResultList> patientLabOrdersClinic = labResultLists.where((elementClinic) => elementClinic.filterName == element.testCode).toList();
element.percentage =
getPercentage(element.calculatedResultFlag ?? ResultFlag.N);
if (patientLabOrdersClinic.length != 0) {
// var value = labResultLists[labResultLists.indexOf(patientLabOrdersClinic[0])]
// .patientLabResultList!
// .where((e) {
// return e.sampleCollectedOn == element.sampleCollectedOn && e.resultValue == element.resultValue;
// })
// .toList();
// if (value.isEmpty)
labResultLists[labResultLists.indexOf(patientLabOrdersClinic[0])].patientLabResultList!.add(element);
labResultLists[labResultLists.indexOf(patientLabOrdersClinic[0])].patientLabResultList!.add(element);
} else {
labResultLists.add(LabResultList(filterName: element.testCode, lab: element));
labResultLists.add(LabResultList(
filterName: element.testCode,
description: element.packageShortDescription,
lab: element));
}
});
setState(ViewState.Idle);
}
}
double getPercentage(ResultFlag flag) {
double percentage = 0.0;
final division = 0.2;
switch (flag) {
case ResultFlag.N: //4
percentage = division * 1.75;
break;
case ResultFlag.H: //5
percentage = division * 2.8;
break;
case ResultFlag.L: //3
percentage = division * .7;
break;
case ResultFlag.CL: //2
case ResultFlag.LCL: //
percentage = division * .07;
break;
case ResultFlag.HCH: //7
case ResultFlag.CH: //6
percentage = division * 3.4;
break;
case ResultFlag.IRR:
percentage = 0;
break;
}
return percentage * 100;
}
getPatientLabOrdersResults({PatientLabOrders? patientLabOrder, String? procedure, bool? isVidaPlus}) async {
setState(ViewState.Busy);
await _labsService.getPatientLabOrdersResults(
@ -129,6 +164,7 @@ class LabsViewModel extends BaseViewModel {
error = _labsService.error!;
setState(ViewState.Error);
} else {
/// commented out based on PAP-304
// bool isShouldClear = false;
// if (_labsService.labOrdersResultsList.length == 1) {
@ -141,17 +177,69 @@ class LabsViewModel extends BaseViewModel {
//
// _labsService.labOrdersResultsList.clear();
_labsService.labOrdersResultsList.forEach((element) {
var sortedResponse = _labsService.sortByFlagAndValue(_labsService.labOrdersResultsList);
print("the sorted list is ${sortedResponse}");
var recentThree = _labsService.getMostRecentThree(sortedResponse);
print("the recentThree list is ${recentThree}");
var mappedToThresholdValue = _labsService.getThresholdValue();
// var mappedToThresholdValue = _labsService.buildThresholdList(recentThree);
// _labsService.adjustLabOrderResults(sortedResponse);
print("the recentThree list is ${mappedToThresholdValue}");
threshold = mappedToThresholdValue;
List months = ['Jan','Feb','Mar','April','May','Jun','July','Aug','Sep','Oct','Nov','Dec'];
double counter = 1;
recentThree.reversed.forEach((element) {
try {
timeSeries.add(new TimeSeriesSales2(DateUtil.convertStringToDate(element.verifiedOnDateTime!), double.parse(element.resultValue!)));
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
// threePointGraphValue.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
threePointGraphValue.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), actualValue: element.resultValue!,label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
counter++;
} catch (e) {}
});
threePointGraphValue.last.label = "thisResult";
threePointGraphValue.last.isStringResource = true;
setState(ViewState.Idle);
}
}
sendLabReportEmail({PatientLabOrders? patientLabOrder, String? mes, AuthenticatedUser? userObj, required bool isVidaPlus, bool isDownload = false, int languageID = 1}) async {
await _labsService.sendLabReportEmail(patientLabOrder: patientLabOrder, userObj: userObj, isVidaPlus: isVidaPlus, isDownload: isDownload, languageID: languageID);
/// This function is used to create a full graph data points for the lab results.
/// @param onComplete: A callback function that is called when the data points are created.
createFullGraphDatPoints(
VoidCallback onComplete
){
completeeGraphValues.clear();
setState(ViewState.Busy);
double counter = 1;
threshold = _labsService.getThresholdValue();
_labsService.labOrdersResultsList.reversed.forEach((element) {
try {
print("the mapping is being done");
var dateTime = DateUtil.convertStringToDate(element.verifiedOnDateTime!);
// completeeGraphValues.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime));
completeeGraphValues.add(DataPoint( labelValue: counter,value : _labsService.transformValueInRange(double.parse(element.resultValue!), element.calculatedResultFlag??""), label: "${months[dateTime.month-1]} ${dateTime.year}", date: dateTime,actualValue: element.resultValue!));
} catch (e) {
print("the mapping is having exception $e");
}
++counter;
});
print("the label values are $counter");
print("the completeeGraphValues length are ${completeeGraphValues.length}");
print("the completeeGraphValues last value of label ias ${completeeGraphValues.last.labelValue}");
completeeGraphValues.last.label = "thisResult";
completeeGraphValues.last.isStringResource = true;
setState(ViewState.Idle);
onComplete();
}
sendLabReportEmail({PatientLabOrders? patientLabOrder, String? mes, AuthenticatedUser? userObj, required bool isVidaPlus, bool isDownload = false}) async {
setState(ViewState.Busy);
await _labsService.sendLabReportEmail(patientLabOrder: patientLabOrder, userObj: userObj, isVidaPlus: isVidaPlus, isDownload: isDownload);
setState(ViewState.Idle);
if (_labsService.hasError) {
error = _labsService.error!;
} else {
@ -173,7 +261,8 @@ class LabsViewModel extends BaseViewModel {
Future<String> _createFileFromString(String encodedStr, String ext) async {
Uint8List bytes = base64.decode(encodedStr);
String dir = (await getApplicationDocumentsDirectory()).path;
File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext);
File file = File(
"$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext);
await file.writeAsBytes(bytes);
return file.path;
}

@ -5,6 +5,7 @@ import 'package:hmg_patient_app/core/service/AlHabibMedicalService/customer_addr
import 'package:hmg_patient_app/services/pharmacy_services/pharmacyAddress_service.dart';
import 'package:hmg_patient_app/uitl/utils.dart';
import 'package:geocoding/geocoding.dart';
// import 'package:google_maps_place_picker_mb/google_maps_place_picker.dart';
import '../../../locator.dart';
import '../base_view_model.dart';

@ -83,7 +83,7 @@ class _ExceptionBottomSheetState extends State<ExceptionBottomSheet> {
borderColor: CustomColors.bgRedLightColor,
textColor: CustomColors.bgRedColor,
icon: "assets/images/svg/cancel.svg",
iconColor: CustomColors.bgRedColor,
iconColor: CustomColors.bgRedColor,
),
),
SizedBox(width: MediaQuery.of(context).size.width * 0.05),

@ -26,6 +26,7 @@ import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmaps;
import 'package:google_maps_place_picker_mb_v2/google_maps_place_picker.dart';
import 'package:huawei_hmsavailability/huawei_hmsavailability.dart';
import 'package:provider/provider.dart';

@ -21,6 +21,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as gmaps;
import 'package:google_maps_place_picker_mb_v2/google_maps_place_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'cmc_location_page.dart';

@ -24,6 +24,7 @@ import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_place_picker_mb_v2/google_maps_place_picker.dart';
import 'package:huawei_hmsavailability/huawei_hmsavailability.dart';
import 'package:provider/provider.dart';

@ -14,6 +14,7 @@ import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
import 'package:hmg_patient_app/widgets/transitions/fade_page.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_place_picker_mb_v2/google_maps_place_picker.dart';
import 'package:provider/provider.dart';
class NewHomeHealthCareStepOnePage extends StatefulWidget {

@ -23,6 +23,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_place_picker_mb_v2/google_maps_place_picker.dart';
import 'location_page.dart';

@ -42,8 +42,6 @@ import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
import 'package:hmg_patient_app/widgets/others/not_auh_page.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
// import 'package:flutter_app_icon_badge/flutter_app_icon_badge.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart';
@ -763,7 +761,7 @@ class _LandingPageState extends State<LandingPage> with WidgetsBindingObserver {
),
),
).then((value) {
sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, value);
sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, value ?? true);
});
}

@ -36,7 +36,6 @@ class _ForgotPassword extends State<ForgotPassword> {
refreshExpired: TurnstileRefreshExpired.manual,
language: 'en',
retryAutomatically: false,
);
String? _token;
@ -87,22 +86,23 @@ class _ForgotPassword extends State<ForgotPassword> {
});
}),
const SizedBox(height: 12.0),
CloudflareTurnstile(
siteKey: '0x4AAAAAAAkmbvEy63pcxBBt',
baseUrl: BASE_URL,
options: _options,
controller: _controller,
onTokenReceived: (token) {
setState(() {
_token = token;
});
},
onTokenExpired: () {},
// mode: TurnstileMode.managed,
onError: (error) {
return Text(error.message);
},
),
// CloudFlareTurnstile(
// siteKey: '0x4AAAAAAAkmbvEy63pcxBBt',
// baseUrl: BASE_URL,
// options: _options,
// controller: _controller,
// onTokenRecived: (token) {
// setState(() {
// print("Cloudflare token: $token");
// _token = token;
// });
// },
// onTokenExpired: () {},
// mode: TurnstileMode.managed,
// errorBuilder: (context, error) {
// return Text(error.message);
// },
// ),
const SizedBox(height: 48.0),
],
),

@ -328,7 +328,7 @@ class _RegisterNew extends State<RegisterNew> {
(!Utils.validateUaeNationalId(nationalIDorFile.text) && selectedCountry.countryCode == '971')) {
context.showBottomSheet(
child: ExceptionBottomSheet(
message: "Please enter correct national id",
message: TranslationBase.of(context).incorrectNationalId,
showCancel: false,
onOkPressed: () {
Navigator.of(context).pop();
@ -657,6 +657,7 @@ class _RegisterNew extends State<RegisterNew> {
setState(() {
isSkipRegistration = true;
});
Navigator.of(context).pop();
sendActivationCode(type, "", null, isSkipRegistration);
},
),
@ -768,7 +769,7 @@ class _RegisterNew extends State<RegisterNew> {
var req = getCommonRequest(type: type);
req.logInTokenID = "";
var request = CheckPatientAuthenticationReq.fromJson(req.toJson());
GifLoaderDialogUtils.showMyDialog(context);
authService.checkPatientAuthentication(request).then((value) async {
if (value['isSMSSent']) {
sharedPref.setString(LOGIN_TOKEN_ID, value['LogInTokenID']);

@ -157,12 +157,15 @@ class _SavedLogin extends State<SavedLogin> {
fontWeight: FontWeight.w600,
),
),
Text(
widget.savedLoginData.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(widget.savedLoginData.createdOn!), "d MMMM, y 'at' HH:mm") : '--',
style: context.dynamicTextStyle(
fontSize: 16,
color: Color(0xFF2E3039),
fontWeight: FontWeight.w600,
Directionality(
textDirection: TextDirection.ltr,
child: Text(
widget.savedLoginData.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(widget.savedLoginData.createdOn!), "d MMMM, y 'at' HH:mm") : '--',
style: context.dynamicTextStyle(
fontSize: 16,
color: Color(0xFF2E3039),
fontWeight: FontWeight.w600,
),
),
),
Container(
@ -295,7 +298,8 @@ class _SavedLogin extends State<SavedLogin> {
icon: "assets/images/svg/whatsapp.svg",
),
),
], myFocusNode: myFocusNode,
],
myFocusNode: myFocusNode,
),
),
);

@ -261,7 +261,8 @@ class _WelcomeLogin extends State<WelcomeLogin> {
icon: "assets/images/svg/whatsapp.svg",
),
),
], myFocusNode: myFocusNode,
],
myFocusNode: myFocusNode,
),
),
);
@ -273,7 +274,7 @@ class _WelcomeLogin extends State<WelcomeLogin> {
} else {
context.showBottomSheet(
child: ExceptionBottomSheet(
message: TranslationBase.of(context).pleaseEnterNationalIdOrFileNo,
message: TranslationBase.of(context).pleaseEnterNationalIdOrFileNo,
showCancel: false,
onOkPressed: () {
Navigator.of(context).pop();

@ -1,22 +1,13 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:hmg_patient_app/core/model/labs/patient_lab_orders.dart';
import 'package:hmg_patient_app/core/viewModels/medical/labs_view_model.dart';
import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
import 'package:hmg_patient_app/pages/base/base_view.dart';
import 'package:hmg_patient_app/uitl/app_toast.dart';
import 'package:hmg_patient_app/uitl/gif_loader_dialog_utils.dart';
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:hmg_patient_app/widgets/buttons/defaultButton.dart';
import 'package:hmg_patient_app/uitl/utils.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/laboratory_result_widget.dart';
import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
class LaboratoryResultPage extends StatefulWidget {
@ -52,39 +43,27 @@ class _LaboratoryResultPageState extends State<LaboratoryResultPage> {
body: SingleChildScrollView(
child: Column(
children: [
ListView.builder(
addAutomaticKeepAlives: true,
physics: BouncingScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.only(bottom: 12),
itemBuilder: (context, index) => LaboratoryResultWidget(
onTap: () async {
GifLoaderDialogUtils.showMyDialog(context);
await model.sendLabReportEmail(
LaboratoryResultWidget(
onTap: () async {
await model.sendLabReportEmail(
patientLabOrder: widget.patientLabOrders,
mes: TranslationBase.of(context).sendSuc,
userObj: projectViewModel.user,
languageID: projectViewModel.isArabic ? 1 : 2,
isDownload: true,
isVidaPlus: Utils.isVidaPlusProject(
projectViewModel,
int.parse(widget.patientLabOrders!.projectID!),
),
);
GifLoaderDialogUtils.hideDialog(context);
},
billNo: widget.patientLabOrders!.invoiceNo,
// details: model.patientLabSpecialResult[index].resultDataHTML,
details: model.patientLabSpecialResult.isEmpty ? null : getSpecialResults(model),
orderNo: widget.patientLabOrders!.orderNo,
patientLabOrder: widget.patientLabOrders,
projectName: widget.patientLabOrders!.projectName,
),
itemCount: 1,
isDownload: true);
},
showConfirmMessageDialog: false,
billNo: widget.patientLabOrders!.invoiceNo,
// details: model.patientLabSpecialResult[index].resultDataHTML,
details: model.patientLabSpecialResult.isEmpty
? null
: getSpecialResults(model),
orderNo: widget.patientLabOrders!.orderNo,
patientLabOrder: widget.patientLabOrders,
),
SizedBox(
height: 120.0,
)
],
),
),

@ -70,7 +70,7 @@ class AppToast {
child: toast,
gravity: ToastGravity.TOP,
toastDuration: Duration(seconds: timeInSeconds),
positionedToastBuilder: (context, child, gravity) {
positionedToastBuilder: (context, child, _) {
return Positioned(top: 50, left: 10, right: 10, child: child);
});
}

@ -3517,6 +3517,37 @@ class TranslationBase {
String get pleaseEnterNationalIdOrFileNo => localizedValues["pleaseEnterNationalIdOrFileNo"][locale.languageCode];
String get readMore => localizedValues["readMore"][locale.languageCode];
String get showLess => localizedValues["showLess"][locale.languageCode];
String get change => localizedValues["change"][locale.languageCode];
String get referenceRange => localizedValues["referenceRange"][locale.languageCode];
String get criticalHigh => localizedValues["criticalHigh"][locale.languageCode];
String get criticalLow => localizedValues["criticalLow"][locale.languageCode];
String get high => localizedValues["high"][locale.languageCode];
String get thisResult => localizedValues["thisResult"][locale.languageCode];
String get seeAllGraphValues => localizedValues["seeAllGraphValues"][locale.languageCode];
String get normal => localizedValues["normal"][locale.languageCode];
String get low => localizedValues["low"][locale.languageCode];
String getTranslation(String label) {
switch (label) {
case "thisResult":
return thisResult;
case "high":
return high;
case "criticalLow":
return criticalLow;
case "criticalHigh":
return criticalHigh;
case "low":
return low;
case "normal":
return normal;
default:
return "";
}
}
}
class TranslationBaseDelegate extends LocalizationsDelegate<TranslationBase> {

@ -19,6 +19,7 @@ class _GifLoaderContainerState extends State<GifLoaderContainer> with TickerProv
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
// controller = GifController(autoPlay: true, onFinish: () {}, loop: true);
controller = GifController();
// controller.repeat(min: 0, max: 11, period: Duration(milliseconds: 750), reverse: true);
} catch (ex) {}

@ -12,8 +12,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'LineChartCurved.dart';
import '../../../../core/enum/viewstate.dart';
import '../../../../uitl/translations_delegate_base.dart';
import '../../../Loader/gif_loader_container.dart';
import 'full_screen_lab_result_graph.dart';
import 'lab_result_chart_and_detials.dart';
import 'lab_result_graph.dart';
class FlowChartPage extends StatelessWidget {
final PatientLabOrders? patientLabOrder;
@ -27,65 +31,126 @@ class FlowChartPage extends StatelessWidget {
Widget build(BuildContext context) {
projectViewModel = Provider.of(context);
return BaseView<LabsViewModel>(
onModelReady: (model) =>
model.getPatientLabOrdersResults(patientLabOrder: patientLabOrder, procedure: filterName, isVidaPlus: Utils.isVidaPlusProject(projectViewModel!, int.parse(patientLabOrder!.projectID!))),
builder: (context, model, w) => AppScaffold(
isShowAppBar: true,
appBarTitle: filterName!,
baseViewModel: model,
showNewAppBar: true,
showNewAppBarTitle: true,
backgroundColor: CustomColors.appBackgroudGrey2Color,
body: SingleChildScrollView(
child: model.labOrdersResultsList.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: double.infinity,
padding: EdgeInsets.only(left: 17, top: 12, right: 13, bottom: 12),
margin: EdgeInsets.only(left: 21, right: 21, top: 21),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
//spreadRadius: 5,
blurRadius: 27,
offset: Offset(0, -3),
onModelReady: (model) => model.getPatientLabOrdersResults(
patientLabOrder: patientLabOrder,
procedure: filterName,
isVidaPlus: Utils.isVidaPlusProject(
projectViewModel!, int.parse(patientLabOrder!.projectID!))),
builder: (context, model, w) => SizedBox(
height: MediaQuery.sizeOf(context).height * .75,
child: (model.state == ViewState.Busy)
? Material(
color: Colors.grey.withOpacity(0.6),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20))),
child: Center(
child: Container(
child: GifLoaderContainer(),
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).size.height * 0.09)),
),
)
: SingleChildScrollView(
child: model.labOrdersResultsList.isNotEmpty
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
top: 16.0, right: 16.0, bottom: 10),
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Align(
alignment: Alignment.centerRight,
child: Icon(
Icons.close,
color: Colors.black,
size: 28,
),
),
),
),
Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 17, top: 12, right: 13, bottom: 12),
margin:
EdgeInsets.only(left: 21, right: 21, top: 21),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
//spreadRadius: 5,
blurRadius: 27,
offset: Offset(0, -3),
),
],
),
// child: LineChartCurved(
// title: filterName,
// labResult: model.labOrdersResultsList,
// ),
// child: ShowChart(
// title: filterName!,
// timeSeries: model.timeSeries,
// indexes: model.timeSeries.length ~/ 5.5 ?? 0,
// ),
child: Column(
children: [
SizedBox(
height: MediaQuery.sizeOf(context).height / 4,
child: DynamicResultChart(
dataPoints: model.threePointGraphValue,
thresholds: model.threshold,
width: MediaQuery.sizeOf(context).width - 77,
scrollDirection: Axis.horizontal,
height: MediaQuery.sizeOf(context).height / 4,
),
),
InkWell(
onTap: () {
model.createFullGraphDatPoints(() {
Navigator.push(context,
MaterialPageRoute(builder: (_) {
return FullScreenGraph(
completeeGraphValues: model.completeeGraphValues,
threshold: model.threshold,
);
}));
});
},
child: Text(TranslationBase.of(context)
.seeAllGraphValues),
)
],
),
),
LabResultChartAndDetails(
name: filterName!,
// labResult: model.labOrdersResultsList,
labResult: model.labOrdersResultsList.reversed.toList(),
),
],
)
: Center(
child: Container(
padding: EdgeInsets.only(
top: MediaQuery.of(context).size.height * 0.32),
child: Center(
child: getNoDataWidget(context),
),
),
),
// child: LineChartCurved(
// title: filterName,
// labResult: model.labOrdersResultsList,
// ),
child: ShowChart(
title: filterName!,
timeSeries: model.timeSeries,
indexes: model.timeSeries.length ~/ 5.5 ?? 0,
),
),
LabResultChartAndDetails(
name: filterName!,
labResult: model.labOrdersResultsList,
),
],
)
: Center(
child: Container(
padding: EdgeInsets.only(top: MediaQuery.of(context).size.height * 0.32),
child: Center(
child: getNoDataWidget(context),
),
),
),
),
),
),
);
}

@ -3,7 +3,6 @@ import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
import 'package:hmg_patient_app/uitl/date_uitl.dart';
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:hmg_patient_app/uitl/utils.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -29,6 +28,7 @@ class _VitalSignDetailsWidgetState extends State<LabResultDetailsWidget> {
columnWidths: {
0: FlexColumnWidth(2),
1: FlexColumnWidth(1),
2: FlexColumnWidth(1),
},
children: fullData(widget.labResult!, context),
);
@ -41,6 +41,7 @@ class _VitalSignDetailsWidgetState extends State<LabResultDetailsWidget> {
children: [
Utils.tableColumnTitle(TranslationBase.of(context).date),
Utils.tableColumnTitle(TranslationBase.of(context).labResults),
Utils.tableColumnTitle(TranslationBase.of(context).change),
],
),
);
@ -51,9 +52,34 @@ class _VitalSignDetailsWidgetState extends State<LabResultDetailsWidget> {
children: [
Utils.tableColumnValue(DateUtil.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(labOrderResultList![i].verifiedOnDateTime!)), isLast: i == (labOrderResultList.length - 1), mProjectViewModel: projectViewModel),
Utils.tableColumnValue(labOrderResultList[i].resultValue!, isLast: i == (labOrderResultList.length - 1), mProjectViewModel: projectViewModel),
changeResult(labOrderResultList[i].changeResult ?? '8%')
],
),
);
return tableRow;
}
Widget changeResult(String changeValue) => Padding(
padding: const EdgeInsets.all(4.0),
child: Material(
color: (changeValue.contains("-")) ? Colors.black : Color(0xffcecbcb),
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
child: Center(
child: Text(
changeValue,
style: TextStyle(
color: (changeValue.contains("-"))
? Colors.white
: Colors.black,
fontSize: 11,
letterSpacing: -.64,
fontWeight: FontWeight.w400),
),
),
),
),
);
}

@ -0,0 +1,59 @@
import 'package:hmg_patient_app/core/viewModels/medical/labs_view_model.dart';
import 'package:hmg_patient_app/pages/base/base_view.dart';
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/lab_result_graph.dart';
import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/material.dart';
class FullScreenGraph extends StatelessWidget {
final List<DataPoint> completeeGraphValues ;
final List<ThresholdRange> threshold ;
const FullScreenGraph({super.key, required this.completeeGraphValues, required this.threshold});
@override
Widget build(BuildContext context) {
return AppScaffold(
isShowAppBar: true,
appBarTitle: TranslationBase.of(context).labResult,
showNewAppBar: true,
showNewAppBarTitle: true,
backgroundColor: Color(0xffF8F8F8),
body: RotatedBox(
quarterTurns: 1,
child: SizedBox(
// width: MediaQuery.sizeOf(context).height,
height: MediaQuery.sizeOf(context).width,
child: Material(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18),
child: DynamicResultChart(
dataPoints: completeeGraphValues,
thresholds: threshold,
// width:((completeeGraphValues.length <=2)? MediaQuery.sizeOf(context).height : (MediaQuery.sizeOf(context).height* (
// completeeGraphValues.length <= 3
// ? 1
// : (completeeGraphValues.length/3))
// ) )-77,
width: MediaQuery.sizeOf(context).height-100,
maxX: completeeGraphValues.length.toDouble(),
scrollDirection: Axis.horizontal,
height: MediaQuery.sizeOf(context).width,
isFullScreenGraph: true,
),
),
),
),
),
);
}
// double getMax(List<DataPoint> dataPoints) {
// double max = double.negativeInfinity;
// for (var point in dataPoints) {
// if (point.value > max) {
// max = point.y;
// }
// }
// return max;
// }
}

@ -0,0 +1,276 @@
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:intl/intl.dart';
class DynamicResultChart extends StatelessWidget {
final List<DataPoint> dataPoints;
final List<ThresholdRange> thresholds;
final double? width;
final double height;
final double? maxX;
final Axis scrollDirection;
final bool isFullScreenGraph;
const DynamicResultChart(
{super.key, required this.dataPoints, required this.thresholds, required this.width, required this.scrollDirection, required this.height, this.maxX, this.isFullScreenGraph = false});
@override
Widget build(BuildContext context) {
var minY = 0.0;
var maxY = 0.0;
if (thresholds.isNotEmpty) {
print("the thresholds are $thresholds");
minY = thresholds.first.value;
maxY = thresholds.last.value + 1;
} else {
return SizedBox.shrink();
}
final spots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList();
final widthPerPoint = 30.0; // Customize as needed
final chartWidth = (dataPoints.length + 1) * widthPerPoint;
return Material(
color: Colors.white,
child: SizedBox(
width: width,
height: height,
child: Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8),
child: LineChart(
LineChartData(
minY: minY,
maxY: maxY,
minX: dataPoints.first.labelValue - 1,
maxX: maxX,
lineTouchData: LineTouchData(touchTooltipData: LineTouchTooltipData(getTooltipItems: (touchedSpots) {
if (touchedSpots.isEmpty) return [];
// Only show tooltip for the first touched spot, hide others
return touchedSpots.map((spot) {
if (spot == touchedSpots.first) {
final dataPoint = dataPoints[spot.x.toInt()];
if (dataPoint.isStringResource) {
return LineTooltipItem(
'${double.parse(dataPoint.actualValue).toStringAsFixed(2)}',
const TextStyle(color: Colors.white),
);
}
return LineTooltipItem(
// '${dataPoint.label} ${spot.y.toStringAsFixed(2)}',
'${dataPoint.label} ${double.parse(dataPoint.actualValue).toStringAsFixed(2)}',
const TextStyle(color: Colors.white),
);
}
return null; // hides the rest
}).toList();
})),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 77,
interval: .1, // Let fl_chart handle it
getTitlesWidget: (value, _) {
// print("the value is ======== ${value}");
// Compare with 2-decimal precision to avoid close duplicates
final matchingThreshold = thresholds.firstWhere(
(t) => t.value.toStringAsFixed(1) == value.toStringAsFixed(1),
orElse: () => ThresholdRange(label: '', value: 0, color: Colors.transparent, lineColor: Colors.transparent),
);
var actualValue = (matchingThreshold.actualValue != null)
? "${TranslationBase.of(context).getTranslation(matchingThreshold.label)} (${matchingThreshold.actualValue})"
: '${TranslationBase.of(context).getTranslation(matchingThreshold.label)}';
if (matchingThreshold.label.isNotEmpty) {
return Text(
actualValue,
style: const TextStyle(fontSize: 10),
);
}
return const SizedBox.shrink();
},
),
),
bottomTitles: AxisTitles(
axisNameSize: 60,
sideTitles: SideTitles(
showTitles: !isFullScreenGraph,
reservedSize: 50,
getTitlesWidget: (value, _) {
if (value.toInt() >= 0 && value.toInt() < dataPoints.length) {
var label = dataPoints[value.toInt()].label;
if (dataPoints[value.toInt()].isStringResource) {
label = TranslationBase.of(context).getTranslation(dataPoints[value.toInt()].label);
}
return Padding(
padding: EdgeInsetsDirectional.only(
top: 8.0,
),
child: Text(
label,
style: const TextStyle(fontSize: 12),
),
);
}
return const SizedBox.shrink();
},
interval: 1, // ensures 1:1 mapping with spots
),
),
topTitles: AxisTitles(),
rightTitles: AxisTitles(),
),
borderData: FlBorderData(show: false),
lineBarsData: _buildColoredLineSegments(dataPoints, thresholds),
gridData: FlGridData(show: false),
extraLinesData: ExtraLinesData(
horizontalLines: thresholds.map((t) {
return HorizontalLine(
y: t.value,
color: t.lineColor,
strokeWidth: 2,
// label: HorizontalLineLabel(
// show: true,
// alignment: Alignment.centerLeft,
// style: const TextStyle(fontSize: 10),
// labelResolver: (_) => '${t.label} (${t.value.toStringAsFixed(2)})',
// ),
);
}).toList(),
),
rangeAnnotations: RangeAnnotations(
horizontalRangeAnnotations: _buildRangeShades(thresholds),
),
),
),
),
));
}
List<LineChartBarData> _buildColoredLineSegments(List<DataPoint> dataPoints, List<ThresholdRange> thresholds) {
List<LineChartBarData> segments = [];
Color getColor(double value) {
for (int i = thresholds.length - 1; i >= 0; i--) {
if (value >= thresholds[i].value) {
return thresholds[i].lineColor;
}
}
return Colors.grey;
}
for (int i = 0; i < dataPoints.length - 1; i++) {
final dp1 = dataPoints[i];
final dp2 = dataPoints[i + 1];
final spot1 = FlSpot(i.toDouble(), dp1.value);
final spot2 = FlSpot((i + 1).toDouble(), dp2.value);
final color1 = getColor(dp1.value);
final color2 = getColor(dp2.value);
segments.add(LineChartBarData(
spots: [spot1, spot2],
isCurved: false,
barWidth: 2,
gradient: LinearGradient(
colors: [color1, color2],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
dotData: FlDotData(show: false), // We'll handle dots separately
));
}
// Add dot markers separately so they are on top
final List<FlSpot> allSpots = dataPoints.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList();
print("the spots are $allSpots");
var data = [
...segments,
LineChartBarData(
spots: allSpots,
isCurved: false,
barWidth: 0, // invisible line
dotData: FlDotData(
show: true,
getDotPainter: (spot, _, __, index) {
final value = spot.y;
final color = getColor(value);
return FlDotCirclePainter(
radius: 4,
color: color,
strokeWidth: 1,
strokeColor: Colors.white,
);
},
),
)
];
// var max = LineChartHelper.calculateMaxAxisValues(data).maxX;
// print("the maxX is the -------> $max");
return data;
}
List<HorizontalRangeAnnotation> _buildRangeShades(List<ThresholdRange> thresholds) {
List<HorizontalRangeAnnotation> ranges = [];
for (int i = 0; i < thresholds.length - 1; i++) {
ranges.add(HorizontalRangeAnnotation(
y1: thresholds[i].value,
y2: thresholds[i + 1].value,
color: thresholds[i].color,
));
}
return ranges;
}
}
final List<DataPoint> sampleData = [
DataPoint(value: 1.4, label: 'Jan 2024', date: DateTime(2024, 1, 1)),
DataPoint(value: 3.6, label: 'Feb 2024', date: DateTime(2024, 2, 1)),
DataPoint(value: 1.96, label: 'This result', date: DateTime(2024, 3, 1)),
];
final List<ThresholdRange> thresholdLevels = [
ThresholdRange(label: 'Critical Low', value: 1.4, color: Color(0xfff6e9e9), lineColor: Color(0xFFe9a2a4)),
ThresholdRange(label: 'Low', value: 3.6, color: Color(0xFFf2fbf5), lineColor: Color(0xFFeecd94)),
ThresholdRange(label: 'Normal', value: 5.96, color: Color(0xFFf2fbf5), lineColor: Color(0xFF5dc36b)),
ThresholdRange(label: 'High', value: 7.15, color: Color(0xfff6e9e9), lineColor: Color(0xFFeecd94)),
ThresholdRange(label: 'Critical High', value: 10.15, color: Color(0xfff6e9e9), lineColor: Color(0xFFe9a2a4)),
];
class DataPoint {
final double value;
double labelValue;
String label;
String actualValue;
final DateTime date;
bool isStringResource;
DataPoint({required this.value, required this.label, required this.date, this.isStringResource = false, this.labelValue = 0.0, this.actualValue = ""});
}
class ThresholdRange {
final String label;
final double value;
final Color color;
final Color lineColor;
final String? actualValue;
ThresholdRange({required this.label, required this.value, required this.color, required this.lineColor, this.actualValue});
@override
String toString() {
return 'ThresholdRange(label: $label, value: $value, color: ${color.value.toRadixString(16)}, lineColor: ${lineColor.value.toRadixString(16)})';
}
}

@ -5,16 +5,13 @@ import 'package:hmg_patient_app/models/header_model.dart';
import 'package:hmg_patient_app/pages/base/base_view.dart';
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:hmg_patient_app/uitl/utils.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/newUI/LabItem.dart';
import 'package:hmg_patient_app/widgets/new_design/doctor_header.dart';
import 'package:hmg_patient_app/widgets/others/network_base_view.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:provider/provider.dart';
import 'LabResultWidget.dart';
class LaboratoryResultWidget extends StatefulWidget {
final GestureTapCallback? onTap;
final String? billNo;
@ -22,8 +19,9 @@ class LaboratoryResultWidget extends StatefulWidget {
final String? orderNo;
final String? projectName;
final PatientLabOrders? patientLabOrder;
final bool showConfirmMessageDialog;
const LaboratoryResultWidget({Key? key, this.onTap, this.billNo, this.details, this.orderNo, this.projectName, this.patientLabOrder}) : super(key: key);
const LaboratoryResultWidget({Key? key, this.onTap, this.billNo, this.details, this.orderNo, this.projectName, this.patientLabOrder, this.showConfirmMessageDialog = true}) : super(key: key);
@override
_LaboratoryResultWidgetState createState() => _LaboratoryResultWidgetState();
@ -31,9 +29,8 @@ class LaboratoryResultWidget extends StatefulWidget {
class _LaboratoryResultWidgetState extends State<LaboratoryResultWidget> {
bool _isShowMore = true;
bool _isShowMoreGeneral = true;
late ProjectViewModel projectViewModel;
late String labSpecialResult;
late String? labSpecialResult;
@override
void initState() {
@ -50,147 +47,123 @@ class _LaboratoryResultWidgetState extends State<LaboratoryResultWidget> {
builder: (_, model, w) => NetworkBaseView(
baseViewModel: model,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DoctorHeader(
headerModel: HeaderModel(
widget.patientLabOrder!.doctorName!,
widget.patientLabOrder!.doctorID ?? 0,
widget.patientLabOrder!.doctorImageURL!,
widget.patientLabOrder!.speciality ?? [""],
widget.billNo!,
widget.patientLabOrder!.projectName ?? "",
widget.patientLabOrder!.orderDate!,
"",
widget.patientLabOrder!.nationalityFlagURL ?? "",
widget.patientLabOrder!.doctorRate!,
widget.patientLabOrder!.actualDoctorRate!,
widget.patientLabOrder!.noOfPatientsRate!,
projectViewModel.user.emailAddress!),
buttonTitle: TranslationBase.of(context).download,
buttonIcon: "assets/images/new/download_1.svg",
showConfirmMessageDialog: false,
isDownload: true,
onTap: widget.onTap!,
),
mainAxisSize: MainAxisSize.max,
children: [
_buildDoctorHeader(),
SizedBox(height: 10),
if (model.labResultLists.isNotEmpty)
Container(
child: Column(
children: [
InkWell(
onTap: () {
setState(
() {
_isShowMoreGeneral = !_isShowMoreGeneral;
},
);
},
child: Container(
padding: EdgeInsets.only(left: 17, top: 12, right: 13, bottom: 12),
margin: EdgeInsets.only(left: 21, right: 21, top: 12),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
//spreadRadius: 5,
blurRadius: 27,
offset: Offset(0, -3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Text(
TranslationBase.of(context).generalResult,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16),
),
),
Icon(
_isShowMoreGeneral ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Color(0xff575757),
size: 22,
)
],
),
if (_isShowMoreGeneral)
...List.generate(
model.labResultLists.length,
(index) => LabResultWidget(
patientLabOrder: widget.patientLabOrder,
filterName: model.labResultLists[index].filterName,
patientLabResultList: model.labResultLists[index].patientLabResultList,
),
)
],
),
),
),
],
),
Padding(
padding: const EdgeInsets.all(21.0),
child: _buildLabResultsList(model),
),
InkWell(
onTap: () {
setState(() {
_isShowMore = !_isShowMore;
});
},
child: Container(
padding: EdgeInsets.only(left: 17, top: 14, right: 13, bottom: 10),
margin: EdgeInsets.only(left: 21, right: 21, top: 12),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
//spreadRadius: 5,
blurRadius: 27,
offset: Offset(0, -3),
// Expanded(
// child: _buildLabResultsList(model),
// ),
_buildSpecialResultsToggle(),
SizedBox(height: 16),
],
),
),
);
}
Widget _buildDoctorHeader() {
return DoctorHeader(
headerModel: HeaderModel(
widget.patientLabOrder!.doctorName!,
widget.patientLabOrder!.doctorID ?? 0,
widget.patientLabOrder!.doctorImageURL!,
widget.patientLabOrder!.speciality ?? [""],
widget.billNo!,
widget.patientLabOrder!.projectName ?? "",
widget.patientLabOrder!.orderDate!,
"",
widget.patientLabOrder!.nationalityFlagURL ?? "",
widget.patientLabOrder!.doctorRate!,
widget.patientLabOrder!.actualDoctorRate!,
widget.patientLabOrder!.noOfPatientsRate!,
projectViewModel.user.emailAddress!,
),
onTap: widget.onTap!,
// buttonColor: Color(0xFF09AA28),
buttonTitle: TranslationBase.of(context).download,
buttonIcon: "assets/images/svg/download_1.svg",
showConfirmMessageDialog: widget.showConfirmMessageDialog,
isDownload: true,
);
}
Widget _buildLabResultsList(LabsViewModel model) {
return ListView.separated(
shrinkWrap: true,
itemCount: model.labResultLists.length,
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (_, __) => SizedBox(
height: 26,
),
itemBuilder: (context, index) {
var data = model.labResultLists[index];
return LabItem(key: ValueKey(data.filterName), item: data, patientLabOrders: widget.patientLabOrder);
},
);
}
Widget _buildSpecialResultsToggle() {
return InkWell(
onTap: () {
setState(() {
_isShowMore = !_isShowMore;
});
},
child: Container(
padding: EdgeInsets.only(left: 17, top: 14, right: 13, bottom: 10),
margin: EdgeInsets.only(left: 21, right: 21, top: 12),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(10.0),
),
boxShadow: [
BoxShadow(
color: Color(0xff000000).withOpacity(.05),
blurRadius: 27,
offset: Offset(0, -3),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: <Widget>[
Expanded(
child: Text(
TranslationBase.of(context).specialResult,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xff2E303A),
letterSpacing: -0.64,
height: 25 / 16,
),
],
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: <Widget>[
Expanded(
child: Text(
TranslationBase.of(context).specialResult,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16),
),
),
Icon(
_isShowMore ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Color(0xff575757),
size: 22,
)
],
),
if (_isShowMore)
Container(
width: MediaQuery.of(context).size.width * 0.9,
child: HtmlWidget(labSpecialResult),
),
],
Icon(
_isShowMore ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Color(0xff575757),
size: 22,
),
),
],
),
if (_isShowMore)
Container(
width: double.infinity,
child: Html(
data: labSpecialResult ?? TranslationBase.of(context).noDataAvailable,
),
),
],
),
),

@ -0,0 +1,156 @@
import 'package:hmg_patient_app/core/model/labs/lab_result.dart';
import 'package:hmg_patient_app/core/model/labs/patient_lab_orders.dart';
import 'package:hmg_patient_app/theme/colors.dart';
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/newUI/labWidgets.dart';
import 'package:flutter/material.dart';
import 'package:readmore/readmore.dart';
import '../FlowChartPage.dart';
class LabItem extends StatefulWidget {
final LabResultList item;
PatientLabOrders? patientLabOrders;
LabItem({super.key, required this.item, this.patientLabOrders});
@override
State<LabItem> createState() => _LabItemState();
}
class _LabItemState extends State<LabItem> {
bool _isShowMoreGeneral = false;
bool showBottomSheet = false;
@override
void initState() {
super.initState();
if (showBottomSheet) {
print('the bottom sheet is showing');
openFlowChart(context, '');
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () {
setState(() {
_isShowMoreGeneral = !_isShowMoreGeneral;
});
},
behavior: HitTestBehavior.opaque,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Text(
widget.item.filterName ?? '',
style: TextStyle(fontSize: 21, fontWeight: FontWeight.bold, color: Color(0xff2B353E), letterSpacing: -0.64, height: 25 / 16),
),
),
Icon(
_isShowMoreGeneral ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
color: Color(0xff2B353E),
size: 26,
)
],
),
),
SizedBox(
height: 12,
),
ReadMoreText(
widget.item.description ?? '',
trimMode: TrimMode.Line,
trimLines: 3,
style: const TextStyle(color: Color(0xff575757), fontSize: 11),
colorClickableText: CustomColors.accentColor,
trimCollapsedText: '${TranslationBase.of(context).readMore}',
trimExpandedText: ' show less',
),
SizedBox(
height: 8,
),
if (_isShowMoreGeneral)
...List.generate(widget.item.patientLabResultList?.length ?? 0, (index) {
var data = widget.item.patientLabResultList?[index];
return Column(
children: [
ItemResultCardWidgetWithParams(
title: data?.description ?? '',
subTitle: data?.resultValue ?? '',
referenceRange: data?.referanceRange ?? '',
percentage: data?.percentage ?? 0.0,
note: data?.testShortDescription ?? '',
source: "",
buttonText: "",
type: data?.calculatedResultFlag?.getType() ?? ResultTypes.unknown,
shouldShowResultBarAndGraph: data?.shouldShowResultBarAndGraph() ?? true,
onButtonPressed: () {
openFlowChart(context, data?.description ?? '');
},
),
SizedBox(
height: 13,
),
],
);
})
// Expanded(
// child: ListView.builder(
// itemCount: widget.item.patientLabResultList?.length ?? 0,
// itemBuilder: (context, index) {
// var data = widget.item.patientLabResultList?[index];
// Column(
// children: [
// EllipsisTextWithMore(
// text: data?.packageShortDescription ?? '',
// ),
// SizedBox(
// height: 8,
// ),
// ItemResultCardWidgetWithParams(
// title: data?.description ?? '',
// subTitle: data?.resultValue ?? '',
// referenceRange: data?.referanceRange ?? '',
// percentage: 20,
// note: data?.testShortDescription ?? '',
// source: "",
// buttonText: "",
// type: data?.calculatedResultFlag?.getType() ??
// ResultTypes.unknown),
// ],
// );
// }))
],
);
}
openFlowChart(BuildContext context, String procedure) {
print('openFlowChart: the bottom sheet is showing');
showModalBottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
context: context,
scrollControlDisabledMaxHeightRatio: .75,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20))),
builder: (context) {
print('builder: the bottom sheet is showing');
return FlowChartPage(
filterName: procedure,
patientLabOrder: widget.patientLabOrders,
);
}).then((value) {
setState(() {
showBottomSheet = false;
});
});
}
}

@ -0,0 +1,523 @@
import 'dart:math';
import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
import 'package:hmg_patient_app/uitl/utils.dart';
import 'package:flutter/material.dart';
///@author
///
enum ResultTypes { lowCriticalLow, criticalLow, low, normal, high, criticalHigh, highCriticalHigh, IRR, unknown }
class ItemResultCardWidget extends StatelessWidget {
final Widget child;
const ItemResultCardWidget({
Key? key,
required this.child,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 12, right: 12, bottom: 12),
child: child,
),
),
],
);
}
}
class CustomResultProgressBar extends StatelessWidget {
final num percentage;
final String value;
final ResultTypes type;
CustomResultProgressBar({Key? key, required this.percentage, required this.value, required this.type}) : super(key: key);
final GlobalKey lcl = GlobalKey();
final GlobalKey cl = GlobalKey();
final GlobalKey l = GlobalKey();
final GlobalKey n = GlobalKey();
final GlobalKey h = GlobalKey();
final GlobalKey ch = GlobalKey();
final GlobalKey hch = GlobalKey();
OverlayEntry? overlayEntry;
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
final double totalWidth = constraints.maxWidth;
final double spacing = 2;
final double tooltipPosition = (totalWidth * (percentage / 100)).clamp(0, totalWidth - 50);
return Stack(
children: [
Row(
children: [
_buildResultBar(
key: cl,
flex: 3,
color: Color(0xFFDE7676),
title: TranslationBase.of(context).criticalLow,
),
SizedBox(
width: 2,
),
_buildResultBar(
key: l,
flex: 2,
color: Color(0xFFEAB157),
title: TranslationBase.of(context).low,
),
SizedBox(
width: 2,
),
_buildResultBar(
key: n,
flex: 5,
color: Color(0xFF09AA28),
title: TranslationBase.of(context).normal,
),
SizedBox(
width: 2,
),
_buildResultBar(
key: h,
flex: 2,
color: Color(0xFFEAB157),
title: TranslationBase.of(context).high,
),
SizedBox(
width: 2,
),
_buildResultBar(
key: ch,
flex: 3,
color: Color(0xFFDE7676),
title: TranslationBase.of(context).criticalHigh,
),
],
),
if (!percentage.isNegative)
Positioned(
left: tooltipPosition,
top: 33,
child: Column(
children: [
TextCloud(
text: "${value}",
color: getColorForResultType(type),
width: 100,
height: 20,
padding: EdgeInsets.zero,
axisDirection: AxisDirection.up,
),
],
),
),
],
);
});
}
Color getColorForResultType(ResultTypes type) {
switch (type) {
case ResultTypes.lowCriticalLow:
case ResultTypes.highCriticalHigh:
case ResultTypes.criticalLow:
case ResultTypes.criticalHigh:
return Color(0xFFDE7676);
case ResultTypes.low:
case ResultTypes.high:
return Color(0xFFEAB157);
case ResultTypes.normal:
return Color(0xFF09AA28);
default:
return Color(0xFF09AA28);
}
}
Widget _buildResultBar({required int flex, required Color color, required String title, Key? key}) {
return Expanded(
key: key,
flex: flex,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
title,
style: TextStyle(
fontSize: 8,
fontFamily: 'Poppins',
letterSpacing: -0.3,
color: color,
fontWeight: FontWeight.w400,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 5),
Container(
height: 5,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
),
],
),
);
}
}
class TextCloud extends StatelessWidget {
final String text; // write here box content
final Color color; // Set box Color
final EdgeInsets padding; // Set content padding
final double width; // Box width
final double height; // Box Height
final AxisDirection axisDirection; // Set triangle location up,left,right,down
final double locationOfArrow; // set between 0 and 1, If 0.5 is set triangle position will be centered
const TextCloud({
super.key,
required this.text,
this.color = Colors.white,
this.padding = const EdgeInsets.all(10),
this.width = 200,
this.height = 100,
this.axisDirection = AxisDirection.down,
this.locationOfArrow = 0.5,
});
@override
Widget build(BuildContext context) {
Size arrowSize = const Size(12, 12);
return Stack(
clipBehavior: Clip.none,
children: [
Container(
width: width,
height: height,
padding: padding,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(10),
),
child: Center(
child: Text(
text,
style: TextStyle(color: Colors.white, fontSize: 9, letterSpacing: -.3),
)),
),
Builder(builder: (context) {
double angle = 0;
switch (axisDirection) {
case AxisDirection.left:
angle = pi * -0.5;
break;
case AxisDirection.up:
angle = pi * -2;
break;
case AxisDirection.right:
angle = pi * 0.5;
break;
case AxisDirection.down:
angle = pi;
break;
default:
angle = 0;
}
return Positioned(
left: axisDirection == AxisDirection.left
? -arrowSize.width + 5
: (axisDirection == AxisDirection.up || axisDirection == AxisDirection.down ? width * locationOfArrow - arrowSize.width / 2 : null),
right: axisDirection == AxisDirection.right ? -arrowSize.width + 5 : null,
top: axisDirection == AxisDirection.up
? -arrowSize.width + 5
: (axisDirection == AxisDirection.right || axisDirection == AxisDirection.left ? height * locationOfArrow - arrowSize.width / 2 : null),
bottom: axisDirection == AxisDirection.down ? -arrowSize.width + 5 : null,
child: Transform.rotate(
angle: angle,
child: CustomPaint(
size: arrowSize,
painter: ArrowPaint(color: color),
),
),
);
})
],
);
}
}
class ArrowPaint extends CustomPainter {
final Color color;
ArrowPaint({required this.color});
@override
void paint(Canvas canvas, Size size) {
Path path_0 = Path();
path_0.moveTo(size.width * 0.5745375, size.height * 0.06573708);
path_0.lineTo(size.width * 0.9813667, size.height * 0.8794000);
path_0.cubicTo(size.width * 1.001950, size.height * 0.9205625, size.width * 0.9852625, size.height * 0.9706208, size.width * 0.9441000, size.height * 0.9912000);
path_0.cubicTo(size.width * 0.9325250, size.height * 0.9969875, size.width * 0.9197667, size.height, size.width * 0.9068292, size.height);
path_0.lineTo(size.width * 0.09316958, size.height);
path_0.cubicTo(size.width * 0.04714583, size.height, size.width * 0.009836208, size.height * 0.9626917, size.width * 0.009836208, size.height * 0.9166667);
path_0.cubicTo(size.width * 0.009836208, size.height * 0.9037292, size.width * 0.01284829, size.height * 0.8909708, size.width * 0.01863392, size.height * 0.8794000);
path_0.lineTo(size.width * 0.4254625, size.height * 0.06573708);
path_0.cubicTo(size.width * 0.4460458, size.height * 0.02457225, size.width * 0.4961042, size.height * 0.007886875, size.width * 0.5372667, size.height * 0.02846929);
path_0.cubicTo(size.width * 0.5533958, size.height * 0.03653296, size.width * 0.5664708, size.height * 0.04961000, size.width * 0.5745375, size.height * 0.06573708);
path_0.close();
Paint paint_0_fill = Paint()..style = PaintingStyle.fill;
paint_0_fill.color = color;
canvas.drawPath(path_0, paint_0_fill);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
class ItemResultCardWidgetWithParams extends StatelessWidget {
final String title;
final String subTitle;
final String note;
final String source;
final String referenceRange;
final num percentage;
final String buttonText;
final ResultTypes type;
final bool isArabic;
final Function()? onButtonPressed;
final bool shouldShowResultBarAndGraph;
const ItemResultCardWidgetWithParams({
Key? key,
required this.title,
required this.subTitle,
required this.referenceRange,
required this.percentage,
required this.note,
required this.source,
required this.buttonText,
required this.type,
this.onButtonPressed,
this.isArabic = false,
this.shouldShowResultBarAndGraph = true,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final fontFamily = isArabic ? 'Cairo' : 'Poppins';
return Container(
margin: const EdgeInsets.only(top: 21),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(10)),
border: BorderDirectional(
start: BorderSide(
color: getColorForResultType(type),
width: MediaQuery.of(context).size.width * 0.015,
),
),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
// Title Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: TextStyle(fontSize: 16, fontFamily: fontFamily, fontWeight: FontWeight.w600, color: const Color(0xff2E303A), letterSpacing: -.64),
),
),
// GestureDetector(
// onTap: onButtonPressed,
// child: Utils.tableColumnValueWithUnderLine(TranslationBase.of(context).viewFlowChart, isLast: true, isCapitable: false),
// ),
Visibility(
visible: shouldShowResultBarAndGraph,
child: GestureDetector(
onTap: onButtonPressed,
child: Utils.tableColumnValueWithUnderLine(TranslationBase.of(context).viewFlowChart, isLast: true, isCapitable: false),
),
),
],
),
const SizedBox(height: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ResultStatusWidget(type: type),
SizedBox(width: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
subTitle,
style: TextStyle(fontSize: 14, fontFamily: fontFamily, fontWeight: FontWeight.w600, color: const Color(0xff2E303A), letterSpacing: -0.56),
),
Text(
"${TranslationBase.of(context).referenceRange} \n $referenceRange",
style: TextStyle(
fontSize: 10,
fontFamily: fontFamily,
fontWeight: FontWeight.w600,
color: const Color(0xff2E303A),
letterSpacing: -0.4,
),
),
],
),
],
),
const SizedBox(height: 10),
// Progress Bar
Visibility(
visible: shouldShowResultBarAndGraph,
child: Column(
children: [
SizedBox(
height: 55,
child: CustomResultProgressBar(
percentage: percentage,
value: subTitle,
type: type,
),
),
const SizedBox(height: 12),
],
),
),
// Note Section
if (note.isNotEmpty)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider(color: Color(0xFFEFEFEF), thickness: 1),
const SizedBox(height: 10),
Text(
"${TranslationBase.of(context).notes}:",
style: TextStyle(
fontSize: 12,
color: const Color(0xFF2E303A),
letterSpacing: -0.4,
fontWeight: FontWeight.bold,
),
),
Text(
note,
style: TextStyle(
fontSize: 12,
color: const Color(0xFF575757),
letterSpacing: -0.4,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 3),
],
),
),
);
}
Color getColorForResultType(ResultTypes type) {
switch (type) {
case ResultTypes.criticalLow:
case ResultTypes.criticalHigh:
case ResultTypes.lowCriticalLow:
case ResultTypes.highCriticalHigh:
return Color(0xFFDE7676);
case ResultTypes.low:
case ResultTypes.high:
return Color(0xFFEAB157);
case ResultTypes.normal:
return Color(0xFF09AA28);
//todo handle irr here
default:
return Color(0xFF09AA28);
}
}
}
class ResultStatusWidget extends StatelessWidget {
final ResultTypes type;
const ResultStatusWidget({
Key? key,
required this.type,
}) : super(key: key);
@override
Widget build(BuildContext context) {
Color? color;
IconData? icon;
switch (type) {
case ResultTypes.criticalLow:
case ResultTypes.lowCriticalLow:
color = const Color(0xFFDE7676);
icon = Icons.arrow_circle_down;
break;
case ResultTypes.low:
color = const Color(0xFFEAB157);
icon = Icons.arrow_circle_down;
break;
case ResultTypes.criticalHigh:
case ResultTypes.highCriticalHigh:
color = const Color(0xFFDE7676);
icon = Icons.arrow_circle_up;
break;
case ResultTypes.high:
color = const Color(0xFFEAB157);
icon = Icons.arrow_circle_up;
break;
case ResultTypes.normal:
color = const Color(0xFF09AA28);
icon = Icons.check_circle;
break;
case ResultTypes.unknown:
case ResultTypes.IRR:
color = Color(0xFF09AA28);
icon = Icons.check_circle;
break;
}
// Return the icon if defined, otherwise a placeholder widget
return icon != null
? Icon(
icon,
color: color,
)
: const SizedBox(); // Use SizedBox to keep the layout consistent
}
}

@ -38,9 +38,9 @@ class MyInAppBrowser extends InAppBrowser {
static String APPLE_PAY_PAYFORT_URL = 'https://hmgwebservices.com/PayFortWebLive/PayFortApi/MakeApplePayRequest'; // Payfort Payment Gateway URL LIVE
// static String APPLE_PAY_PAYFORT_URL = 'https://hmgwebservices.com/PayFortWebLive/PayFortApi/MakeApplePayRequest'; // Payfort Payment Gateway URL UAT
static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT
// static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT
// static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE
static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE
// static String SERVICE_URL = 'https://uat.hmgwebservices.com/payfortforvidaplus/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL UAT VIDA PLUS
@ -290,7 +290,8 @@ class MyInAppBrowser extends InAppBrowser {
form = form.replaceFirst('PATIENT_TYPE_ID', patientData == null ? patientType.toString() : "1");
Platform.isIOS
? form = form.replaceFirst('DEVICE_TOKEN', await AppSharedPreferences().getStringWithDefaultValue(PUSH_TOKEN, "") + "," + await AppSharedPreferences().getStringWithDefaultValue(ONESIGNAL_APNS_TOKEN, ""))
? form = form.replaceFirst(
'DEVICE_TOKEN', await AppSharedPreferences().getStringWithDefaultValue(PUSH_TOKEN, "") + "," + await AppSharedPreferences().getStringWithDefaultValue(ONESIGNAL_APNS_TOKEN, ""))
: form = form.replaceFirst('DEVICE_TOKEN', await sharedPref.getStringWithDefaultValue(PUSH_TOKEN, "") ?? "");
// form = form.replaceFirst('DEVICE_TOKEN', await AppSharedPreferences().getString(PUSH_TOKEN) + "," + await AppSharedPreferences().getString(ONESIGNAL_APNS_TOKEN));

@ -15,6 +15,7 @@ import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart' as googleMap;
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_place_picker_mb_v2/google_maps_place_picker.dart';
import 'package:huawei_hmsavailability/huawei_hmsavailability.dart';
import 'package:provider/provider.dart';

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

@ -0,0 +1,69 @@
group 'ai.panoptic.vital_sign_camera'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.5.20'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 24
}
}
dependencies {
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.camera:camera-core:1.0.2'
implementation 'com.google.mlkit:face-detection:16.1.2'
implementation 'com.android.volley:volley:1.2.1'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(":vital-sign-engine")
implementation "androidx.camera:camera-core:1.1.0-beta02"
implementation "androidx.camera:camera-camera2:1.1.0-beta02"
implementation "androidx.camera:camera-lifecycle:1.1.0-beta02"
implementation "androidx.camera:camera-video:1.1.0-beta02"
implementation "androidx.camera:camera-view:1.1.0-beta02"
implementation "androidx.camera:camera-extensions:1.1.0-beta02"
implementation "androidx.exifinterface:exifinterface:1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.5.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
}

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

@ -0,0 +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

@ -0,0 +1,2 @@
configurations.maybeCreate("default")
artifacts.add("default", file('vital-sign-engine-debug.aar'))

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ai.panoptic.vital_sign_camera">
</manifest>

@ -0,0 +1,108 @@
package ai.panoptic.vital_sign_camera
import ai.panoptic.wvs.camera.*
import ai.panoptic.wvs.engine.Converter
import android.content.Context
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView
@RequiresApi(Build.VERSION_CODES.O)
class FlutterVitalSignCamera internal constructor(
context: Context,
messenger: BinaryMessenger,
id: Int,
props: Map<String, Any>,
lifecycle: Lifecycle
) : PlatformView, MethodChannel.MethodCallHandler, EventChannel.StreamHandler, VitalSignCameraEventHandler {
private val camera: VitalSignCamera
private val methodChannel: MethodChannel
private var eventSink: EventChannel.EventSink? = null
private val mainThread = Handler(Looper.getMainLooper())
override fun getView(): View {
return camera
}
init {
camera = VitalSignCamera(context, MLKitFaceDetector(), lifecycle, this)
methodChannel = MethodChannel(messenger, "ai.panoptic/channel/flutter_vital_sign_camera_$id")
methodChannel.setMethodCallHandler(this)
val eventChannel = EventChannel(messenger, "ai.panoptic/channel/flutter_vital_sign_camera_processed_frame_event_$id")
eventChannel.setStreamHandler(this)
camera.setProps(props)
}
override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
when (methodCall.method) {
"startScanning" -> {
camera.startStartScanning()
.thenAccept { result.success(null) }
.exceptionally {
result.error("-1", it.message, it.cause)
return@exceptionally null
}
}
"stopScanning" -> {
camera.stopScanning()
.thenAccept { result.success(null) }
.exceptionally {
result.error("-2", it.message, it.cause)
return@exceptionally null
}
}
"configure" -> {
val arguments = methodCall.arguments as? Map<String, Any>
arguments?.let {
camera.setProps(it)
}
}
else -> {
result.notImplemented()
}
}
}
override fun dispose() {
// camera.destroy()
}
override fun onVideoFrameProcessed(result: ai.panoptic.wvs.engine.ProcessFrameResult): kotlin.Unit {
// Already handled in onEvent.
// No need to handle here.
}
override fun onEvent(cameraView: VitalSignCamera, eventName: String, event: Map<String,Any>?) {
when(eventName) {
"videoFrameProcessed" -> {
eventSink?.let {
mainThread.post {
it.success(event)
}
}
}
}
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events
}
override fun onCancel(arguments: Any?) {
eventSink = null
}
}

@ -0,0 +1,165 @@
package ai.panoptic.vital_sign_camera;
import static com.google.mlkit.vision.face.FaceContour.FACE;
import android.graphics.PointF;
import android.media.Image;
import android.util.ArraySet;
import com.google.android.gms.tasks.Task;
import com.google.mlkit.vision.common.InputImage;
import com.google.mlkit.vision.face.Face;
import com.google.mlkit.vision.face.FaceContour;
import com.google.mlkit.vision.face.FaceDetection;
import com.google.mlkit.vision.face.FaceDetector;
import com.google.mlkit.vision.face.FaceDetectorOptions;
import java.util.List;
import java.util.Set;
import ai.panoptic.wvs.engine.EyesOpenProbability;
import ai.panoptic.wvs.engine.FaceDetectorFeature;
import ai.panoptic.wvs.engine.HeadEulerAngle;
import ai.panoptic.wvs.engine.NormalizedFacebox;
import ai.panoptic.wvs.engine.VitalSignFaceDetector;
public class MLKitFaceDetector implements VitalSignFaceDetector {
ai.panoptic.wvs.engine.Face face = null;
FaceDetectorOptions options =
new FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
.setContourMode(FaceDetectorOptions.CONTOUR_MODE_NONE)
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
.setMinFaceSize(0.15f)
.build();
FaceDetector faceDetector = FaceDetection.getClient(options);
public ai.panoptic.wvs.engine.Face detectFace(Image mediaImage, int rotationDegrees) {
if (mediaImage == null) {
return null;
}
float width = mediaImage.getWidth();
float height = mediaImage.getHeight();
if (rotationDegrees == 270 || rotationDegrees == 90) {
width = mediaImage.getHeight();
height = mediaImage.getWidth();
}
final float _width = width;
final float _height = height;
InputImage image = InputImage.fromMediaImage(mediaImage, rotationDegrees);
Task<List<Face>> task = faceDetector.process(image);
task.addOnCompleteListener( result -> {
if (result.isCanceled()) {
return;
}
if (!result.isSuccessful()) {
return;
}
if (!result.isComplete()) {
return;
}
try {
List<Face> faces = result.getResult();
if (faces.isEmpty()) {
this.face = null;
return;
}
Face face = faces.get(0);
this.face = createFace(face, _width, _height);
} catch (Exception e) {
e.printStackTrace();
this.face = null;
}
});
return face;
}
static private ai.panoptic.wvs.engine.Face createFace(Face face, float _width, float _height) {
FaceContour contour = face.getContour(FACE);
NormalizedFacebox facebox = new NormalizedFacebox();
float minX = _width;
float minY = _height;
float maxX = 0;
float maxY = 0;
if (contour != null) {
for (PointF p : contour.getPoints()) {
if (p.x < minX) {
minX = p.x;
}
if (p.y < minY) {
minY = p.y;
}
if (p.x > maxX) {
maxX = p.x;
}
if (p.y > maxY) {
maxY = p.y;
}
}
} else {
minX = face.getBoundingBox().left;
minY = face.getBoundingBox().top;
maxX = face.getBoundingBox().right;
maxY = face.getBoundingBox().bottom;
}
facebox.xCenter = (minX + (maxX - minX) / 2.0f) / _width;
facebox.yCenter = (minY + (maxY - minY) / 2.0f) / _height;
facebox.width = (maxX - minX) / _width;
facebox.height = (maxY - minY) / _height;
ai.panoptic.wvs.engine.Face result = new ai.panoptic.wvs.engine.Face(facebox);
result.setEulerAngle(
new HeadEulerAngle(
face.getHeadEulerAngleX(),
face.getHeadEulerAngleY(),
face.getHeadEulerAngleY()
)
);
if (face.getLeftEyeOpenProbability() != null && face.getRightEyeOpenProbability() != null) {
result.setEyesOpenProbability(
new EyesOpenProbability(
face.getLeftEyeOpenProbability(),
face.getRightEyeOpenProbability()
)
);
}
return result;
}
@Override
public Set<FaceDetectorFeature> supportedFeatures() {
ArraySet<FaceDetectorFeature> features = new ArraySet<FaceDetectorFeature>();
features.add(FaceDetectorFeature.eyeOpenProbability);
features.add(FaceDetectorFeature.eulerAngle);
return features;
}
@Override
public NormalizedFacebox detect(Image mediaImage, int rotationDegrees) {
detectFace(mediaImage, rotationDegrees);
if (face == null) {
return null;
}
return face.getFacebox();
}
}

@ -0,0 +1,145 @@
package ai.panoptic.vital_sign_camera
import ai.panoptic.wvs.camera.availableCameraDevices
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.annotation.NonNull
import androidx.core.content.ContextCompat
import androidx.lifecycle.*
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/** VitalSignCameraPlugin */
class VitalSignCameraPlugin: FlutterPlugin, MethodCallHandler, ActivityAware,
RequestPermissionsResultListener {
companion object {
const val TAG = "VitalSignCameraPlugin"
fun parsePermissionStatus(status: Int): String {
return when (status) {
PackageManager.PERMISSION_DENIED -> "denied"
PackageManager.PERMISSION_GRANTED -> "authorized"
else -> "not-determined"
}
}
}
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel : MethodChannel
private lateinit var pluginBinding: FlutterPlugin.FlutterPluginBinding
private lateinit var context: Context
private lateinit var activity: Activity
private lateinit var activityBinding:ActivityPluginBinding
private val coroutineScope = CoroutineScope(Dispatchers.Main)
private var requestCode = 1
private var permissionRequests = HashMap<Int, Result>()
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
Log.d("VitalSignCameraPlugin", "onAttachedToEngine")
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "vital_sign_camera_plugin")
channel.setMethodCallHandler(this)
pluginBinding = flutterPluginBinding
context = flutterPluginBinding.applicationContext
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
Log.d(TAG, "onAttachedToActivity")
val lifecycle: Lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding)
pluginBinding.platformViewRegistry.registerViewFactory(
"ai.panoptic/flutter_vital_sign_camera",
VitalSignCameraFactory(pluginBinding.binaryMessenger, lifecycle)
)
activity = binding.activity
activityBinding = binding
activityBinding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}
override fun onDetachedFromActivity() {
activityBinding.removeRequestPermissionsResultListener(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
"availableCameraDevices" -> {
coroutineScope.launch {
val devices = availableCameraDevices(context)
val returnValue = devices.map { it.map }
Log.d(TAG, "$returnValue")
result.success(returnValue)
}
}
"getCameraPermissionStatus" -> {
Log.d(TAG, "getCameraPermissionStatus")
val status =
parsePermissionStatus(
ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA))
result.success(status)
}
"requestCameraPermission" -> {
Log.d(TAG, "requestCameraPermission")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// API 21 and below always grants permission on app install
result.success("authorized")
}
activity.requestPermissions(arrayOf(Manifest.permission.CAMERA), requestCode)
permissionRequests[requestCode] = result
requestCode += 1
}
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
permissionRequests[requestCode]?.let {
val permissionStatus = if (grantResults.isNotEmpty()) grantResults[0] else PackageManager.PERMISSION_DENIED
it.success(parsePermissionStatus(permissionStatus))
permissionRequests.remove(requestCode)
return true
}
return false
}
}

@ -0,0 +1,218 @@
//
// MLKitFaceDetector.swift
// VitalSignEngineCore
//
// Created by Eddie Lau on 24/8/2022.
//
import Foundation
import MLKitFaceDetection
import MLKitVision
import VitalSignEngineCore
public class MLKitFaceDetector : VitalSignFaceDetector {
public typealias Face = MLKitFace
public let supportedFeatures: Set<VitalSignEngineCore.FaceDetectorFeature>
= [.eyesOpenProbability, .eularAngle]
public init() {
}
public let maxDetectionHeight:CGFloat = 240
let fps = Fps(name: "MLKitFaceDetector")
lazy var faceDetector:FaceDetector = {
let option = FaceDetectorOptions()
option.contourMode = .all
option.classificationMode = .all
option.landmarkMode = .none
option.performanceMode = .fast
return FaceDetector.faceDetector(options: option)
}()
var face:Face? {
didSet {
fps.tick()
}
}
public var frameRate: Double? { fps.lastReportedAverage }
public var maxConcurrency = 2
var jobCount = 0
public func detect(from frame: UIImage) -> Face? {
guard jobCount < maxConcurrency else {
return face
}
jobCount += 1
DispatchQueue.global().async {
let scaledFrame = self.scaled(videoFrame: frame)
let frameSize = scaledFrame.size
let image = VisionImage(image: scaledFrame)
image.orientation = image.orientation
DispatchQueue.main.async {
self.faceDetector.process(image) { faces, error in
guard let face = faces?.first else {
self.face = nil
self.jobCount -= 1
return
}
self.face = MLKitFace(face: face, frameSize: frameSize)
self.jobCount -= 1
}
}
}
return face
}
func scaled(videoFrame frame:UIImage) -> UIImage {
var scale = CGFloat(1.0)
if frame.size.height > maxDetectionHeight {
scale = maxDetectionHeight / frame.size.height
}
let scaledScale = CGSize(width:frame.size.width * scale, height:frame.size.height * scale)
return frame.resizedImage(size: scaledScale)!
}
}
public struct MLKitFaceLandmarks: FaceLandmarks {
public typealias ContourType = FaceContour
let mlkitFace:MLKitFaceDetection.Face
public let videoFrameSize: CGSize
init(mlkitFace: MLKitFaceDetection.Face, videoFrameSize: CGSize) {
self.mlkitFace = mlkitFace
self.videoFrameSize = videoFrameSize
}
public var faceOval: FaceContour? { mlkitFace.contour(ofType: .face) }
public func contour(of type: VitalSignEngineCore.FaceLandmarkType) -> FaceContour? {
guard let contourType = type.asContourType else { return nil }
return mlkitFace.contour(ofType: contourType)
}
}
public class MLKitFace : FaceProtocol {
public typealias ContourType = FaceContour
let mlkitFace:MLKitFaceDetection.Face
public let faceBox: NormalizedFaceBox
public let faceLandmarks: MLKitFaceLandmarks
public let eulerAngle: (x: CGFloat, y: CGFloat, z: CGFloat)?
public let eyesOpenProbability: (leftEye:CGFloat, rightEye:CGFloat)?
init?(face:MLKitFaceDetection.Face, frameSize:CGSize) {
self.mlkitFace = face
self.faceLandmarks = MLKitFaceLandmarks(mlkitFace: face, videoFrameSize: frameSize)
guard let faceContour = face.contour(ofType: FaceContourType.face) else {
return nil
}
let eyesOpenPropbability: (leftEye:CGFloat, rightEye:CGFloat)?
if face.hasLeftEyeOpenProbability && face.hasRightEyeOpenProbability {
eyesOpenPropbability = (face.leftEyeOpenProbability, face.rightEyeOpenProbability)
} else {
eyesOpenPropbability = nil
}
self.eyesOpenProbability = eyesOpenPropbability
let eulerAngle: (x:CGFloat, y:CGFloat, z:CGFloat)?
if face.hasHeadEulerAngleX && face.hasHeadEulerAngleY && face.hasHeadEulerAngleZ {
eulerAngle = (face.headEulerAngleX, face.headEulerAngleY, face.headEulerAngleZ)
} else {
eulerAngle = nil
}
self.eulerAngle = eulerAngle
self.faceBox = faceContour.toFacebox(frameSize: frameSize)
}
}
fileprivate extension MLKitFaceDetection.FaceContour {
func toFacebox(frameSize:CGSize) -> NormalizedFaceBox {
var minX = CGFloat.greatestFiniteMagnitude
var minY = CGFloat.greatestFiniteMagnitude
var maxX = -CGFloat.greatestFiniteMagnitude
var maxY = -CGFloat.greatestFiniteMagnitude
points.forEach { point in
minX = min(minX, point.x)
minY = min(minY, point.y)
maxX = max(maxX, point.x)
maxY = max(maxY, point.y)
}
return NormalizedFaceBox(
xCenter: ((maxX - minX)/2 + minX) / frameSize.width,
yCenter: ((maxY - minY)/2 + minY) / frameSize.height,
width: (maxX - minX) / frameSize.width,
height: (maxY - minY) / frameSize.height
)
}
}
fileprivate extension UIImage {
func resizedImage(size: CGSize) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
return image?.data.flatMap(UIImage.init) // This line is copied from MLKit, Not sure why is needed
}
private var data: Data? {
#if swift(>=4.2)
return pngData() ?? jpegData(compressionQuality: Constant.jpegCompressionQuality)
#else
return pngData() ?? jpegData(compressionQuality: Constant.jpegCompressionQuality)
#endif // swift(>=4.2)
}
private enum Constant {
static let jpegCompressionQuality: CGFloat = 0.8
}
}
extension VisionPoint : FaceContourPoint {
}
extension FaceContour : Contour {
}
extension VitalSignEngineCore.FaceLandmarkType {
var asContourType:MLKitFaceDetection.FaceContourType? {
switch self {
case .faceOval: return .face
case .leftEyebrowTop: return .leftEyebrowTop
case .leftEyebrowBottom: return .leftEyebrowBottom
case .rightEyebrowTop: return .rightEyebrowTop
case .rightEyebrowBottom: return .rightEyebrowBottom
case .leftEye: return .leftEye
case .rightEye: return .rightEye
case .upperLipTop: return .upperLipTop
case .upperLipBottom: return .upperLipBottom
case .lowerLipTop: return .lowerLipTop
case .lowerLipBottom: return .lowerLipBottom
case .noseBridge: return .noseBridge
case .noseBottom: return .noseBottom
case .leftCheek: return .leftCheek
case .rightCheek: return .rightCheek
@unknown default: return nil
}
}
}

@ -0,0 +1,91 @@
import Flutter
import UIKit
import VitalSignEngineCore
public class SwiftVitalSignCameraPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "vital_sign_camera_plugin", binaryMessenger: registrar.messenger())
let instance = SwiftVitalSignCameraPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
registrar.register(VitalSignCameraFactory(messenger: registrar.messenger()), withId: "ai.panoptic/flutter_vital_sign_camera")
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method == "availableCameraDevices" {
let devices = VitalSignCamera.availableCameraDevices()
result(devices.map({ $0.flutterCompatibleDict }))
}
if call.method == "requestCameraPermission" {
VitalSignCamera.requestCameraPermission().done { status in
result(status.rawValue)
}.catch { error in
result(FlutterError(code: "\(error.code)", message: error.message, details: nil))
}
}
if call.method == "getCameraPermissionStatus" {
let status = VitalSignCamera.getCameraPermissionStatus()
result(status.rawValue)
}
}
}
fileprivate extension CameraDevice {
var flutterCompatibleDict:[String:Any] {
[
"id": id,
"devices": devices.map { $0.rawValue },
"position": position.rawValue,
"name": name,
"hasFlash": NSNumber(value: hasFlash),
"hasTorch": NSNumber(value: hasTorch),
"minZoom": NSNumber(value: Double(minZoom)),
"neutralZoom": NSNumber(value: Double(neutralZoom)),
"maxZoom": NSNumber(value: Double(maxZoom)),
"isMultiCam": NSNumber(value: isMultiCam),
"supportsParallelVideoProcessing": NSNumber(value: supportsParallelVideoProcessing),
"supportsDepthCapture": NSNumber(value: supportsDepthCapture),
"supportsRawCapture": NSNumber(value: supportsRawCapture),
"supportsLowLightBoost": NSNumber(value: supportsLowLightBoost),
"supportsFocus": NSNumber(value: supportsFocus),
"formats": formats.map { format -> [String: Any] in
format.flutterCompatibleDict
},
]
}
}
fileprivate extension CameraCaptureFormat {
var flutterCompatibleDict:[String:Any] {
var dict:[String:Any] = [
"videoStabilizationModes": videoStabilizationModes.map { $0.rawValue },
"autoFocusSystem": autoFocusSystem.rawValue,
"photoHeight": NSNumber(value: Double(photoHeight)),
"photoWidth": NSNumber(value: Double(photoWidth)),
"videoHeight": NSNumber(value: Double(videoHeight)),
"videoWidth": NSNumber(value: Double(videoWidth)),
"maxISO": NSNumber(value: Int(maxISO)),
"minISO": NSNumber(value: Int(minISO)),
"fieldOfView": NSNumber(value: Double(fieldOfView)),
"maxZoom": NSNumber(value: Double(maxZoom)),
"colorSpaces": colorSpaces.map { $0.rawValue },
"supportsVideoHDR": NSNumber(value: supportsVideoHDR),
"supportsPhotoHDR": NSNumber(value: supportsPhotoHDR),
"frameRateRanges": frameRateRanges.map {
[
"minFrameRate": NSNumber(value: Int($0.minFrameRate)),
"maxFrameRate": NSNumber(value: Int($0.maxFrameRate)),
]
},
"pixelFormat": pixelFormat.rawValue,
]
if let isHighestPhotoQualitySupported = isHighestPhotoQualitySupported {
dict["isHighestPhotoQualitySupported"] = isHighestPhotoQualitySupported
}
return dict
}
}

@ -0,0 +1,37 @@
//
// VitalSignCameraFactory.swift
// vital_sign_camera
//
// Created by Eddie Lau on 24/1/2023.
//
import Foundation
import Foundation
import Flutter
import UIKit
class VitalSignCameraFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
return FlutterVitalSignCamera(
frame: frame,
viewIdentifier: viewId,
arguments: args,
binaryMessenger: messenger)
}
func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
FlutterStandardMessageCodec.sharedInstance()
}
}

@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface VitalSignCameraPlugin : NSObject<FlutterPlugin>
@end

@ -0,0 +1,15 @@
#import "VitalSignCameraPlugin.h"
#if __has_include(<vital_sign_camera/vital_sign_camera-Swift.h>)
#import <vital_sign_camera/vital_sign_camera-Swift.h>
#else
// Support project import fallback if the generated compatibility header
// is not copied when this plugin is created as a library.
// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
#import "vital_sign_camera-Swift.h"
#endif
@implementation VitalSignCameraPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftVitalSignCameraPlugin registerWithRegistrar:registrar];
}
@end

@ -0,0 +1,411 @@
#if 0
#elif defined(__arm64__) && __arm64__
// Generated by Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
#ifndef VITALSIGNENGINECORE_SWIFT_H
#define VITALSIGNENGINECORE_SWIFT_H
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgcc-compat"
#if !defined(__has_include)
# define __has_include(x) 0
#endif
#if !defined(__has_attribute)
# define __has_attribute(x) 0
#endif
#if !defined(__has_feature)
# define __has_feature(x) 0
#endif
#if !defined(__has_warning)
# define __has_warning(x) 0
#endif
#if __has_include(<swift/objc-prologue.h>)
# include <swift/objc-prologue.h>
#endif
#pragma clang diagnostic ignored "-Wauto-import"
#if defined(__OBJC__)
#include <Foundation/Foundation.h>
#endif
#if defined(__cplusplus)
#include <cstdint>
#include <cstddef>
#include <cstdbool>
#include <cstring>
#include <stdlib.h>
#include <new>
#include <type_traits>
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#endif
#if defined(__cplusplus)
#if defined(__arm64e__) && __has_include(<ptrauth.h>)
# include <ptrauth.h>
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-macro-identifier"
# ifndef __ptrauth_swift_value_witness_function_pointer
# define __ptrauth_swift_value_witness_function_pointer(x)
# endif
# ifndef __ptrauth_swift_class_method_pointer
# define __ptrauth_swift_class_method_pointer(x)
# endif
#pragma clang diagnostic pop
#endif
#endif
#if !defined(SWIFT_TYPEDEFS)
# define SWIFT_TYPEDEFS 1
# if __has_include(<uchar.h>)
# include <uchar.h>
# elif !defined(__cplusplus)
typedef uint_least16_t char16_t;
typedef uint_least32_t char32_t;
# endif
typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
#endif
#if !defined(SWIFT_PASTE)
# define SWIFT_PASTE_HELPER(x, y) x##y
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
#endif
#if !defined(SWIFT_METATYPE)
# define SWIFT_METATYPE(X) Class
#endif
#if !defined(SWIFT_CLASS_PROPERTY)
# if __has_feature(objc_class_property)
# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
# else
# define SWIFT_CLASS_PROPERTY(...)
# endif
#endif
#if !defined(SWIFT_RUNTIME_NAME)
# if __has_attribute(objc_runtime_name)
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
# else
# define SWIFT_RUNTIME_NAME(X)
# endif
#endif
#if !defined(SWIFT_COMPILE_NAME)
# if __has_attribute(swift_name)
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
# else
# define SWIFT_COMPILE_NAME(X)
# endif
#endif
#if !defined(SWIFT_METHOD_FAMILY)
# if __has_attribute(objc_method_family)
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
# else
# define SWIFT_METHOD_FAMILY(X)
# endif
#endif
#if !defined(SWIFT_NOESCAPE)
# if __has_attribute(noescape)
# define SWIFT_NOESCAPE __attribute__((noescape))
# else
# define SWIFT_NOESCAPE
# endif
#endif
#if !defined(SWIFT_RELEASES_ARGUMENT)
# if __has_attribute(ns_consumed)
# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
# else
# define SWIFT_RELEASES_ARGUMENT
# endif
#endif
#if !defined(SWIFT_WARN_UNUSED_RESULT)
# if __has_attribute(warn_unused_result)
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
# else
# define SWIFT_WARN_UNUSED_RESULT
# endif
#endif
#if !defined(SWIFT_NORETURN)
# if __has_attribute(noreturn)
# define SWIFT_NORETURN __attribute__((noreturn))
# else
# define SWIFT_NORETURN
# endif
#endif
#if !defined(SWIFT_CLASS_EXTRA)
# define SWIFT_CLASS_EXTRA
#endif
#if !defined(SWIFT_PROTOCOL_EXTRA)
# define SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_ENUM_EXTRA)
# define SWIFT_ENUM_EXTRA
#endif
#if !defined(SWIFT_CLASS)
# if __has_attribute(objc_subclassing_restricted)
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# else
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
# endif
#endif
#if !defined(SWIFT_RESILIENT_CLASS)
# if __has_attribute(objc_class_stub)
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub))
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME)
# else
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME)
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
# endif
#endif
#if !defined(SWIFT_PROTOCOL)
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
#endif
#if !defined(SWIFT_EXTENSION)
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
#endif
#if !defined(OBJC_DESIGNATED_INITIALIZER)
# if __has_attribute(objc_designated_initializer)
# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
# else
# define OBJC_DESIGNATED_INITIALIZER
# endif
#endif
#if !defined(SWIFT_ENUM_ATTR)
# if __has_attribute(enum_extensibility)
# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
# else
# define SWIFT_ENUM_ATTR(_extensibility)
# endif
#endif
#if !defined(SWIFT_ENUM)
# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# if __has_feature(generalized_swift_name)
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
# else
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
# endif
#endif
#if !defined(SWIFT_UNAVAILABLE)
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
#endif
#if !defined(SWIFT_UNAVAILABLE_MSG)
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
#endif
#if !defined(SWIFT_AVAILABILITY)
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
#endif
#if !defined(SWIFT_WEAK_IMPORT)
# define SWIFT_WEAK_IMPORT __attribute__((weak_import))
#endif
#if !defined(SWIFT_DEPRECATED)
# define SWIFT_DEPRECATED __attribute__((deprecated))
#endif
#if !defined(SWIFT_DEPRECATED_MSG)
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
#endif
#if !defined(SWIFT_DEPRECATED_OBJC)
# if __has_feature(attribute_diagnose_if_objc)
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
# else
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
# endif
#endif
#if defined(__OBJC__)
#if !defined(IBSegueAction)
# define IBSegueAction
#endif
#endif
#if !defined(SWIFT_EXTERN)
# if defined(__cplusplus)
# define SWIFT_EXTERN extern "C"
# else
# define SWIFT_EXTERN extern
# endif
#endif
#if !defined(SWIFT_CALL)
# define SWIFT_CALL __attribute__((swiftcall))
#endif
#if !defined(SWIFT_INDIRECT_RESULT)
# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result))
#endif
#if !defined(SWIFT_CONTEXT)
# define SWIFT_CONTEXT __attribute__((swift_context))
#endif
#if !defined(SWIFT_ERROR_RESULT)
# define SWIFT_ERROR_RESULT __attribute__((swift_error_result))
#endif
#if defined(__cplusplus)
# define SWIFT_NOEXCEPT noexcept
#else
# define SWIFT_NOEXCEPT
#endif
#if !defined(SWIFT_C_INLINE_THUNK)
# if __has_attribute(always_inline)
# if __has_attribute(nodebug)
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug))
# else
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline))
# endif
# else
# define SWIFT_C_INLINE_THUNK inline
# endif
#endif
#if defined(_WIN32)
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport)
#endif
#else
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
# define SWIFT_IMPORT_STDLIB_SYMBOL
#endif
#endif
#if defined(__OBJC__)
#if __has_feature(objc_modules)
#if __has_warning("-Watimport-in-framework-header")
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
#endif
@import AVFoundation;
@import CoreFoundation;
@import CoreMedia;
@import Dispatch;
@import ObjectiveC;
@import UIKit;
#endif
#endif
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
#if __has_warning("-Wpragma-clang-attribute")
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wnullability"
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#if __has_attribute(external_source_symbol)
# pragma push_macro("any")
# undef any
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="VitalSignEngineCore",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
# pragma pop_macro("any")
#endif
#if defined(__OBJC__)
SWIFT_CLASS("_TtC19VitalSignEngineCore12CameraQueues")
@interface CameraQueues : NSObject
/// The serial execution queue for the camera preview layer (input stream) as well as output processing of photos.
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull cameraQueue;)
+ (dispatch_queue_t _Nonnull)cameraQueue SWIFT_WARN_UNUSED_RESULT;
/// The serial execution queue for output processing of videos for recording.
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull videoQueue;)
+ (dispatch_queue_t _Nonnull)videoQueue SWIFT_WARN_UNUSED_RESULT;
/// The serial execution queue for output processing of videos for frame processing.
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull frameProcessorQueue;)
+ (dispatch_queue_t _Nonnull)frameProcessorQueue SWIFT_WARN_UNUSED_RESULT;
/// The serial execution queue for output processing of audio buffers.
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull audioQueue;)
+ (dispatch_queue_t _Nonnull)audioQueue SWIFT_WARN_UNUSED_RESULT;
/// The serial execution queue for output processing of audio buffers.
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull recordingQueue;)
+ (dispatch_queue_t _Nonnull)recordingQueue SWIFT_WARN_UNUSED_RESULT;
/// The serial execution queue for output processing of audio buffers.
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) dispatch_queue_t _Nonnull simulatorQueue;)
+ (dispatch_queue_t _Nonnull)simulatorQueue SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
@class NSCoder;
SWIFT_CLASS("_TtC19VitalSignEngineCore10CameraView")
@interface CameraView : UIView
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) Class _Nonnull layerClass;)
+ (Class _Nonnull)layerClass SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)_ OBJC_DESIGNATED_INITIALIZER SWIFT_UNAVAILABLE;
- (void)willMoveToSuperview:(UIView * _Nullable)newSuperview;
@end
@class UIPinchGestureRecognizer;
@interface CameraView (SWIFT_EXTENSION(VitalSignEngineCore))
- (void)onPinch:(UIPinchGestureRecognizer * _Nonnull)gesture;
- (void)zoomWithFactor:(CGFloat)factor animated:(BOOL)animated;
@end
@class AVCaptureOutput;
@class AVCaptureConnection;
@interface CameraView (SWIFT_EXTENSION(VitalSignEngineCore)) <AVCaptureAudioDataOutputSampleBufferDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>
- (void)captureOutput:(AVCaptureOutput * _Nonnull)captureOutput didOutputSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer fromConnection:(AVCaptureConnection * _Nonnull)_;
@end
SWIFT_CLASS("_TtC19VitalSignEngineCore15VitalSignCamera")
@interface VitalSignCamera : UIView
- (nonnull instancetype)initWithFrame:(CGRect)frame OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder SWIFT_UNAVAILABLE;
- (void)layoutSubviews;
@end
#endif
#if __has_attribute(external_source_symbol)
# pragma clang attribute pop
#endif
#if defined(__cplusplus)
#endif
#pragma clang diagnostic pop
#endif
#else
#error unsupported Swift architecture
#endif

@ -0,0 +1,4 @@
framework module VitalSignEngineCore {
header "VitalSignEngineCore-Swift.h"
requires objc
}

@ -0,0 +1,183 @@
/* Class = "UILabel"; text = "Continuously monitors exposure levels and automatically adjusts exposure when necessary."; ObjectID = "2ad-p5-WH5"; */
"2ad-p5-WH5.text" = "Continuously monitors exposure levels and automatically adjusts exposure when necessary.";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "4ie-UM-xw1"; */
"4ie-UM-xw1.text" = "White Balance";
/* Class = "UILabel"; text = "Front"; ObjectID = "55X-XR-Xoe"; */
"55X-XR-Xoe.text" = "Front";
/* Class = "UITableViewController"; title = "Preview Layer Gravity"; ObjectID = "5qf-l9-Bpt"; */
"5qf-l9-Bpt.title" = "Preview Layer Gravity";
/* Class = "UILabel"; text = "Show the camera preview by preserving its aspect ratio and fits it within the layer's bounds."; ObjectID = "6I3-ak-vEx"; */
"6I3-ak-vEx.text" = "Show the camera preview by preserving its aspect ratio and fits it within the layer's bounds.";
/* Class = "UILabel"; text = "Continuous Auto"; ObjectID = "6yD-61-CEf"; */
"6yD-61-CEf.text" = "Continuous Auto";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "77e-kN-xI5"; */
"77e-kN-xI5.text" = "White Balance";
/* Class = "UILabel"; text = "Preview Gravity"; ObjectID = "7AW-UQ-WfA"; */
"7AW-UQ-WfA.text" = "Preview Gravity";
/* Class = "UILabel"; text = "Lens Aperture"; ObjectID = "7ZB-LO-AYF"; */
"7ZB-LO-AYF.text" = "Lens Aperture";
/* Class = "UINavigationItem"; title = "Preview Gravity"; ObjectID = "7aV-Vw-b2w"; */
"7aV-Vw-b2w.title" = "Preview Gravity";
/* Class = "UINavigationItem"; title = "Camera"; ObjectID = "8D6-fu-CJu"; */
"8D6-fu-CJu.title" = "Camera";
/* Class = "UILabel"; text = "Medium"; ObjectID = "9xa-bl-6eJ"; */
"9xa-bl-6eJ.text" = "Medium";
/* Class = "UITableViewSection"; headerTitle = "Camera"; ObjectID = "AKs-Pq-lcR"; */
"AKs-Pq-lcR.headerTitle" = "Camera";
/* Class = "UILabel"; text = "640x480"; ObjectID = "B6a-KT-T4m"; */
"B6a-KT-T4m.text" = "640x480";
/* Class = "UILabel"; text = "Face Detector"; ObjectID = "Byq-a3-CTH"; */
"Byq-a3-CTH.text" = "Face Detector";
/* Class = "UILabel"; text = "1920x1080"; ObjectID = "Dp9-9b-EEt"; */
"Dp9-9b-EEt.text" = "1920x1080";
/* Class = "UILabel"; text = "A mode that locks the white balance state."; ObjectID = "FOH-2X-R88"; */
"FOH-2X-R88.text" = "A mode that locks the white balance state.";
/* Class = "UILabel"; text = "Duration"; ObjectID = "FPh-cx-ffz"; */
"FPh-cx-ffz.text" = "Duration";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Hmc-Mc-AME"; */
"Hmc-Mc-AME.text" = "Exposure";
/* Class = "UILabel"; text = "Resolution"; ObjectID = "J12-De-6Ah"; */
"J12-De-6Ah.text" = "Resolution";
/* Class = "UITextField"; text = "100"; ObjectID = "J2A-ot-v0j"; */
"J2A-ot-v0j.text" = "100";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "J5t-dB-TfS"; */
"J5t-dB-TfS.text" = "30 fps";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "JgS-aW-Prv"; */
"JgS-aW-Prv.text" = "Frame Rate";
/* Class = "UILabel"; text = "Video Size"; ObjectID = "KJ4-Uw-4AI"; */
"KJ4-Uw-4AI.text" = "Video Size";
/* Class = "UILabel"; text = "Locked"; ObjectID = "Nuf-3J-OLu"; */
"Nuf-3J-OLu.text" = "Locked";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "RKd-LF-IM5"; */
"RKd-LF-IM5.text" = "Frame Rate";
/* Class = "UILabel"; text = "Resize Aspect"; ObjectID = "Rz5-6b-y0h"; */
"Rz5-6b-y0h.text" = "Resize Aspect";
/* Class = "UILabel"; text = "f1.0"; ObjectID = "SxX-36-5NN"; */
"SxX-36-5NN.text" = "f1.0";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Szo-Pj-jno"; */
"Szo-Pj-jno.text" = "Exposure";
/* Class = "UILabel"; text = "ISO"; ObjectID = "TTb-bW-pMN"; */
"TTb-bW-pMN.text" = "ISO";
/* Class = "UILabel"; text = "Camera"; ObjectID = "WaU-wB-ecv"; */
"WaU-wB-ecv.text" = "Camera";
/* Class = "UITableViewController"; title = "White Balance"; ObjectID = "X40-UA-27w"; */
"X40-UA-27w.title" = "White Balance";
/* Class = "UINavigationItem"; title = "Settings"; ObjectID = "Xje-kQ-L0P"; */
"Xje-kQ-L0P.title" = "Settings";
/* Class = "UIBarButtonItem"; title = "Close"; ObjectID = "XvT-YW-xBL"; */
"XvT-YW-xBL.title" = "Close";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YGU-l8-ZMR"; */
"YGU-l8-ZMR.text" = "Locked";
/* Class = "UILabel"; text = "Format"; ObjectID = "YPv-nc-7nK"; */
"YPv-nc-7nK.text" = "Format";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YrA-Y0-l1z"; */
"YrA-Y0-l1z.text" = "Locked";
/* Class = "UINavigationItem"; title = "Format"; ObjectID = "Z09-mH-385"; */
"Z09-mH-385.title" = "Format";
/* Class = "UILabel"; text = "Camera On/Off"; ObjectID = "ZiN-q3-8Cs"; */
"ZiN-q3-8Cs.text" = "Camera On/Off";
/* Class = "UINavigationItem"; title = "White Balance"; ObjectID = "b1A-7E-zBH"; */
"b1A-7E-zBH.title" = "White Balance";
/* Class = "UILabel"; text = "100"; ObjectID = "b1L-VE-boz"; */
"b1L-VE-boz.text" = "100";
/* Class = "UINavigationItem"; title = "Exposure"; ObjectID = "bwF-Fn-XeY"; */
"bwF-Fn-XeY.title" = "Exposure";
/* Class = "UIButton"; configuration.title = "Test"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.configuration.title" = "Test";
/* Class = "UIButton"; normalTitle = "Button"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.normalTitle" = "Test";
/* Class = "UISegmentedControl"; fSu-H9-Cz9.segmentTitles[0] = "sec"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[0]" = "sec";
/* Class = "UISegmentedControl"; fSu-H9-Cz9.segmentTitles[1] = "ms"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[1]" = "ms";
/* Class = "UISegmentedControl"; fSu-H9-Cz9.segmentTitles[2] = "μs"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[2]" = "μs";
/* Class = "UILabel"; text = "Locked"; ObjectID = "fty-cZ-huK"; */
"fty-cZ-huK.text" = "Locked";
/* Class = "UILabel"; text = "Auto (10ms)"; ObjectID = "hVI-M7-yVe"; */
"hVI-M7-yVe.text" = "Auto (10ms)";
/* Class = "UILabel"; text = "Camera Name"; ObjectID = "if3-5B-xPF"; */
"if3-5B-xPF.text" = "Camera Name";
/* Class = "UILabel"; text = "Unit"; ObjectID = "l6D-7r-DH5"; */
"l6D-7r-DH5.text" = "Unit";
/* Class = "UINavigationItem"; title = "Camera Test"; ObjectID = "lZ7-W3-MRS"; */
"lZ7-W3-MRS.title" = "Camera Test";
/* Class = "UILabel"; text = "8ms"; ObjectID = "lct-8a-Lqk"; */
"lct-8a-Lqk.text" = "8ms";
/* Class = "UITableViewController"; title = "Camera"; ObjectID = "lvC-jH-Oee"; */
"lvC-jH-Oee.title" = "Camera";
/* Class = "UILabel"; text = "Resize Aspect Fill"; ObjectID = "mod-K3-RQU"; */
"mod-K3-RQU.text" = "Resize Aspect Fill";
/* Class = "UILabel"; text = "HD 1920x1080"; ObjectID = "nbm-4k-ihb"; */
"nbm-4k-ihb.text" = "HD 1920x1080";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "pKS-rd-Dx6"; */
"pKS-rd-Dx6.text" = "30 fps";
/* Class = "UILabel"; text = "Exposure Duration"; ObjectID = "soE-ah-Y4f"; */
"soE-ah-Y4f.text" = "Exposure Duration";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "u82-lR-lTP"; */
"u82-lR-lTP.text" = "30 fps";
/* Class = "UILabel"; text = "100"; ObjectID = "y9H-1I-dYX"; */
"y9H-1I-dYX.text" = "100";
/* Class = "UILabel"; text = "ISO"; ObjectID = "yOn-QG-KiO"; */
"yOn-QG-KiO.text" = "ISO";

@ -0,0 +1,183 @@
/* Class = "UILabel"; text = "Continuously monitors exposure levels and automatically adjusts exposure when necessary."; ObjectID = "2ad-p5-WH5"; */
"2ad-p5-WH5.text" = "持續監測曝光水平並在必要時自動調整曝光。";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "4ie-UM-xw1"; */
"4ie-UM-xw1.text" = "白平衡";
/* Class = "UILabel"; text = "Front"; ObjectID = "55X-XR-Xoe"; */
"55X-XR-Xoe.text" = "前置";
/* Class = "UITableViewController"; text = "Preview Layer Gravity"; ObjectID = "5qf-l9-Bpt"; */
"5qf-l9-Bpt.title" = "相機預覽展示形式";
/* Class = "UILabel"; text = "Show the camera preview by preserving its aspect ratio and fits it within the layer's bounds."; ObjectID = "6I3-ak-vEx"; */
"6I3-ak-vEx.text" = "按比例顯示相機預覽,並使其限制在圖層的範圍內。";
/* Class = "UILabel"; text = "Continuous Auto"; ObjectID = "6yD-61-CEf"; */
"6yD-61-CEf.text" = "連續自動";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "77e-kN-xI5"; */
"77e-kN-xI5.text" = "白平衡";
/* Class = "UILabel"; text = "Preview Gravity"; ObjectID = "7AW-UQ-WfA"; */
"7AW-UQ-WfA.text" = "相機預覽展示形式";
/* Class = "UILabel"; text = "Lens Aperture"; ObjectID = "7ZB-LO-AYF"; */
"7ZB-LO-AYF.text" = "鏡頭光圈";
/* Class = "UINavigationItem"; text = "Preview Gravity"; ObjectID = "7aV-Vw-b2w"; */
"7aV-Vw-b2w.title" = "相機預覽展示形式";
/* Class = "UINavigationItem"; text = "Camera"; ObjectID = "8D6-fu-CJu"; */
"8D6-fu-CJu.title" = "相機";
/* Class = "UILabel"; text = "Medium"; ObjectID = "9xa-bl-6eJ"; */
"9xa-bl-6eJ.text" = "中等品質";
/* Class = "UITableViewSection"; text = "Camera"; ObjectID = "AKs-Pq-lcR"; */
"AKs-Pq-lcR.headerTitle" = "相機";
/* Class = "UILabel"; text = "640x480"; ObjectID = "B6a-KT-T4m"; */
"B6a-KT-T4m.text" = "640x480";
/* Class = "UILabel"; text = "Face Detector"; ObjectID = "Byq-a3-CTH"; */
"Byq-a3-CTH.text" = "人臉辨識";
/* Class = "UILabel"; text = "1920x1080"; ObjectID = "Dp9-9b-EEt"; */
"Dp9-9b-EEt.text" = "1920x1080";
/* Class = "UILabel"; text = "A mode that locks the white balance state."; ObjectID = "FOH-2X-R88"; */
"FOH-2X-R88.text" = "一種鎖定白平衡狀態的模式。";
/* Class = "UILabel"; text = "Duration"; ObjectID = "FPh-cx-ffz"; */
"FPh-cx-ffz.text" = "持續時間";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Hmc-Mc-AME"; */
"Hmc-Mc-AME.text" = "曝光";
/* Class = "UILabel"; text = "Resolution"; ObjectID = "J12-De-6Ah"; */
"J12-De-6Ah.text" = "解析度";
/* Class = "UITextField"; text = "100"; ObjectID = "J2A-ot-v0j"; */
"J2A-ot-v0j.text" = "100";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "J5t-dB-TfS"; */
"J5t-dB-TfS.text" = "30 fps";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "JgS-aW-Prv"; */
"JgS-aW-Prv.text" = "幀率";
/* Class = "UILabel"; text = "Video Size"; ObjectID = "KJ4-Uw-4AI"; */
"KJ4-Uw-4AI.text" = "視頻尺寸";
/* Class = "UILabel"; text = "Locked"; ObjectID = "Nuf-3J-OLu"; */
"Nuf-3J-OLu.text" = "鎖定";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "RKd-LF-IM5"; */
"RKd-LF-IM5.text" = "幀率";
/* Class = "UILabel"; text = "Resize Aspect"; ObjectID = "Rz5-6b-y0h"; */
"Rz5-6b-y0h.text" = "按比例調整";
/* Class = "UILabel"; text = "f1.0"; ObjectID = "SxX-36-5NN"; */
"SxX-36-5NN.text" = "f1.0";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Szo-Pj-jno"; */
"Szo-Pj-jno.text" = "曝光";
/* Class = "UILabel"; text = "ISO"; ObjectID = "TTb-bW-pMN"; */
"TTb-bW-pMN.text" = "ISO";
/* Class = "UILabel"; text = "Camera"; ObjectID = "WaU-wB-ecv"; */
"WaU-wB-ecv.text" = "相機";
/* Class = "UITableViewController"; text = "White Balance"; ObjectID = "X40-UA-27w"; */
"X40-UA-27w.title" = "白平衡";
/* Class = "UINavigationItem"; text = "Settings"; ObjectID = "Xje-kQ-L0P"; */
"Xje-kQ-L0P.title" = "設定";
/* Class = "UIBarButtonItem"; text = "Close"; ObjectID = "XvT-YW-xBL"; */
"XvT-YW-xBL.title" = "關閉";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YGU-l8-ZMR"; */
"YGU-l8-ZMR.text" = "鎖定";
/* Class = "UILabel"; text = "Format"; ObjectID = "YPv-nc-7nK"; */
"YPv-nc-7nK.text" = "格式";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YrA-Y0-l1z"; */
"YrA-Y0-l1z.text" = "鎖定";
/* Class = "UINavigationItem"; text = "Format"; ObjectID = "Z09-mH-385"; */
"Z09-mH-385.title" = "格式";
/* Class = "UILabel"; text = "Camera On/Off"; ObjectID = "ZiN-q3-8Cs"; */
"ZiN-q3-8Cs.text" = "相機 開/關";
/* Class = "UINavigationItem"; text = "White Balance"; ObjectID = "b1A-7E-zBH"; */
"b1A-7E-zBH.title" = "白平衡";
/* Class = "UILabel"; text = "100"; ObjectID = "b1L-VE-boz"; */
"b1L-VE-boz.text" = "100";
/* Class = "UINavigationItem"; text = "Exposure"; ObjectID = "bwF-Fn-XeY"; */
"bwF-Fn-XeY.title" = "曝光";
/* Class = "UIButton"; text = "Test"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.configuration.title" = "測試";
/* Class = "UIButton"; text = "Button"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.normalTitle" = "測試";
/* Class = "UISegmentedControl"; text = "sec"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[0]" = "秒";
/* Class = "UISegmentedControl"; text = "ms"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[1]" = "毫秒";
/* Class = "UISegmentedControl"; text = "μs"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[2]" = "微秒";
/* Class = "UILabel"; text = "Locked"; ObjectID = "fty-cZ-huK"; */
"fty-cZ-huK.text" = "鎖定";
/* Class = "UILabel"; text = "Auto (10ms)"; ObjectID = "hVI-M7-yVe"; */
"hVI-M7-yVe.text" = "自動10毫秒";
/* Class = "UILabel"; text = "Camera Name"; ObjectID = "if3-5B-xPF"; */
"if3-5B-xPF.text" = "相機名稱";
/* Class = "UILabel"; text = "Unit"; ObjectID = "l6D-7r-DH5"; */
"l6D-7r-DH5.text" = "單位";
/* Class = "UINavigationItem"; text = "Camera Test"; ObjectID = "lZ7-W3-MRS"; */
"lZ7-W3-MRS.title" = "相機測試";
/* Class = "UILabel"; text = "8ms"; ObjectID = "lct-8a-Lqk"; */
"lct-8a-Lqk.text" = "8毫秒";
/* Class = "UITableViewController"; text = "Camera"; ObjectID = "lvC-jH-Oee"; */
"lvC-jH-Oee.title" = "相機";
/* Class = "UILabel"; text = "Resize Aspect Fill"; ObjectID = "mod-K3-RQU"; */
"mod-K3-RQU.text" = "按比例填充";
/* Class = "UILabel"; text = "HD 1920x1080"; ObjectID = "nbm-4k-ihb"; */
"nbm-4k-ihb.text" = "HD 1920x1080";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "pKS-rd-Dx6"; */
"pKS-rd-Dx6.text" = "30 fps";
/* Class = "UILabel"; text = "Exposure Duration"; ObjectID = "soE-ah-Y4f"; */
"soE-ah-Y4f.text" = "曝光時間";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "u82-lR-lTP"; */
"u82-lR-lTP.text" = "30 fps";
/* Class = "UILabel"; text = "100"; ObjectID = "y9H-1I-dYX"; */
"y9H-1I-dYX.text" = "100";
/* Class = "UILabel"; text = "ISO"; ObjectID = "yOn-QG-KiO"; */
"yOn-QG-KiO.text" = "ISO";

@ -0,0 +1,183 @@
/* Class = "UILabel"; text = "Continuously monitors exposure levels and automatically adjusts exposure when necessary."; ObjectID = "2ad-p5-WH5"; */
"2ad-p5-WH5.text" = "持续监测曝光水平并在必要时自动调整曝光。";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "4ie-UM-xw1"; */
"4ie-UM-xw1.text" = "白平衡";
/* Class = "UILabel"; text = "Front"; ObjectID = "55X-XR-Xoe"; */
"55X-XR-Xoe.text" = "前置";
/* Class = "UITableViewController"; text = "Preview Layer Gravity"; ObjectID = "5qf-l9-Bpt"; */
"5qf-l9-Bpt.title" = "相机预览展示形式";
/* Class = "UILabel"; text = "Show the camera preview by preserving its aspect ratio and fits it within the layer's bounds."; ObjectID = "6I3-ak-vEx"; */
"6I3-ak-vEx.text" = "按比例显示相机预览,并使其限制在图层的范围内。";
/* Class = "UILabel"; text = "Continuous Auto"; ObjectID = "6yD-61-CEf"; */
"6yD-61-CEf.text" = "连续自动";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "77e-kN-xI5"; */
"77e-kN-xI5.text" = "白平衡";
/* Class = "UILabel"; text = "Preview Gravity"; ObjectID = "7AW-UQ-WfA"; */
"7AW-UQ-WfA.text" = "相机预览展示形式";
/* Class = "UILabel"; text = "Lens Aperture"; ObjectID = "7ZB-LO-AYF"; */
"7ZB-LO-AYF.text" = "镜头光圈";
/* Class = "UINavigationItem"; text = "Preview Gravity"; ObjectID = "7aV-Vw-b2w"; */
"7aV-Vw-b2w.title" = "相机预览展示形式";
/* Class = "UINavigationItem"; text = "Camera"; ObjectID = "8D6-fu-CJu"; */
"8D6-fu-CJu.title" = "相机";
/* Class = "UILabel"; text = "Medium"; ObjectID = "9xa-bl-6eJ"; */
"9xa-bl-6eJ.text" = "中等品质";
/* Class = "UITableViewSection"; text = "Camera"; ObjectID = "AKs-Pq-lcR"; */
"AKs-Pq-lcR.headerTitle" = "相机";
/* Class = "UILabel"; text = "640x480"; ObjectID = "B6a-KT-T4m"; */
"B6a-KT-T4m.text" = "640x480";
/* Class = "UILabel"; text = "Face Detector"; ObjectID = "Byq-a3-CTH"; */
"Byq-a3-CTH.text" = "人脸识别";
/* Class = "UILabel"; text = "1920x1080"; ObjectID = "Dp9-9b-EEt"; */
"Dp9-9b-EEt.text" = "1920x1080";
/* Class = "UILabel"; text = "A mode that locks the white balance state."; ObjectID = "FOH-2X-R88"; */
"FOH-2X-R88.text" = "一种锁定白平衡状态的模式。";
/* Class = "UILabel"; text = "Duration"; ObjectID = "FPh-cx-ffz"; */
"FPh-cx-ffz.text" = "持续时间";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Hmc-Mc-AME"; */
"Hmc-Mc-AME.text" = "曝光";
/* Class = "UILabel"; text = "Resolution"; ObjectID = "J12-De-6Ah"; */
"J12-De-6Ah.text" = "分辨率";
/* Class = "UITextField"; text = "100"; ObjectID = "J2A-ot-v0j"; */
"J2A-ot-v0j.text" = "100";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "J5t-dB-TfS"; */
"J5t-dB-TfS.text" = "30 fps";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "JgS-aW-Prv"; */
"JgS-aW-Prv.text" = "帧率";
/* Class = "UILabel"; text = "Video Size"; ObjectID = "KJ4-Uw-4AI"; */
"KJ4-Uw-4AI.text" = "视频尺寸";
/* Class = "UILabel"; text = "Locked"; ObjectID = "Nuf-3J-OLu"; */
"Nuf-3J-OLu.text" = "锁定";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "RKd-LF-IM5"; */
"RKd-LF-IM5.text" = "帧率";
/* Class = "UILabel"; text = "Resize Aspect"; ObjectID = "Rz5-6b-y0h"; */
"Rz5-6b-y0h.text" = "按比例调整";
/* Class = "UILabel"; text = "f1.0"; ObjectID = "SxX-36-5NN"; */
"SxX-36-5NN.text" = "f1.0";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Szo-Pj-jno"; */
"Szo-Pj-jno.text" = "曝光";
/* Class = "UILabel"; text = "ISO"; ObjectID = "TTb-bW-pMN"; */
"TTb-bW-pMN.text" = "ISO";
/* Class = "UILabel"; text = "Camera"; ObjectID = "WaU-wB-ecv"; */
"WaU-wB-ecv.text" = "相机";
/* Class = "UITableViewController"; text = "White Balance"; ObjectID = "X40-UA-27w"; */
"X40-UA-27w.title" = "白平衡";
/* Class = "UINavigationItem"; text = "Settings"; ObjectID = "Xje-kQ-L0P"; */
"Xje-kQ-L0P.title" = "设定";
/* Class = "UIBarButtonItem"; text = "Close"; ObjectID = "XvT-YW-xBL"; */
"XvT-YW-xBL.title" = "关闭";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YGU-l8-ZMR"; */
"YGU-l8-ZMR.text" = "鎖定";
/* Class = "UILabel"; text = "Format"; ObjectID = "YPv-nc-7nK"; */
"YPv-nc-7nK.text" = "格式";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YrA-Y0-l1z"; */
"YrA-Y0-l1z.text" = "鎖定";
/* Class = "UINavigationItem"; text = "Format"; ObjectID = "Z09-mH-385"; */
"Z09-mH-385.title" = "格式";
/* Class = "UILabel"; text = "Camera On/Off"; ObjectID = "ZiN-q3-8Cs"; */
"ZiN-q3-8Cs.text" = "相机 开/关";
/* Class = "UINavigationItem"; text = "White Balance"; ObjectID = "b1A-7E-zBH"; */
"b1A-7E-zBH.title" = "白平衡";
/* Class = "UILabel"; text = "100"; ObjectID = "b1L-VE-boz"; */
"b1L-VE-boz.text" = "100";
/* Class = "UINavigationItem"; text = "Exposure"; ObjectID = "bwF-Fn-XeY"; */
"bwF-Fn-XeY.title" = "曝光";
/* Class = "UIButton"; text = "Test"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.configuration.title" = "测试";
/* Class = "UIButton"; text = "Button"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.normalTitle" = "测试";
/* Class = "UISegmentedControl"; text = "sec"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[0]" = "秒";
/* Class = "UISegmentedControl"; text = "ms"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[1]" = "毫秒";
/* Class = "UISegmentedControl"; text = "μs"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[2]" = "微秒";
/* Class = "UILabel"; text = "Locked"; ObjectID = "fty-cZ-huK"; */
"fty-cZ-huK.text" = "鎖定";
/* Class = "UILabel"; text = "Auto (10ms)"; ObjectID = "hVI-M7-yVe"; */
"hVI-M7-yVe.text" = "自动10毫秒";
/* Class = "UILabel"; text = "Camera Name"; ObjectID = "if3-5B-xPF"; */
"if3-5B-xPF.text" = "相机名称";
/* Class = "UILabel"; text = "Unit"; ObjectID = "l6D-7r-DH5"; */
"l6D-7r-DH5.text" = "单位";
/* Class = "UINavigationItem"; text = "Camera Test"; ObjectID = "lZ7-W3-MRS"; */
"lZ7-W3-MRS.title" = "相机测试";
/* Class = "UILabel"; text = "8ms"; ObjectID = "lct-8a-Lqk"; */
"lct-8a-Lqk.text" = "8毫秒";
/* Class = "UITableViewController"; text = "Camera"; ObjectID = "lvC-jH-Oee"; */
"lvC-jH-Oee.title" = "相机";
/* Class = "UILabel"; text = "Resize Aspect Fill"; ObjectID = "mod-K3-RQU"; */
"mod-K3-RQU.text" = "按比例填充";
/* Class = "UILabel"; text = "HD 1920x1080"; ObjectID = "nbm-4k-ihb"; */
"nbm-4k-ihb.text" = "HD 1920x1080";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "pKS-rd-Dx6"; */
"pKS-rd-Dx6.text" = "30 fps";
/* Class = "UILabel"; text = "Exposure Duration"; ObjectID = "soE-ah-Y4f"; */
"soE-ah-Y4f.text" = "曝光时间";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "u82-lR-lTP"; */
"u82-lR-lTP.text" = "30 fps";
/* Class = "UILabel"; text = "100"; ObjectID = "y9H-1I-dYX"; */
"y9H-1I-dYX.text" = "100";
/* Class = "UILabel"; text = "ISO"; ObjectID = "yOn-QG-KiO"; */
"yOn-QG-KiO.text" = "ISO";

@ -0,0 +1,183 @@
/* Class = "UILabel"; text = "Continuously monitors exposure levels and automatically adjusts exposure when necessary."; ObjectID = "2ad-p5-WH5"; */
"2ad-p5-WH5.text" = "持續監測曝光水平並在必要時自動調整曝光。";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "4ie-UM-xw1"; */
"4ie-UM-xw1.text" = "白平衡";
/* Class = "UILabel"; text = "Front"; ObjectID = "55X-XR-Xoe"; */
"55X-XR-Xoe.text" = "前置";
/* Class = "UITableViewController"; text = "Preview Layer Gravity"; ObjectID = "5qf-l9-Bpt"; */
"5qf-l9-Bpt.title" = "相機預覽展示形式";
/* Class = "UILabel"; text = "Show the camera preview by preserving its aspect ratio and fits it within the layer's bounds."; ObjectID = "6I3-ak-vEx"; */
"6I3-ak-vEx.text" = "按比例顯示相機預覽,並使其限制在圖層的範圍內。";
/* Class = "UILabel"; text = "Continuous Auto"; ObjectID = "6yD-61-CEf"; */
"6yD-61-CEf.text" = "連續自動";
/* Class = "UILabel"; text = "White Balance"; ObjectID = "77e-kN-xI5"; */
"77e-kN-xI5.text" = "白平衡";
/* Class = "UILabel"; text = "Preview Gravity"; ObjectID = "7AW-UQ-WfA"; */
"7AW-UQ-WfA.text" = "相機預覽展示形式";
/* Class = "UILabel"; text = "Lens Aperture"; ObjectID = "7ZB-LO-AYF"; */
"7ZB-LO-AYF.text" = "鏡頭光圈";
/* Class = "UINavigationItem"; text = "Preview Gravity"; ObjectID = "7aV-Vw-b2w"; */
"7aV-Vw-b2w.title" = "相機預覽展示形式";
/* Class = "UINavigationItem"; text = "Camera"; ObjectID = "8D6-fu-CJu"; */
"8D6-fu-CJu.title" = "相機";
/* Class = "UILabel"; text = "Medium"; ObjectID = "9xa-bl-6eJ"; */
"9xa-bl-6eJ.text" = "中等品質";
/* Class = "UITableViewSection"; text = "Camera"; ObjectID = "AKs-Pq-lcR"; */
"AKs-Pq-lcR.headerTitle" = "相機";
/* Class = "UILabel"; text = "640x480"; ObjectID = "B6a-KT-T4m"; */
"B6a-KT-T4m.text" = "640x480";
/* Class = "UILabel"; text = "Face Detector"; ObjectID = "Byq-a3-CTH"; */
"Byq-a3-CTH.text" = "人臉辨識";
/* Class = "UILabel"; text = "1920x1080"; ObjectID = "Dp9-9b-EEt"; */
"Dp9-9b-EEt.text" = "1920x1080";
/* Class = "UILabel"; text = "A mode that locks the white balance state."; ObjectID = "FOH-2X-R88"; */
"FOH-2X-R88.text" = "一種鎖定白平衡狀態的模式。";
/* Class = "UILabel"; text = "Duration"; ObjectID = "FPh-cx-ffz"; */
"FPh-cx-ffz.text" = "持續時間";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Hmc-Mc-AME"; */
"Hmc-Mc-AME.text" = "曝光";
/* Class = "UILabel"; text = "Resolution"; ObjectID = "J12-De-6Ah"; */
"J12-De-6Ah.text" = "解析度";
/* Class = "UITextField"; text = "100"; ObjectID = "J2A-ot-v0j"; */
"J2A-ot-v0j.text" = "100";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "J5t-dB-TfS"; */
"J5t-dB-TfS.text" = "30 fps";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "JgS-aW-Prv"; */
"JgS-aW-Prv.text" = "幀率";
/* Class = "UILabel"; text = "Video Size"; ObjectID = "KJ4-Uw-4AI"; */
"KJ4-Uw-4AI.text" = "視頻尺寸";
/* Class = "UILabel"; text = "Locked"; ObjectID = "Nuf-3J-OLu"; */
"Nuf-3J-OLu.text" = "鎖定";
/* Class = "UILabel"; text = "Frame Rate"; ObjectID = "RKd-LF-IM5"; */
"RKd-LF-IM5.text" = "幀率";
/* Class = "UILabel"; text = "Resize Aspect"; ObjectID = "Rz5-6b-y0h"; */
"Rz5-6b-y0h.text" = "按比例調整";
/* Class = "UILabel"; text = "f1.0"; ObjectID = "SxX-36-5NN"; */
"SxX-36-5NN.text" = "f1.0";
/* Class = "UILabel"; text = "Exposure"; ObjectID = "Szo-Pj-jno"; */
"Szo-Pj-jno.text" = "曝光";
/* Class = "UILabel"; text = "ISO"; ObjectID = "TTb-bW-pMN"; */
"TTb-bW-pMN.text" = "ISO";
/* Class = "UILabel"; text = "Camera"; ObjectID = "WaU-wB-ecv"; */
"WaU-wB-ecv.text" = "相機";
/* Class = "UITableViewController"; text = "White Balance"; ObjectID = "X40-UA-27w"; */
"X40-UA-27w.title" = "白平衡";
/* Class = "UINavigationItem"; text = "Settings"; ObjectID = "Xje-kQ-L0P"; */
"Xje-kQ-L0P.title" = "設定";
/* Class = "UIBarButtonItem"; text = "Close"; ObjectID = "XvT-YW-xBL"; */
"XvT-YW-xBL.title" = "關閉";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YGU-l8-ZMR"; */
"YGU-l8-ZMR.text" = "鎖定";
/* Class = "UILabel"; text = "Format"; ObjectID = "YPv-nc-7nK"; */
"YPv-nc-7nK.text" = "格式";
/* Class = "UILabel"; text = "Locked"; ObjectID = "YrA-Y0-l1z"; */
"YrA-Y0-l1z.text" = "鎖定";
/* Class = "UINavigationItem"; text = "Format"; ObjectID = "Z09-mH-385"; */
"Z09-mH-385.title" = "格式";
/* Class = "UILabel"; text = "Camera On/Off"; ObjectID = "ZiN-q3-8Cs"; */
"ZiN-q3-8Cs.text" = "相機 開/關";
/* Class = "UINavigationItem"; text = "White Balance"; ObjectID = "b1A-7E-zBH"; */
"b1A-7E-zBH.title" = "白平衡";
/* Class = "UILabel"; text = "100"; ObjectID = "b1L-VE-boz"; */
"b1L-VE-boz.text" = "100";
/* Class = "UINavigationItem"; text = "Exposure"; ObjectID = "bwF-Fn-XeY"; */
"bwF-Fn-XeY.title" = "曝光";
/* Class = "UIButton"; text = "Test"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.configuration.title" = "測試";
/* Class = "UIButton"; text = "Button"; ObjectID = "cC0-N9-hxt"; */
"cC0-N9-hxt.normalTitle" = "測試";
/* Class = "UISegmentedControl"; text = "sec"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[0]" = "秒";
/* Class = "UISegmentedControl"; text = "ms"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[1]" = "毫秒";
/* Class = "UISegmentedControl"; text = "μs"; ObjectID = "fSu-H9-Cz9"; */
"fSu-H9-Cz9.segmentTitles[2]" = "微秒";
/* Class = "UILabel"; text = "Locked"; ObjectID = "fty-cZ-huK"; */
"fty-cZ-huK.text" = "鎖定";
/* Class = "UILabel"; text = "Auto (10ms)"; ObjectID = "hVI-M7-yVe"; */
"hVI-M7-yVe.text" = "自動10毫秒";
/* Class = "UILabel"; text = "Camera Name"; ObjectID = "if3-5B-xPF"; */
"if3-5B-xPF.text" = "相機名稱";
/* Class = "UILabel"; text = "Unit"; ObjectID = "l6D-7r-DH5"; */
"l6D-7r-DH5.text" = "單位";
/* Class = "UINavigationItem"; text = "Camera Test"; ObjectID = "lZ7-W3-MRS"; */
"lZ7-W3-MRS.title" = "相機測試";
/* Class = "UILabel"; text = "8ms"; ObjectID = "lct-8a-Lqk"; */
"lct-8a-Lqk.text" = "8毫秒";
/* Class = "UITableViewController"; text = "Camera"; ObjectID = "lvC-jH-Oee"; */
"lvC-jH-Oee.title" = "相機";
/* Class = "UILabel"; text = "Resize Aspect Fill"; ObjectID = "mod-K3-RQU"; */
"mod-K3-RQU.text" = "按比例填充";
/* Class = "UILabel"; text = "HD 1920x1080"; ObjectID = "nbm-4k-ihb"; */
"nbm-4k-ihb.text" = "HD 1920x1080";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "pKS-rd-Dx6"; */
"pKS-rd-Dx6.text" = "30 fps";
/* Class = "UILabel"; text = "Exposure Duration"; ObjectID = "soE-ah-Y4f"; */
"soE-ah-Y4f.text" = "曝光時間";
/* Class = "UILabel"; text = "30 fps"; ObjectID = "u82-lR-lTP"; */
"u82-lR-lTP.text" = "30 fps";
/* Class = "UILabel"; text = "100"; ObjectID = "y9H-1I-dYX"; */
"y9H-1I-dYX.text" = "100";
/* Class = "UILabel"; text = "ISO"; ObjectID = "yOn-QG-KiO"; */
"yOn-QG-KiO.text" = "ISO";

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save