Compare commits

..

13 Commits

Author SHA1 Message Date
haroon amjad 6344d78302 Merge branch 'master_merge' 1 week ago
Haroon Amjad 4baf5f9389 Update dependencies and remove unused keys from localization 1 week ago
Haroon6138 cd9f8e0b48 Merge pull request 'flutter_upgrade_with_timesheet_changes' (#21) from flutter_upgrade_with_timesheet_changes into master
Reviewed-on: https://34.17.182.140/Haroon6138/mohemm-flutter-app/pulls/21
1 week ago
Haroon6138 c49872f798 Merge branch 'master' into flutter_upgrade_with_timesheet_changes 1 week ago
Haroon Amjad f503dca074 Merge branch 'apilevel_raise' into master_merge
# Conflicts:
#	android/app/build.gradle
#	android/gradle/wrapper/gradle-wrapper.properties
#	android/settings.gradle
#	assets/langs/ar-SA.json
#	assets/langs/en-US.json
#	lib/api/dashboard_api_client.dart
#	lib/classes/consts.dart
#	lib/config/routes.dart
#	lib/generated/codegen_loader.g.dart
#	lib/generated/locale_keys.g.dart
#	lib/models/generic_response_model.dart
#	lib/ui/landing/dashboard_screen.dart
#	lib/ui/screens/mowadhafhi/mowadhafhi_hr_request.dart
#	pubspec.yaml
1 week ago
haroon amjad 9dcbb0d855 changes for Latest AGP 1 week ago
taha a6be594b3b Update dependencies and improve code structure 2 weeks ago
taha 9284d0e100 chat history maintained 2 weeks ago
Sultan khan 0175c958ef translation added. 2 weeks ago
Sultan khan 3cfe5355ef Merge branch 'master' of http://34.17.182.140/Haroon6138/mohemm-flutter-app into flutter_upgrade_with_timesheet_changes
* 'master' of http://34.17.182.140/Haroon6138/mohemm-flutter-app:
  Update to stores VersionID 10.1
  updates
3 weeks ago
aamir-csol 4f4db082fa Chat Bot Completed 10 months ago
aamir-csol f253a5e99e Chat Bot Completed 10 months ago
aamir-csol 234dce65e5 chat bot & Speech to Text 10 months ago

@ -19,6 +19,11 @@ analyzer:
unnecessary_null_checks: error unnecessary_null_checks: error
unnecessary_brace_in_string_interps: error unnecessary_brace_in_string_interps: error
unnecessary_final: error unnecessary_final: error
exclude:
- build/**
- android/**
- ios/**
- web/**
linter: linter:

@ -18,7 +18,7 @@ if (keystorePropertiesFile.exists()) {
android { android {
namespace 'hmg.cloudSolutions.mohem' namespace 'hmg.cloudSolutions.mohem'
compileSdk 36 compileSdk 37
ndkVersion '28.2.13676358' ndkVersion '28.2.13676358'
@ -34,7 +34,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "hmg.cloudSolutions.mohem" applicationId "hmg.cloudSolutions.mohem"
minSdkVersion 30 minSdkVersion 30
targetSdk 35 targetSdk 37
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode flutter.versionCode versionCode flutter.versionCode
versionName flutter.versionName versionName flutter.versionName
@ -60,12 +60,14 @@ android {
buildTypes { buildTypes {
debug { debug {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
minifyEnabled false
shrinkResources false
} }
release { release {
signingConfig signingConfigs.release signingConfig signingConfigs.release
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles 'proguard-rules.pro'
} }
} }
} }

@ -4,7 +4,6 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
@ -31,11 +30,19 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<queries>
<intent>
<action android:name="android.speech.RecognitionService" />
</intent>
</queries>
<application <application
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="MoheM" android:label="MoheM"
android:extractNativeLibs="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"> android:roundIcon="@mipmap/ic_launcher_round">
<provider <provider

@ -70,7 +70,16 @@ allprojects {
} }
} }
subprojects {
afterEvaluate { project ->
if (project.name == "huawei_push") {
project.android {
compileSdk 36
}
}
}
}
rootProject.buildDir = '../build' rootProject.buildDir = '../build'
subprojects { subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}" project.buildDir = "${rootProject.buildDir}/${project.name}"

@ -1,4 +1,5 @@
org.gradle.jvmargs=-Xmx4096m #org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m -XX:MaxMetaspaceSize=1024m
org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Xmx4096m -Dfile.encoding=UTF-8
kotlin.daemon.jvmargs=-Xmx4096m kotlin.daemon.jvmargs=-Xmx4096m
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true

@ -1,6 +1,6 @@
#Tue Jul 08 11:08:34 AST 2025 #Tue Jul 08 11:08:34 AST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

@ -30,8 +30,10 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.11.0' apply false // id "com.android.application" version '8.11.0' apply false
id("org.jetbrains.kotlin.android") version "2.2.0" apply false // id("org.jetbrains.kotlin.android") version "2.2.0" apply false
id "com.android.application" version '9.0.1' apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
id("com.google.gms.google-services") version "4.4.3" apply false id("com.google.gms.google-services") version "4.4.3" apply false
id("com.google.firebase.crashlytics") version "3.0.4" apply false id("com.google.firebase.crashlytics") version "3.0.4" apply false
id('org.gradle.toolchains.foojay-resolver-convention') version '0.9.0' apply false id('org.gradle.toolchains.foojay-resolver-convention') version '0.9.0' apply false

@ -0,0 +1,46 @@
<svg width="74" height="74" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_119_438)">
<rect x="17" y="17" width="40" height="40" rx="20" fill="url(#paint0_linear_119_438)"/>
</g>
<g filter="url(#filter1_d_119_438)">
<rect x="13" y="13" width="48" height="48" rx="24" fill="url(#paint1_linear_119_438)" fill-opacity="0.4" shape-rendering="crispEdges"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9992 37.5291C40.7637 38.0552 39.4301 38.3796 38.0352 38.4637C37.7946 38.4782 37.5523 38.4856 37.3082 38.4856C37.0642 38.4856 36.8219 38.4782 36.5813 38.4637C35.1861 38.3796 33.8522 38.055 32.6165 37.5287C30.0232 36.4243 27.8623 34.4316 26.4749 31.9094C28.6373 27.9782 32.6791 25.3333 37.3082 25.3333C41.9374 25.3333 45.9792 27.9782 48.1416 31.9094C46.7541 34.4319 44.5928 36.4247 41.9992 37.5291Z" fill="white"/>
<path d="M32.6163 38.5525C32.3175 38.4253 32.0245 38.2863 31.7378 38.136C30.8058 38.8264 30.018 39.6862 29.4248 40.6673C30.4331 42.3347 32.0037 43.6522 33.8885 44.3824C34.7866 44.7303 35.7561 44.9449 36.7701 45.0005H37.8268C38.8406 44.9449 40.6224 46.0011 38.035 48.6666C39.9201 47.9364 44.1637 42.3349 45.1721 40.6673C44.5806 39.689 43.7956 38.8313 42.8671 38.1419C42.5837 38.29 42.2941 38.4272 41.999 38.5529C40.7635 39.079 39.4299 39.4034 38.035 39.4875C37.7945 39.502 37.5521 39.5093 37.3081 39.5093C37.0641 39.5093 36.8217 39.502 36.5811 39.4875C35.1859 39.4034 33.852 39.0788 32.6163 38.5525Z" fill="white"/>
<rect x="32.0844" y="30.2271" width="10.4705" height="3.93258" rx="1.96629" fill="#162550"/>
<ellipse cx="40.2071" cy="32.1717" rx="0.685571" ry="0.720974" fill="#04FED1"/>
<ellipse cx="37.2984" cy="41.8719" rx="0.685571" ry="0.720974" fill="#162550"/>
<ellipse cx="34.5566" cy="32.1717" rx="0.685571" ry="0.720974" fill="#04FED1"/>
<ellipse cx="34.5566" cy="41.8719" rx="0.685571" ry="0.720974" fill="#162550"/>
<ellipse cx="40.0411" cy="41.8719" rx="0.685571" ry="0.720974" fill="#162550"/>
<defs>
<filter id="filter0_d_119_438" x="4" y="4" width="66" height="66" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="6.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0784314 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_119_438"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_119_438" result="shape"/>
</filter>
<filter id="filter1_d_119_438" x="0" y="0" width="74" height="74" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="6.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.0784314 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_119_438"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_119_438" result="shape"/>
</filter>
<linearGradient id="paint0_linear_119_438" x1="-12.3178" y1="86.2302" x2="-12.3178" y2="18.3971" gradientUnits="userSpaceOnUse">
<stop offset="1" stop-color="#259CB8"/>
<stop stop-color="#32D892"/>
</linearGradient>
<linearGradient id="paint1_linear_119_438" x1="-22.1814" y1="96.0762" x2="-22.1814" y2="14.6765" gradientUnits="userSpaceOnUse">
<stop offset="1" stop-color="#259CB8"/>
<stop stop-color="#32D892"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

@ -1,4 +0,0 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3315 5.33146C10.5145 5.1484 10.5145 4.8516 10.3315 4.66854C10.1484 4.48549 9.8516 4.48549 9.66854 4.66854L7.50009 6.83699L5.95644 5.29352C5.77337 5.11048 5.47657 5.11049 5.29352 5.29356C5.11048 5.47663 5.11049 5.77343 5.29356 5.95648L6.83718 7.49991L6.54354 7.79354C6.36049 7.9766 6.36049 8.2734 6.54354 8.45646C6.7266 8.63951 7.0234 8.63951 7.20646 8.45646L7.50013 8.16278L7.79364 8.45625C7.97671 8.6393 8.27351 8.63928 8.45655 8.45621C8.6396 8.27314 8.63958 7.97634 8.45651 7.79329L8.16305 7.49987L10.3315 5.33146Z" fill="#047857"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.53573 1.09375H7.46427C6.09508 1.09374 5.01965 1.09374 4.18001 1.20662C3.3196 1.3223 2.63694 1.5642 2.10057 2.10057C1.5642 2.63694 1.3223 3.3196 1.20662 4.18001C1.09374 5.01965 1.09374 6.09508 1.09375 7.46427V7.53573C1.09374 8.90492 1.09374 9.98035 1.20662 10.82C1.3223 11.6804 1.5642 12.3631 2.10057 12.8994C2.63694 13.4358 3.3196 13.6777 4.18001 13.7934C5.01965 13.9063 6.09507 13.9063 7.46425 13.9062H7.53573C8.90491 13.9063 9.98035 13.9063 10.82 13.7934C11.6804 13.6777 12.3631 13.4358 12.8994 12.8994C13.4358 12.3631 13.6777 11.6804 13.7934 10.82C13.9063 9.98035 13.9063 8.90493 13.9062 7.53575V7.46427C13.9063 6.09509 13.9063 5.01965 13.7934 4.18001C13.6777 3.3196 13.4358 2.63694 12.8994 2.10057C12.3631 1.5642 11.6804 1.3223 10.82 1.20662C9.98035 1.09374 8.90492 1.09374 7.53573 1.09375ZM2.76348 2.76348C3.09664 2.43033 3.54848 2.23746 4.30493 2.13576C5.07487 2.03225 6.08727 2.03125 7.5 2.03125C8.91273 2.03125 9.92513 2.03225 10.6951 2.13576C11.4515 2.23746 11.9034 2.43033 12.2365 2.76348C12.5697 3.09664 12.7625 3.54848 12.8642 4.30493C12.9678 5.07487 12.9688 6.08727 12.9688 7.5C12.9688 8.91273 12.9678 9.92513 12.8642 10.6951C12.7625 11.4515 12.5697 11.9034 12.2365 12.2365C11.9034 12.5697 11.4515 12.7625 10.6951 12.8642C9.92513 12.9678 8.91273 12.9688 7.5 12.9688C6.08727 12.9688 5.07487 12.9678 4.30493 12.8642C3.54848 12.7625 3.09664 12.5697 2.76348 12.2365C2.43033 11.9034 2.23746 11.4515 2.13576 10.6951C2.03225 9.92513 2.03125 8.91273 2.03125 7.5C2.03125 6.08727 2.03225 5.07487 2.13576 4.30493C2.23746 3.54848 2.43033 3.09664 2.76348 2.76348Z" fill="#047857"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 B

@ -1,5 +0,0 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 9.1875C7.656 9.1875 6.5625 8.094 6.5625 6.75C6.5625 5.406 7.656 4.3125 9 4.3125C10.344 4.3125 11.4375 5.406 11.4375 6.75C11.4375 8.094 10.344 9.1875 9 9.1875ZM9 5.4375C8.27625 5.4375 7.6875 6.02625 7.6875 6.75C7.6875 7.47375 8.27625 8.0625 9 8.0625C9.72375 8.0625 10.3125 7.47375 10.3125 6.75C10.3125 6.02625 9.72375 5.4375 9 5.4375Z" fill="#737373"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.66722 13.5263C8.02722 13.872 8.50047 14.0625 8.99997 14.0625C9.49947 14.0625 9.97272 13.872 10.3327 13.5255C10.5082 13.3568 10.6882 13.1858 10.8705 13.0133C13.1174 10.8818 15.9149 8.22811 14.3692 4.51199C13.4805 2.37374 11.322 0.9375 8.99997 0.9375C6.67797 0.9375 4.52021 2.37374 3.63146 4.51199C2.09096 8.21474 4.86521 10.8555 7.09421 12.9773L7.11578 12.9978C7.30192 13.1753 7.48597 13.3508 7.66722 13.5263ZM4.67022 4.94399C5.38647 3.22049 7.12647 2.0625 8.99997 2.0625C10.8735 2.0625 12.6142 3.22049 13.3312 4.94399C14.5792 7.94549 12.3007 10.107 10.0965 12.1972C10.0547 12.237 10.0129 12.2766 9.97134 12.3162C9.83004 12.4504 9.69004 12.5834 9.55273 12.7155C9.40348 12.8588 9.20697 12.9375 8.99997 12.9375C8.79297 12.9375 8.59646 12.858 8.44871 12.7163C8.25971 12.5333 8.06623 12.3487 7.86973 12.162C5.68581 10.0831 3.42677 7.93263 4.67022 4.94399Z" fill="#737373"/>
<path d="M11.0385 17.0625H6.96142C4.53817 17.0625 3.32169 17.0625 2.72319 16.1745C2.66544 16.0882 2.61517 15.999 2.57392 15.909C2.11666 14.93 2.85295 13.8806 3.87139 12.429L3.87295 12.4267C4.05145 12.1725 4.40243 12.111 4.65668 12.2895C4.91093 12.468 4.97243 12.819 4.79393 13.0733C3.98468 14.2268 3.42443 15.072 3.59543 15.4373C3.61343 15.4763 3.63367 15.5115 3.65692 15.5468C3.92017 15.9375 5.12991 15.9375 6.96138 15.9375H11.0385C12.8699 15.9375 14.0797 15.9375 14.3437 15.546C14.3669 15.5115 14.3872 15.4762 14.4029 15.441C14.5754 15.072 14.0152 14.2268 13.2059 13.0733C13.0274 12.819 13.0889 12.468 13.3432 12.2895C13.5974 12.111 13.9484 12.1725 14.1269 12.4267C15.1462 13.8795 15.8827 14.9295 15.4244 15.9127C15.3847 15.999 15.3352 16.0882 15.2775 16.1737C14.6782 17.0625 13.4617 17.0625 11.0385 17.0625Z" fill="#737373"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1,12 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_120_1007)">
<path d="M13.125 5C13.125 3.27411 11.7259 1.875 10 1.875C8.27411 1.875 6.875 3.27411 6.875 5V10C6.875 11.7259 8.27411 13.125 10 13.125C11.7259 13.125 13.125 11.7259 13.125 10V5Z" stroke="#697586" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 15.625V18.75" stroke="#697586" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15.625 10C15.625 11.4918 15.0324 12.9226 13.9775 13.9775C12.9226 15.0324 11.4918 15.625 10 15.625C8.50816 15.625 7.07742 15.0324 6.02252 13.9775C4.96763 12.9226 4.375 11.4918 4.375 10" stroke="#697586" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_120_1007">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 903 B

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.8744 6.13098L7.58175 10.4694L2.69958 7.41575C2.00006 6.97811 2.14558 5.91558 2.93679 5.6842L14.5284 2.28958C15.253 2.07722 15.9244 2.75462 15.7092 3.4815L12.2798 15.0651C12.0448 15.8574 10.9884 15.999 10.5549 15.2965L7.5795 10.4701" stroke="#0D121C" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 434 B

@ -1,6 +0,0 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.8848 0.782465C11.6672 0.777969 11.077 0.780422 10.3952 0.783256L10.074 0.784571C9.25489 0.787826 8.36176 0.790261 7.8034 0.782363L3.41832 0.782363C3.30775 0.782363 3.09409 0.784794 2.86663 0.826585C2.65209 0.866 2.34887 0.953285 2.12257 1.17828C2.01295 1.28728 1.92531 1.4256 1.85771 1.5488C1.78696 1.67774 1.72248 1.81951 1.66613 1.95532C1.55744 2.2173 1.46792 2.48443 1.41557 2.64068L1.41079 2.65493L0.986568 3.88476C0.919741 4.06317 0.849387 4.33704 0.811103 4.61007C0.791433 4.75035 0.778628 4.90141 0.781624 5.04747C0.784495 5.18744 0.802226 5.35457 0.864472 5.50929C0.963954 5.75657 1.18158 6.01838 1.36007 6.20866C1.36856 6.21771 1.3771 6.22675 1.38569 6.23576C1.38552 6.24088 1.38544 6.24602 1.38544 6.25118L1.38544 9.41047C1.38543 10.2652 1.38541 10.9542 1.45827 11.496C1.5339 12.0586 1.69571 12.5322 2.0719 12.9084C2.4481 13.2846 2.92177 13.4465 3.48434 13.5221C4.0262 13.595 4.71515 13.5949 5.5699 13.5949L7.47919 13.5949C7.73807 13.5949 7.94794 13.3851 7.94794 13.1262C7.94794 12.8673 7.73807 12.6574 7.47919 12.6574L5.60419 12.6574C4.70705 12.6574 4.08136 12.6564 3.60926 12.593C3.15066 12.5313 2.9078 12.4185 2.73482 12.2455C2.56184 12.0726 2.44906 11.8297 2.38741 11.3711C2.32393 10.899 2.32294 10.2733 2.32294 9.37618L2.32294 6.97328C2.45051 7.03849 2.59529 7.10093 2.75359 7.15501C3.17457 7.29882 3.7203 7.39381 4.3406 7.31265C4.89853 7.23967 5.351 7.00851 5.70918 6.71013C6.09419 7.00458 6.70331 7.30587 7.47811 7.31657C8.04917 7.32446 8.56216 7.12979 8.95868 6.90312C9.08184 6.83271 9.19674 6.75753 9.30188 6.68132C9.77763 7.08237 10.6727 7.56818 11.9991 7.20432C13.5774 6.77137 14.083 5.75144 14.209 5.1425C14.2154 5.11127 14.2187 5.07946 14.2187 5.04757C14.2187 4.75732 14.1289 4.33991 13.9659 3.80861C13.7986 3.26346 13.5405 2.55589 13.1738 1.66657C13.1701 1.65753 13.166 1.6486 13.1618 1.63981C13.0956 1.50407 12.9647 1.3046 12.7653 1.13192C12.5616 0.95548 12.2655 0.790329 11.8848 0.782465ZM7.49106 6.37916C6.97962 6.3721 6.58006 6.18008 6.33319 6.00595C6.55391 5.67199 6.6951 5.33553 6.76663 5.07693C6.83564 4.82742 6.68931 4.5692 6.4398 4.50019C6.19028 4.43117 5.93206 4.5775 5.86305 4.82702C5.70947 5.3823 5.16544 6.25926 4.21899 6.38308C3.76402 6.4426 3.36434 6.37295 3.05665 6.26784C2.73842 6.15914 2.54601 6.02235 2.50231 5.98113C2.48528 5.96506 2.46707 5.9503 2.44784 5.93696C2.37021 5.88311 2.20722 5.74146 2.04383 5.56728C1.87113 5.38316 1.76292 5.23071 1.73421 5.15935C1.73421 5.15935 1.73024 5.14811 1.72656 5.12505C1.72269 5.10085 1.71976 5.0687 1.71893 5.02824C1.71725 4.94644 1.72447 4.84757 1.73952 4.74025C1.77015 4.52178 1.82659 4.31346 1.86579 4.21019C1.86748 4.20572 1.86911 4.20123 1.87067 4.19671L2.29773 2.95872L2.30261 2.94414C2.3555 2.78638 2.43617 2.5457 2.53206 2.3146C2.58103 2.19657 2.63099 2.0884 2.67962 1.99977C2.73141 1.90538 2.7681 1.85849 2.78357 1.8431C2.80747 1.81934 2.88262 1.77684 3.03603 1.74865C3.17652 1.72284 3.32038 1.71986 3.41832 1.71986L7.79347 1.71986C8.36231 1.72785 9.26287 1.7253 10.0778 1.72206L10.3974 1.72076C11.0847 1.71792 11.6606 1.71553 11.8655 1.71977C11.9719 1.72196 12.0657 1.76626 12.1515 1.84057C12.2347 1.91263 12.2907 1.99706 12.314 2.0407C12.6704 2.90597 12.9148 3.57905 13.0696 4.08359C13.214 4.55415 13.2701 4.84632 13.2797 5.00173C13.1969 5.33137 12.8878 5.98842 11.7511 6.30022C10.8759 6.54033 10.2955 6.26948 9.96995 6.01784C10.0887 5.82356 10.2297 5.52285 10.3667 5.09485C10.4456 4.84828 10.3097 4.58443 10.0631 4.50553C9.81653 4.42662 9.55268 4.56254 9.47378 4.8091C9.30754 5.32855 9.16081 5.54992 9.1304 5.58829C9.02102 5.71208 8.79354 5.91764 8.4934 6.08922C8.18993 6.2627 7.84298 6.38402 7.49106 6.37916Z" fill="#C2410C"/>
<path d="M3.88544 10.6262C3.88544 10.3673 4.0953 10.1574 4.35419 10.1574L6.85419 10.1574C7.11307 10.1574 7.32294 10.3673 7.32294 10.6262C7.32294 10.8851 7.11307 11.0949 6.85419 11.0949L4.35419 11.0949C4.0953 11.0949 3.88544 10.8851 3.88544 10.6262Z" fill="#C2410C"/>
<path d="M10.9167 10.6262C10.9167 10.281 11.1965 10.0012 11.5417 10.0012L11.5473 10.0012C11.8925 10.0012 12.1723 10.281 12.1723 10.6262C12.1723 10.9714 11.8925 11.2512 11.5473 11.2512L11.5417 11.2512C11.1965 11.2512 10.9167 10.9714 10.9167 10.6262Z" fill="#C2410C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5624 7.96748C10.1037 7.96748 8.90619 9.13055 8.90619 10.5835C8.90619 11.4113 9.32257 12.0297 9.75433 12.5247C9.9082 12.7011 10.0773 12.876 10.2353 13.0394C10.2951 13.1013 10.3536 13.1618 10.4088 13.2199C10.619 13.4408 10.7995 13.6441 10.9391 13.8516C11.0558 14.0251 11.2645 14.2195 11.5661 14.2195C11.8728 14.2195 12.0808 14.0193 12.1939 13.8391C12.3264 13.6278 12.5016 13.425 12.7082 13.2072C12.7757 13.1361 12.85 13.0599 12.9271 12.981C13.0706 12.8339 13.2237 12.677 13.3595 12.525C13.7946 12.038 14.2187 11.4281 14.2187 10.5835C14.2187 9.13055 13.0212 7.96748 11.5624 7.96748ZM9.84369 10.5835C9.84369 9.66465 10.605 8.90498 11.5624 8.90498C12.5199 8.90498 13.2812 9.66465 13.2812 10.5835C13.2812 11.0803 13.0441 11.4709 12.6604 11.9004C12.5358 12.0398 12.4117 12.1667 12.28 12.3015L12.2796 12.302C12.1992 12.3842 12.1159 12.4694 12.028 12.562C11.869 12.7297 11.7058 12.9111 11.5583 13.1097C11.4095 12.9183 11.2464 12.7402 11.0881 12.5738C11.0212 12.5034 10.956 12.436 10.8923 12.3701C10.7408 12.2134 10.5979 12.0656 10.4608 11.9084C10.0786 11.4703 9.84369 11.0692 9.84369 10.5835Z" fill="#C2410C"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 29 KiB

@ -381,7 +381,7 @@
"browseCategories": "تصفح الفئات", "browseCategories": "تصفح الفئات",
"searchItems": "عناصر البحث", "searchItems": "عناصر البحث",
"offerAndDiscounts": "العروض والخصومات", "offerAndDiscounts": "العروض والخصومات",
"offerValid": "العرض ساري", "offerValid": "العرض صالح",
"offerExpired": "انتهى العرض", "offerExpired": "انتهى العرض",
"whatAreYouOffering": "ما الذي تعرضه؟", "whatAreYouOffering": "ما الذي تعرضه؟",
"selectCategory": "اختر الفئة", "selectCategory": "اختر الفئة",
@ -552,6 +552,9 @@
"pleaseClickButtonToJoinMarathon": "الرجاء الضغط على الزر أدناه للانضمام إلى الماراثون", "pleaseClickButtonToJoinMarathon": "الرجاء الضغط على الزر أدناه للانضمام إلى الماراثون",
"youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني", "youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني",
"open": "يفتح", "open": "يفتح",
"youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني",
"pleaseClickButtonToJoinMarathon": "الرجاء الضغط على الزر أدناه للانضمام إلى الماراثون",
"generate": "يولد",
"paymentRequest": "طلب الدفع", "paymentRequest": "طلب الدفع",
"paymentDetails": "تفاصيل الدفع", "paymentDetails": "تفاصيل الدفع",
"requestNo": "رقم الطلب", "requestNo": "رقم الطلب",
@ -602,15 +605,6 @@
"addUsers": "إضافة مستخدمين إلى المجموعة", "addUsers": "إضافة مستخدمين إلى المجموعة",
"admin": "مشرف", "admin": "مشرف",
"areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟", "areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟",
"confirmGrantMazayaAccess": "هل أنت متأكد أنك تريد منح صلاحية مزايا لـ \"{}\"؟",
"confirmRevokeMazayaAccess": "هل أنت متأكد أنك تريد إلغاء صلاحية مزايا لـ \"{}\"؟",
"yes": "نعم",
"no": "لا",
"mazayaInviteInstructions": "يرجى تحميل تطبيق مهمم من الرابط أعلاه واستخدام بيانات الاعتماد التالية لتسجيل الدخول",
"cameraPermissionRequiredToScan": "يرجى منح صلاحية الكاميرا لمسح رمز الاستجابة السريعة",
"cameraPermissionPermanentlyDenied": "يرجى تفعيل صلاحية الكاميرا من إعدادات التطبيق > الصلاحيات",
"openSettings": "فتح الإعدادات",
"nationalIdOrIqamaFallback": "رقم الهوية الوطنية أو رقم الإقامة الخاص بك",
"Attendance": "الحضور", "Attendance": "الحضور",
"audioCall": "مكالمة صوتية", "audioCall": "مكالمة صوتية",
"editGroups": "تعديل المجموعة", "editGroups": "تعديل المجموعة",
@ -676,7 +670,7 @@
"ticketNumber": "رقم التذكرة", "ticketNumber": "رقم التذكرة",
"ticketId": "معرف التذكرة", "ticketId": "معرف التذكرة",
"pleaseSelectProject": "الرجاء اختيار مشروع", "pleaseSelectProject": "الرجاء اختيار مشروع",
"viewAttachment": "عرض المرفق", "viewAttachment" : "عرض المرفق",
"parkingQr": "وقوف السيارات QR", "parkingQr": "وقوف السيارات QR",
"startNow": "ابدأ الآن", "startNow": "ابدأ الآن",
"destination_type": "نوع الوجهة", "destination_type": "نوع الوجهة",
@ -686,122 +680,15 @@
"priceSAR": "السعر (ريال سعودي)", "priceSAR": "السعر (ريال سعودي)",
"amountSAR": "المبلغ (ريال سعودي)", "amountSAR": "المبلغ (ريال سعودي)",
"amuLastThreeMonths": "AMU (آخر 3 أشهر)", "amuLastThreeMonths": "AMU (آخر 3 أشهر)",
"buyerDetails": "تفاصيل المشتري",
"chatBot": "المساعد الذكي",
"helloAskMe": "مرحباً، اسألني",
"anything": "عن أي شيء...",
"mazayaTitle": "مزايا", "thinking": "جاري التفكير",
"mazayaAccessGranted": "تم منح حق الوصول لمزايا", "waitingForResponse": "في انتظار الرد...",
"mazayaAccessGrantedDesc": "أصبح لدى {} حق وصول نشط الآن. شارك رابط التنزيل أدناه ليتمكن من تثبيت تطبيق مهم وإكمال الإعداد.", "typeAMessage": "اكتب رسالة...",
"accessIsNowActive": "الوصول نشط الآن", "initializingChatPleaseWait": "يرجى الانتظار، جاري تهيئة المحادثة...",
"accessIsNowActiveDesc": "يمكن لـ {} استخدام الرابط المرفق لتنزيل تطبيق مزايا ومتابعة الإعداد على جهازه.", "am": "ص",
"dependentAccessActive": "وصول التابع نشط", "pm": "م"
"shareableAppDownloadLink": "رابط تنزيل التطبيق للمشاركة",
"shareableAppDownloadLinkDesc": "أرسل هذا الرابط إلى {} ليتمكن من تنزيل تطبيق مزايا ومتابعة الإعداد.",
"downloadLink": "رابط التنزيل",
"shareDownloadLink": "مشاركة رابط التنزيل",
"copyLink": "نسخ الرابط",
"done": "تم",
"linkCopied": "تم نسخ الرابط بنجاح",
"mazayaAccessRevoked": "تم إلغاء الوصول لمزايا",
"mazayaAccessRevokedDesc": "قام الكفيل بإلغاء وصول التابع. لم يعد بالإمكان فتح العروض أو تسجيل الدخول بالبصمة.",
"contactYourSponsor": "تواصل مع الكفيل",
"contactYourSponsorDesc": "اطلب من كفيلك السماح بالوصول لمزايا مرة أخرى من تفاصيل التابعين. يمكن للكفيل فقط استعادة الوصول.",
"dependentAccessBlocked": "تم حظر وصول التابع",
"returnToSignIn": "العودة إلى تسجيل الدخول",
"allowMazayaAccess": "السماح بالوصول لمزايا",
"revokeMazayaAccess": "إلغاء الوصول لمزايا",
"active": "نشط",
"noAccess": "غير متاح",
"offerDetails": "تفاصيل العرض",
"offerIdNotAvailable": "معرف العرض غير متوفر",
"unableToRedeemOffer": "تعذر الحصول على رابط العرض، يرجى المحاولة لاحقاً",
"errorRedeemingOffer": "حدث خطأ أثناء استخدام العرض",
"useOffer": "استخدام العرض",
"online": "عبر الإنترنت",
"inStore": "في المتجر",
"ksa": "المملكة",
"riyadh": "الرياض",
"noInstructionsAvailable": "لا توجد طريقة استخدام محددة.",
"contactInfo": "معلومات التواصل",
"allCategories": "الكل",
"noOffersAvailable": "لا توجد عروض متاحة",
"addDependents": "إضافة تابعين",
"myDependents": "التابعون",
"offDiscount": "خصم {}",
"usageLabel": "الاستخدام: ",
"areYouSureWantToLogOut": "هل أنت متأكد من تسجيل الخروج؟",
"searchDependents": "البحث في التابعين",
"searchFamilyMembers": "البحث في أفراد العائلة",
"addAnotherUser": "إضافة مستخدم آخر",
"joinMazaya": "انضم إلى مزايا",
"joinMazayaSubtitle": "احصل على أفضل العروض والصفقات الحصرية في المطاعم والتسوق ومكافآت نمط الحياة.",
"continueAsEmployee": "المتابعة كموظف",
"continueAsEmployeeSubtitle": "سجل الدخول باستخدام اسم المستخدم وكلمة المرور الخاصة بالموظف.",
"pleaseEnterNationalId": "الرجاء إدخال رقم الهوية الوطنية",
"pleaseSelectDob": "الرجاء تحديد تاريخ الميلاد",
"unableToRetrieveDependentInfo": "تعذر الحصول على معلومات التابع",
"failedToSendOtp": "فشل إرسال رمز التحقق",
"verificationCodeResent": "تم إعادة إرسال رمز التحقق",
"nationalId": "الهوية الوطنية",
"addDependent": "إضافة تابع",
"stepVerify": "التحقق",
"stepBasic": "الأساسية",
"stepAdditional": "إضافية",
"findYourDependent": "البحث عن تابعك",
"findYourDependentDesc": "أدخل التفاصيل تماماً كما تظهر في الهوية الوطنية. سنقوم باسترداد السجل بأمان من مركز المعلومات الوطني (NHIC).",
"enter10DigitNationalId": "أدخل رقم الهوية المكون من 10 أرقام",
"nationalIdMustBe10Digits": "يجب أن يتكون رقم الهوية من 10 أرقام",
"dateFormatPlaceholder": "يوم / شهر / سنة",
"searchUser": "بحث عن المستخدم",
"dependentNotFoundNhic": "لم يتم العثور على سجل لهذا التابع في مركز المعلومات الوطني",
"basicInfo": "المعلومات الأساسية",
"basicInfoDesc": "بيانات الهوية المطلوبة المستردة من مركز المعلومات الوطني.",
"additionalInfo": "معلومات إضافية",
"additionalInfoDesc": "إضافة أو مراجعة بيانات الكفالة والأهلية.",
"select": "اختر",
"enterFieldPlaceholder": "أدخل {}",
"requiredField": "الحقل المطلوب",
"pleaseFillField": "الرجاء إدخال {}",
"reviewAndSubmit": "المراجعة والإرسال",
"reviewAndSubmitDesc": "يرجى مراجعة تفاصيل التابع بعناية قبل تأكيد الإرسال.",
"arabicName": "الاسم بالعربية",
"relationship": "صلة القرابة",
"sponsor": "الكفيل",
"dependentSponsorDetail": "تفاصيل كفيل التابع",
"insuranceEligibility": "أهلية التأمين",
"relationshipStartDate": "تاريخ بدء العلاقة",
"dependentAddedSuccessfully": "تم إرسال الطلب بنجاح",
"genderMale": "ذكر",
"genderFemale": "أنثى",
"religionIslam": "الإسلام",
"back": "السابق",
"submitRequest": "إرسال الطلب",
"enterDependentDetailsToContinue": "أدخل تفاصيل التابع للمتابعة.",
"enterBasicDetailsDesc": "يرجى إدخال البيانات الأساسية للتابع.",
"locations": "الفروع والمواقع",
"availableOffers": "العروض المتاحة",
"selectOfferToRedeem": "اختر العرض الذي ترغب في استخدامه",
"viewOnMap": "عرض على الخريطة",
"showAllLocations": "عرض جميع الفروع ({})",
"searchBranches": "البحث عن الفروع أو المدن...",
"allCities": "الكل",
"noBranchesFound": "لم يتم العثور على فروع مطابقة",
"browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن",
"branchesCount": "{} فرع",
"kmAway": "على بعد {} كم",
"dependentDetails": "تفاصيل التابع",
"keyDetails": "البيانات الأساسية",
"verified": "موثق",
"idEnding": "الهوية تنتهي بـ {}",
"servicesAndActions": "الخدمات والإجراءات",
"startDependentFlowsDesc": "بدء إجراءات التابع وإدارة الوصول من مكان واحد.",
"mazayaAccess": "الوصول إلى مزايا",
"accessOff": "معطل",
"allowDependentMazayaOffersDesc": "السماح لـ {} باستخدام عروض مزايا بهويتها الموثقة.",
"loginMethod": "طريقة تسجيل الدخول",
"nationalIdAndDob": "الهوية الوطنية وتاريخ الميلاد",
"sponsorOtp": "رمز التحقق للكفيل",
"revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى."
} }

@ -549,15 +549,6 @@
"manage": "Manage", "manage": "Manage",
"members": "Members", "members": "Members",
"areYouSureWantTodelete": "Are you sure want to delete?", "areYouSureWantTodelete": "Are you sure want to delete?",
"confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?",
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login",
"cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings",
"nationalIdOrIqamaFallback": "Your National ID or Iqama Number",
"groupMembers": "Group Members", "groupMembers": "Group Members",
"manageGroup": "Manage Group", "manageGroup": "Manage Group",
"admin": "Admin", "admin": "Admin",
@ -579,6 +570,7 @@
"pleaseClickButtonToJoinMarathon": "Press the button below to join the Marathon.", "pleaseClickButtonToJoinMarathon": "Press the button below to join the Marathon.",
"youCannotJoinTheMarathon": "You cannot join the Marathon because you have exceeded the time limit.", "youCannotJoinTheMarathon": "You cannot join the Marathon because you have exceeded the time limit.",
"requesterOperatingUnit": "Requester Operating Unit", "requesterOperatingUnit": "Requester Operating Unit",
"generate": "Generate",
"paymentRequest": "Pay Request", "paymentRequest": "Pay Request",
"paymentDetails": "Payment Details", "paymentDetails": "Payment Details",
"requestNo": "Request No", "requestNo": "Request No",
@ -676,7 +668,7 @@
"ticketNumber": "Ticket Number", "ticketNumber": "Ticket Number",
"ticketId": "Ticket ID", "ticketId": "Ticket ID",
"pleaseSelectProject": "Please select a project", "pleaseSelectProject": "Please select a project",
"viewAttachment": "View Attachment", "viewAttachment" : "View Attachment",
"parkingQr": "Parking QR", "parkingQr": "Parking QR",
"startNow": "Start Now", "startNow": "Start Now",
"destination_type": "Destination Type", "destination_type": "Destination Type",
@ -686,123 +678,14 @@
"priceSAR": "Price (SAR)", "priceSAR": "Price (SAR)",
"amountSAR": "Amount (SAR)", "amountSAR": "Amount (SAR)",
"amuLastThreeMonths": "AMU (Last 3 months)", "amuLastThreeMonths": "AMU (Last 3 months)",
"buyerDetails": "Buyer Details",
"chatBot": "ChatBot",
"helloAskMe": "Hello, ask me",
"anything": "anything...",
"mazayaTitle": "Mazaya", "thinking": "Thinking",
"mazayaAccessGranted": "Mazaya access granted", "waitingForResponse": "Waiting for response...",
"mazayaAccessGrantedDesc": "{} now has active access. Share the download link below so they can install the Mohem app and complete setup.", "typeAMessage": "Type a message..",
"accessIsNowActive": "Access is now active", "initializingChatPleaseWait": "Please wait, initializing chat...",
"accessIsNowActiveDesc": "{} can use the provided link to download the Mazaya app and continue setup on their device.", "am": "AM",
"dependentAccessActive": "Dependent access active", "pm": "PM"
"shareableAppDownloadLink": "Shareable app-download link",
"shareableAppDownloadLinkDesc": "Send this link to {} so they can download the Mazaya app and continue setup.",
"downloadLink": "Download link",
"shareDownloadLink": "Share Download Link",
"copyLink": "Copy Link",
"done": "Done",
"linkCopied": "Link copied to clipboard",
"mazayaAccessRevoked": "Mazaya access revoked",
"mazayaAccessRevokedDesc": "Your sponsor has revoked your dependent access. You can no longer open offers or sign in with biometrics.",
"contactYourSponsor": "Contact your sponsor",
"contactYourSponsorDesc": "Ask your sponsor to allow Mazaya access again from Dependent Details. Only the sponsor can restore access.",
"dependentAccessBlocked": "Dependent access blocked",
"returnToSignIn": "Return to Sign In",
"allowMazayaAccess": "Allow Mazaya Access",
"revokeMazayaAccess": "Revoke Mazaya Access",
"active": "Active",
"noAccess": "No access",
"offerDetails": "Offer Details",
"offerIdNotAvailable": "Offer ID not available",
"unableToRedeemOffer": "Unable to redeem offer, please try again later",
"errorRedeemingOffer": "An error occurred while redeeming offer",
"useOffer": "Use Offer",
"online": "Online",
"inStore": "In Store",
"ksa": "KSA",
"riyadh": "Riyadh",
"noInstructionsAvailable": "No instructions available.",
"contactInfo": "Contact Information",
"allCategories": "All",
"noOffersAvailable": "No offers available",
"addDependents": "Add Dependents",
"myDependents": "My Dependents",
"offDiscount": "{} Off",
"usageLabel": "Usage: ",
"areYouSureWantToLogOut": "Are you sure you want to log out?",
"searchDependents": "Search dependents",
"searchFamilyMembers": "Search family members",
"addAnotherUser": "Add another user",
"joinMazaya": "Join Mazaya",
"joinMazayaSubtitle": "Get the best offers and exclusive deals across dining, shopping, and lifestyle rewards.",
"continueAsEmployee": "Continue as an employee",
"continueAsEmployeeSubtitle": "Sign in with your employee username and password.",
"pleaseEnterNationalId": "Please enter National ID",
"pleaseSelectDob": "Please select Date of Birth",
"unableToRetrieveDependentInfo": "Unable to retrieve dependent information",
"failedToSendOtp": "Failed to send verification code",
"verificationCodeResent": "Verification code resent",
"nationalId": "National ID",
"addDependent": "Add Dependent",
"stepVerify": "Verify",
"stepBasic": "Basic",
"stepAdditional": "Additional",
"findYourDependent": "Find your dependent",
"findYourDependentDesc": "Enter the details exactly as they appear on the National ID. We'll securely retrieve the record from NHIC.",
"enter10DigitNationalId": "Enter 10-digit National ID",
"nationalIdMustBe10Digits": "National ID must be 10 digits",
"dateFormatPlaceholder": "DD / MM / YYYY",
"searchUser": "Search User",
"dependentNotFoundNhic": "Sorry, error while getting dependent info from NHIC",
"basicInfo": "Basic Info",
"basicInfoDesc": "Required identity details fetched from NHIC.",
"additionalInfo": "Additional Info",
"additionalInfoDesc": "Add or review sponsorship and eligibility details.",
"select": "Select",
"enterFieldPlaceholder": "Enter {}",
"requiredField": "Required field",
"pleaseFillField": "Please fill {}",
"reviewAndSubmit": "Review & Submit",
"reviewAndSubmitDesc": "Please review the dependent details carefully before submitting.",
"arabicName": "Arabic Name",
"relationship": "Relationship",
"sponsor": "Sponsor",
"dependentSponsorDetail": "Dependent Sponsor Detail",
"insuranceEligibility": "Insurance Eligibility",
"relationshipStartDate": "Start Date",
"dependentAddedSuccessfully": "Dependent added successfully",
"genderMale": "Male",
"genderFemale": "Female",
"religionIslam": "Islam",
"back": "Back",
"submitRequest": "Submit",
"enterDependentDetailsToContinue": "Enter the dependent details to continue.",
"enterBasicDetailsDesc": "Please enter the dependent's basic identity details.",
"locations": "Locations",
"availableOffers": "Available Offers",
"selectOfferToRedeem": "Select an offer to use",
"viewOnMap": "View on map",
"showAllLocations": "Show all {} locations",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches",
"kmAway": "{} KM Away",
"dependentDetails": "Dependent Details",
"keyDetails": "Key details",
"verified": "Verified",
"idEnding": "ID ending {}",
"servicesAndActions": "Services & actions",
"startDependentFlowsDesc": "Start dependent flows and manage access from one place.",
"mazayaAccess": "Mazaya access",
"accessOff": "Off",
"allowDependentMazayaOffersDesc": "Allow {} to use Mazaya offers with her verified identity.",
"loginMethod": "Login method",
"nationalIdAndDob": "National ID & Date of Birth",
"sponsorOtp": "Sponsor OTP",
"revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again."
} }

@ -0,0 +1,699 @@
#!/bin/bash
# Enhanced ELF Alignment Checker for Android 16KB Page Size Compatibility
# This script checks if your app's native libraries are compatible with 16KB page size devices
# as required by Google Play starting November 1st, 2025 for apps targeting Android 15+
progname="${0##*/}"
progname="${progname%.sh}"
# Color codes and formatting - detect if colors are supported
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && tput colors >/dev/null 2>&1 && [[ $(tput colors) -ge 8 ]]; then
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
BLUE="\033[34m"
CYAN="\033[36m"
PURPLE="\033[35m"
BOLD="\033[1m"
DIM="\033[2m"
ENDCOLOR="\033[0m"
USE_COLORS=true
else
# No color support - use plain text
RED=""
GREEN=""
YELLOW=""
BLUE=""
CYAN=""
PURPLE=""
BOLD=""
DIM=""
ENDCOLOR=""
USE_COLORS=false
fi
# Unicode symbols with fallbacks
if [[ $USE_COLORS == true ]]; then
CHECK_MARK="✓"
CROSS_MARK="✗"
WARNING_MARK="⚠"
INFO_MARK="ℹ"
ROCKET="🚀"
GEAR="⚙"
PACKAGE="📦"
DOCUMENT="📄"
ALERT="🚨"
PARTY="🎉"
TOOLS="🔧"
BOOKS="📚"
ARROW="▶"
else
CHECK_MARK="[OK]"
CROSS_MARK="[X]"
WARNING_MARK="[!]"
INFO_MARK="[i]"
ROCKET="[>]"
GEAR="[*]"
PACKAGE="[P]"
DOCUMENT="[D]"
ALERT="[!]"
PARTY="[*]"
TOOLS="[T]"
BOOKS="[B]"
ARROW=">"
fi
# Enhanced formatting functions
print_banner() {
local title="$1"
local subtitle="$2"
local width=88
echo
printf "%*s\n" $width | tr ' ' '═'
# Main title
local title_len=${#title}
local title_padding=$(( (width - title_len - 4) / 2 ))
printf "║${BOLD}${BLUE}%*s %s %*s${ENDCOLOR}║\n" \
$title_padding "" "$title" $title_padding ""
# Subtitle if provided
if [[ -n "$subtitle" ]]; then
local subtitle_len=${#subtitle}
local subtitle_padding=$(( (width - subtitle_len - 4) / 2 ))
printf "║${DIM}%*s %s %*s${ENDCOLOR}║\n" \
$subtitle_padding "" "$subtitle" $subtitle_padding ""
fi
printf "%*s\n" $width | tr ' ' '═'
echo
}
print_section() {
local icon="$1"
local title="$2"
local description="$3"
echo
echo -e "${BOLD}${BLUE}$icon $title${ENDCOLOR}"
if [[ -n "$description" ]]; then
echo -e "${DIM}$description${ENDCOLOR}"
fi
printf "${BLUE}%*s${ENDCOLOR}\n" 80 | tr ' ' '─'
}
print_status() {
local status="$1"
local message="$2"
local detail="$3"
case $status in
"success")
printf " ${GREEN}${CHECK_MARK}${ENDCOLOR} %s" "$message"
;;
"error")
printf " ${RED}${CROSS_MARK}${ENDCOLOR} %s" "$message"
;;
"warning")
printf " ${YELLOW}${WARNING_MARK}${ENDCOLOR} %s" "$message"
;;
"info")
printf " ${CYAN}${INFO_MARK}${ENDCOLOR} %s" "$message"
;;
"processing")
printf " ${PURPLE}${GEAR}${ENDCOLOR} %s" "$message"
;;
esac
if [[ -n "$detail" ]]; then
printf "\n ${DIM}%s${ENDCOLOR}" "$detail"
fi
echo
}
print_subsection() {
local title="$1"
echo
echo -e " ${BOLD}${PURPLE}${ARROW} $title${ENDCOLOR}"
printf " ${PURPLE}%*s${ENDCOLOR}\n" 50 | tr ' ' '┈'
}
print_table_header() {
echo
local line1="┌$(printf '─%.0s' {1..29})┬$(printf '─%.0s' {1..18})┬$(printf '─%.0s' {1..15})┬$(printf '─%.0s' {1..13})┐"
local line2="│ $(printf '%-27s' 'Library') │ $(printf '%-16s' 'Architecture') │ $(printf '%-13s' 'Alignment') │ $(printf '%-11s' 'Status') │"
local line3="├$(printf '─%.0s' {1..29})┼$(printf '─%.0s' {1..18})┼$(printf '─%.0s' {1..15})┼$(printf '─%.0s' {1..13})┤"
echo -e "${CYAN}$line1${ENDCOLOR}"
echo -e "${BOLD}${CYAN}$line2${ENDCOLOR}"
echo -e "${CYAN}$line3${ENDCOLOR}"
}
print_table_row() {
local lib="$1"
local arch="$2"
local alignment="$3"
local status="$4"
local is_critical="$5"
local status_symbol
local status_color
local status_text
if [[ $status == "ALIGNED" ]]; then
status_symbol="$CHECK_MARK"
status_color="$GREEN"
status_text="PASS"
else
status_symbol="$CROSS_MARK"
if [[ $is_critical == "true" ]]; then
status_color="$RED"
status_text="FAIL"
else
status_color="$YELLOW"
status_text="WARN"
fi
fi
# Truncate library name if too long
local display_lib="$lib"
if [[ ${#lib} -gt 27 ]]; then
display_lib="${lib:0:24}..."
fi
printf "${CYAN}│${ENDCOLOR} %-27s ${CYAN}│${ENDCOLOR} %-16s ${CYAN}│${ENDCOLOR} %-13s ${CYAN}│${ENDCOLOR} ${status_color}%s %-7s${ENDCOLOR} ${CYAN}│${ENDCOLOR}\n" \
"$display_lib" "$arch" "$alignment" "$status_symbol" "$status_text"
if [[ $is_critical == "true" && $status == "UNALIGNED" ]]; then
printf "${CYAN}│${ENDCOLOR} ${RED}%-75s${ENDCOLOR} ${CYAN}│${ENDCOLOR}\n" " ${ALERT} CRITICAL: Required for Google Play compliance!"
fi
}
print_table_footer() {
local line="└$(printf '─%.0s' {1..29})┴$(printf '─%.0s' {1..18})┴$(printf '─%.0s' {1..15})┴$(printf '─%.0s' {1..13})┘"
echo -e "${CYAN}$line${ENDCOLOR}"
echo
}
print_summary_box() {
local title="$1"
local status="$2" # success, warning, error
shift 2
local lines=("$@")
local box_color
local title_color
case $status in
"success") box_color="$GREEN"; title_color="$GREEN" ;;
"warning") box_color="$YELLOW"; title_color="$YELLOW" ;;
"error") box_color="$RED"; title_color="$RED" ;;
*) box_color="$BLUE"; title_color="$BLUE" ;;
esac
local width=78
echo
printf "${box_color}┌%*s┐${ENDCOLOR}\n" $((width-2)) | tr ' ' '─'
# Title
local title_len=${#title}
local title_padding=$(( (width - title_len - 4) / 2 ))
printf "${box_color}│${ENDCOLOR}${BOLD}${title_color}%*s %s %*s${ENDCOLOR}${box_color}│${ENDCOLOR}\n" \
$title_padding "" "$title" $title_padding ""
printf "${box_color}├%*s┤${ENDCOLOR}\n" $((width-2)) | tr ' ' '─'
# Content lines
for line in "${lines[@]}"; do
printf "${box_color}│${ENDCOLOR} %-*s ${box_color}│${ENDCOLOR}\n" $((width-4)) "$line"
done
printf "${box_color}└%*s┘${ENDCOLOR}\n" $((width-2)) | tr ' ' '─'
echo
}
cleanup_trap() {
if [ -n "${tmp}" -a -d "${tmp}" ]; then
print_status "processing" "Cleaning up temporary files..."
rm -rf "${tmp}"
fi
exit $1
}
usage() {
print_banner "Android 16KB Page Size Compatibility Checker" "Google Play Compliance Tool"
echo -e "${BOLD}DESCRIPTION:${ENDCOLOR}"
echo " This tool verifies that your Android app's native libraries are compatible"
echo " with 16KB page size devices, as required by Google Play starting November 1st, 2025."
echo
echo -e "${BOLD}USAGE:${ENDCOLOR}"
echo -e " ${GREEN}$progname${ENDCOLOR} ${CYAN}[input-path|input-APK|input-APEX]${ENDCOLOR}"
echo
echo -e "${BOLD}EXAMPLES:${ENDCOLOR}"
echo -e " ${DIM}# Check an APK file${ENDCOLOR}"
echo -e " $progname ${CYAN}app/build/outputs/apk/release/app-release.apk${ENDCOLOR}"
echo
echo -e " ${DIM}# Check a directory of native libraries${ENDCOLOR}"
echo -e " $progname ${CYAN}/path/to/native/libs/${ENDCOLOR}"
echo
echo -e "${BOLD}WHAT THIS TOOL CHECKS:${ENDCOLOR}"
echo -e " ${CHECK_MARK} APK zip-alignment for 16KB boundaries (requires build-tools 35.0.0+)"
echo -e " ${CHECK_MARK} ELF segment alignment in native libraries (arm64-v8a and x86_64)"
echo -e " ${CHECK_MARK} Compliance with Android 16KB page size requirements"
echo
echo -e "${BOLD}RESULT MEANINGS:${ENDCOLOR}"
echo -e " ${GREEN}${CHECK_MARK} PASS${ENDCOLOR} - Library is compatible with 16KB page sizes (2**14 or higher)"
echo -e " ${RED}${CROSS_MARK} FAIL${ENDCOLOR} - Library needs recompilation with 16KB ELF alignment"
echo -e " ${YELLOW}${WARNING_MARK} WARN${ENDCOLOR} - Non-critical architecture but should be fixed"
echo
}
# Enhanced dependency checking
check_dependencies() {
print_section "$GEAR" "Dependency Check" "Verifying required tools are available"
local missing_tools=()
local available_tools=()
# Check each tool
local tools=("objdump" "unzip" "file")
for tool in "${tools[@]}"; do
if command -v "$tool" >/dev/null 2>&1; then
available_tools+=("$tool")
print_status "success" "$tool" "$(which "$tool")"
else
missing_tools+=("$tool")
print_status "error" "$tool not found"
fi
done
if [ ${#missing_tools[@]} -gt 0 ]; then
echo
print_subsection "Installation Instructions"
for tool in "${missing_tools[@]}"; do
case $tool in
objdump)
print_status "info" "Install objdump:" "macOS: Xcode Command Line Tools or Android NDK's llvm-objdump"
print_status "info" "" "Linux: sudo apt-get install binutils (Ubuntu/Debian)"
;;
unzip)
print_status "info" "Install unzip:" "Usually pre-installed on most systems"
print_status "info" "" "Linux: sudo apt-get install unzip"
;;
file)
print_status "info" "Install file:" "Usually pre-installed on most systems"
print_status "info" "" "Linux: sudo apt-get install file"
;;
esac
echo
done
exit 1
fi
print_status "success" "All dependencies satisfied" "${#available_tools[@]} tools available"
}
# Validate input arguments
if [ ${#} -ne 1 ]; then
usage
exit 1
fi
case ${1} in
--help | -h | -\?)
usage
exit 0
;;
*)
dir="${1}"
;;
esac
# Validate input file/directory
if ! [ -f "${dir}" -o -d "${dir}" ]; then
print_status "error" "Invalid input: ${dir}"
echo " Please provide a valid APK file, APEX file, or directory containing native libraries."
exit 1
fi
# Check dependencies before proceeding
check_dependencies
print_banner "ANALYSIS IN PROGRESS" "Checking 16KB Page Size Compatibility"
print_status "processing" "Target: $(basename "${dir}")"
print_status "info" "Compliance Deadline: November 1st, 2025"
print_status "info" "Requirement: Apps targeting Android 15+ must support 16KB pages"
# APK Processing
if [[ "${dir}" == *.apk ]]; then
trap 'cleanup_trap' EXIT
print_section "$PACKAGE" "APK Analysis" "Processing Android Package file"
# Enhanced zipalign check
if command -v zipalign >/dev/null 2>&1; then
if { zipalign --help 2>&1 | grep -q "\-P <pagesize_kb>"; }; then
print_status "processing" "Checking APK zip-alignment for 16KB boundaries..."
zip_result=$(zipalign -v -c -P 16 4 "${dir}" 2>&1)
zip_exit_code=$?
if [ $zip_exit_code -eq 0 ]; then
print_status "success" "APK zip-alignment verification passed"
else
print_status "error" "APK zip-alignment verification failed"
echo " ${DIM}Details:${ENDCOLOR}"
echo "$zip_result" | grep -E 'lib/arm64-v8a|lib/x86_64|Verification|would be' | sed 's/^/ /' || echo " $zip_result"
fi
else
print_status "warning" "zipalign version doesn't support 16KB alignment checks"
print_status "info" "Solution: Update to Android SDK build-tools 35.0.0+"
echo " ${DIM}Update via Android Studio → SDK Manager → SDK Tools${ENDCOLOR}"
echo " ${DIM}Or run: sdkmanager \"build-tools;35.0.0\"${ENDCOLOR}"
fi
else
print_status "warning" "zipalign not found in PATH"
print_status "info" "Ensure Android SDK build-tools are installed and in PATH"
fi
# Extract APK
dir_filename=$(basename "${dir}")
tmp=$(mktemp -d -t "${dir_filename%.apk}_out_XXXXX")
if [ ! -d "${tmp}" ]; then
print_status "error" "Failed to create temporary directory"
exit 1
fi
print_status "processing" "Extracting native libraries from APK..."
if ! unzip -q "${dir}" "lib/*" -d "${tmp}" 2>/dev/null; then
print_summary_box "NO NATIVE LIBRARIES FOUND" "success" \
"${PARTY} Your app contains only Java/Kotlin code!" \
"" \
"${CHECK_MARK} Apps without native libraries automatically support 16KB devices" \
"${CHECK_MARK} No additional changes required for Google Play compliance" \
"${CHECK_MARK} You're all set for the November 1st, 2025 deadline!"
cleanup_trap 0
fi
dir="${tmp}"
fi
# APEX Processing
if [[ "${dir}" == *.apex ]]; then
trap 'cleanup_trap' EXIT
print_section "$DOCUMENT" "APEX Analysis" "Processing Android Pony EXpress file"
if ! command -v deapexer >/dev/null 2>&1; then
print_status "error" "deapexer tool not found"
echo " Please ensure Android SDK tools are properly installed and in your PATH."
exit 1
fi
dir_filename=$(basename "${dir}")
tmp=$(mktemp -d -t "${dir_filename%.apex}_out_XXXXX")
if [ ! -d "${tmp}" ]; then
print_status "error" "Failed to create temporary directory"
exit 1
fi
print_status "processing" "Extracting APEX contents..."
if ! deapexer extract "${dir}" "${tmp}"; then
print_status "error" "Failed to extract APEX file"
cleanup_trap 1
fi
dir="${tmp}"
fi
# Track libraries for enhanced summary
unaligned_libs=()
aligned_libs=()
critical_unaligned_libs=()
non_critical_unaligned_libs=()
total_libs=0
critical_libs=0
print_section "$GEAR" "ELF Segment Analysis" "Scanning native libraries for 16KB alignment compliance"
# Find all native libraries
matches="$(find "${dir}" -type f -name '*.so' 2>/dev/null)"
if [ -z "$matches" ]; then
# Also check for ELF files without .so extension
matches="$(find "${dir}" -type f -exec file {} \; 2>/dev/null | grep 'ELF' | cut -d: -f1)"
fi
if [ -z "$matches" ]; then
print_summary_box "NO NATIVE LIBRARIES DETECTED" "success" \
"${PARTY} Your app uses only Java/Kotlin code!" \
"" \
"${CHECK_MARK} No native library alignment issues to worry about" \
"${CHECK_MARK} Already compatible with 16KB page size devices" \
"${CHECK_MARK} Ready for Google Play's November 1st, 2025 requirement!"
cleanup_trap 0
fi
print_status "info" "Found native libraries - analyzing ELF segment alignment..."
print_table_header
IFS=$'\n'
for match in $matches; do
# Skip non-ELF files and nested packages
if [[ "${match}" == *".apk" ]]; then
continue
fi
if [[ "${match}" == *".apex" ]]; then
continue
fi
# Verify it's actually an ELF file
if ! [[ $(file "${match}" 2>/dev/null) == *"ELF"* ]]; then
continue
fi
total_libs=$((total_libs + 1))
# Determine architecture and criticality
arch="unknown"
is_critical="false"
if [[ "${match}" == *"arm64-v8a"* ]]; then
arch="arm64-v8a"
is_critical="true"
critical_libs=$((critical_libs + 1))
elif [[ "${match}" == *"x86_64"* ]]; then
arch="x86_64"
is_critical="true"
critical_libs=$((critical_libs + 1))
elif [[ "${match}" == *"armeabi-v7a"* ]]; then
arch="armeabi-v7a"
elif [[ "${match}" == *"x86"* ]]; then
arch="x86"
fi
# Check ELF segment alignment
res="$(objdump -p "${match}" 2>/dev/null | grep LOAD | awk '{ print $NF }' | head -1)"
if [ -z "$res" ]; then
print_table_row "$(basename "${match}")" "$arch" "UNKNOWN" "FAILED" "$is_critical"
continue
fi
# Check if alignment meets 16KB requirement (2**14 or higher)
if [[ $res =~ 2\*\*(1[4-9]|[2-9][0-9]|[1-9][0-9]{2,}) ]]; then
print_table_row "$(basename "${match}")" "$arch" "$res" "ALIGNED" "$is_critical"
aligned_libs+=("${match}")
else
print_table_row "$(basename "${match}")" "$arch" "$res" "UNALIGNED" "$is_critical"
unaligned_libs+=("${match}")
if [[ "$is_critical" == "true" ]]; then
critical_unaligned_libs+=("${match}")
else
non_critical_unaligned_libs+=("${match}")
fi
fi
done
print_table_footer
# Enhanced Summary Section
if [ ${#unaligned_libs[@]} -gt 0 ]; then
# Failure case - unaligned libraries found
summary_lines=(
"${ALERT} ACTION REQUIRED: Unaligned native libraries detected!"
""
"Analysis Results:"
" • Total libraries scanned: $total_libs"
" • Critical architectures (64-bit): $critical_libs"
" • Libraries aligned: ${#aligned_libs[@]}"
" • Libraries UNALIGNED: ${#unaligned_libs[@]}"
)
if [ ${#critical_unaligned_libs[@]} -gt 0 ]; then
summary_lines+=(" • Critical failures: ${#critical_unaligned_libs[@]} (MUST FIX)")
fi
if [ ${#non_critical_unaligned_libs[@]} -gt 0 ]; then
summary_lines+=(" • Non-critical warnings: ${#non_critical_unaligned_libs[@]} (SHOULD FIX)")
fi
summary_lines+=("")
summary_lines+=("Google Play Compliance: FAILED")
summary_lines+=("Deadline: November 1st, 2025")
print_summary_box "COMPATIBILITY CHECK FAILED" "error" "${summary_lines[@]}"
# Detailed library breakdown
if [ ${#critical_unaligned_libs[@]} -gt 0 ]; then
print_subsection "Critical Libraries Requiring Immediate Attention"
for lib in "${critical_unaligned_libs[@]}"; do
arch_type="64-bit"
[[ "${lib}" == *"arm64-v8a"* ]] && arch_type="ARM64"
[[ "${lib}" == *"x86_64"* ]] && arch_type="x86_64"
print_status "error" "$(basename "$lib")" "$arch_type architecture - Required for Google Play"
done
fi
if [ ${#non_critical_unaligned_libs[@]} -gt 0 ]; then
print_subsection "Non-Critical Libraries (Recommended to Fix)"
for lib in "${non_critical_unaligned_libs[@]}"; do
arch_type="32-bit"
[[ "${lib}" == *"armeabi-v7a"* ]] && arch_type="ARMv7"
[[ "${lib}" == *"x86"* ]] && arch_type="x86"
print_status "warning" "$(basename "$lib")" "$arch_type architecture"
done
fi
# Comprehensive fix instructions
print_section "$TOOLS" "How to Fix Unaligned Libraries" "Step-by-step guide to achieve 16KB compatibility"
print_subsection "Step 1: Update Your Build Environment"
print_status "info" "Android Gradle Plugin (AGP)" "Upgrade to version 8.5.1 or higher"
print_status "info" "Android NDK" "Update to NDK r27 or higher (r28+ recommended)"
print_status "info" "Build Tools" "Ensure Android SDK build-tools 35.0.0+ for zipalign"
print_subsection "Step 2: Configure 16KB ELF Alignment"
echo
echo -e "${BOLD}For NDK r28 and newer:${ENDCOLOR}"
print_status "success" "Automatic Support" "16KB alignment enabled by default - no changes needed!"
echo
echo -e "${BOLD}For NDK r27:${ENDCOLOR}"
print_status "info" "Gradle Configuration" "Add to your app/build.gradle:"
echo -e "${DIM} android {"
echo -e " defaultConfig {"
echo -e " externalNativeBuild {"
echo -e " cmake {"
echo -e " arguments '-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON'"
echo -e " }"
echo -e " }"
echo -e " }"
echo -e " }${ENDCOLOR}"
print_status "info" "NDK-Build Configuration" "Add to Application.mk:"
echo -e "${DIM} APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true${ENDCOLOR}"
echo
echo -e "${BOLD}For NDK r26 and older:${ENDCOLOR}"
print_status "info" "Manual Linker Flags" "Add to your native build configuration:"
echo -e "${DIM} # For Android.mk:"
echo -e " LOCAL_LDFLAGS += \"-Wl,-z,max-page-size=16384\""
echo
echo -e " # For CMakeLists.txt:"
echo -e " target_link_options(\${CMAKE_PROJECT_NAME} PRIVATE \"-Wl,-z,max-page-size=16384\")${ENDCOLOR}"
print_subsection "Step 3: Update Library Packaging"
print_status "info" "AGP 8.5.1+" "Uncompressed libraries used by default (no changes needed)"
print_status "info" "Older AGP versions" "Add to app/build.gradle:"
echo -e "${DIM} android {"
echo -e " packagingOptions {"
echo -e " jniLibs {"
echo -e " useLegacyPackaging = true"
echo -e " }"
echo -e " }"
echo -e " }${ENDCOLOR}"
print_subsection "Step 4: Build and Verify"
print_status "processing" "Clean Build" "Run ./gradlew clean"
print_status "processing" "Rebuild Project" "Run ./gradlew assembleRelease"
print_status "processing" "Re-run This Script" "Verify all libraries are now aligned"
print_status "processing" "Test on Device" "Use Android 15 emulator with 16KB system image"
print_section "$BOOKS" "Additional Resources" "Learn more about 16KB page size support"
print_status "info" "Official Guide" "https://developer.android.com/guide/practices/page-sizes"
print_status "info" "NDK Documentation" "https://developer.android.com/ndk/guides/"
print_status "info" "Testing Guide" "Use 'adb shell getconf PAGE_SIZE' (should return 16384)"
print_status "info" "Emulator Setup" "Android 15 system images with 16KB page size"
final_message=""
if [ ${#critical_unaligned_libs[@]} -gt 0 ]; then
final_message="CRITICAL: Fix required for Google Play compliance by November 1st, 2025!"
else
final_message="Warning: Non-critical issues found - recommended to fix for complete compatibility"
fi
print_summary_box "NEXT STEPS" "error" \
"$final_message" \
"" \
"1. Update your build tools and NDK version" \
"2. Apply the configuration changes above" \
"3. Clean and rebuild your project" \
"4. Run this script again to verify fixes" \
"5. Test thoroughly on 16KB devices/emulator"
cleanup_trap 1
else
# Success case - all libraries aligned
summary_lines=(
"${PARTY} All native libraries are properly aligned!"
""
"Analysis Results:"
" • Total libraries scanned: $total_libs"
" • Critical architectures: $critical_libs"
" • All libraries: ${#aligned_libs[@]}/${total_libs} ALIGNED"
""
"Google Play Compliance: PASSED ${CHECK_MARK}"
"Ready for November 1st, 2025 deadline!"
)
print_summary_box "COMPATIBILITY CHECK PASSED" "success" "${summary_lines[@]}"
print_section "$ROCKET" "Next Steps" "Ensure complete 16KB compatibility"
print_status "success" "Library Alignment" "All ELF segments properly aligned for 16KB pages"
print_status "info" "Runtime Testing" "Test on Android 15 emulator with 16KB system image"
print_status "info" "Code Review" "Check for hardcoded PAGE_SIZE dependencies in your code"
print_status "info" "Third-party SDKs" "Verify all dependencies are 16KB compatible"
print_status "info" "Zipalign Verification" "Run: zipalign -c -P 16 -v 4 your-app.apk"
print_subsection "Testing Commands"
echo -e "${DIM} # Verify page size on device/emulator${ENDCOLOR}"
echo -e "${DIM} adb shell getconf PAGE_SIZE # Should return: 16384${ENDCOLOR}"
echo
echo -e "${DIM} # Verify APK alignment${ENDCOLOR}"
echo -e "${DIM} zipalign -c -P 16 -v 4 app-release.apk${ENDCOLOR}"
print_summary_box "CONGRATULATIONS!" "success" \
"${PARTY} Your app is 16KB page size compatible!" \
"" \
"${CHECK_MARK} All native libraries meet Google Play requirements" \
"${CHECK_MARK} Ready for devices with 16KB page sizes" \
"${CHECK_MARK} Compliant with November 1st, 2025 deadline" \
"" \
"Recommendation: Test thoroughly on 16KB environment to ensure" \
"no runtime issues exist in your application code."
fi
echo
print_status "info" "Analysis completed at $(date)"
echo

@ -20,7 +20,5 @@
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict> </dict>
</plist> </plist>

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 54; objectVersion = 60;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -12,6 +12,7 @@
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
76AE113C28E078EA00513112 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 76AE113B28E078EA00513112 /* GoogleService-Info.plist */; }; 76AE113C28E078EA00513112 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 76AE113B28E078EA00513112 /* GoogleService-Info.plist */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
@ -39,6 +40,7 @@
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7627313328E45A2400F354F7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; 7627313328E45A2400F354F7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
76AE113B28E078EA00513112 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; 76AE113B28E078EA00513112 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
@ -57,6 +59,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */, 1CCF895CFB3C95DEBB84A05F /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@ -75,6 +78,7 @@
9740EEB11CF90186004384FC /* Flutter */ = { 9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */, 9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@ -144,13 +148,16 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
C4CFBC4C5CAC00182015ACD5 /* [CP] Embed Pods Frameworks */, C4CFBC4C5CAC00182015ACD5 /* [CP] Embed Pods Frameworks */,
EF29CE25BB57F5AA48CE1136 /* [CP] Copy Pods Resources */, D9FFC0F21D4146458615A992 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
); );
name = Runner; name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner; productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
@ -179,6 +186,9 @@
Base, Base,
); );
mainGroup = 97C146E51CF9000F007C117D; mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */; productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
@ -242,23 +252,6 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
6C6E76E4FB19FAF11C24005D /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@ -291,6 +284,23 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
D9FFC0F21D4146458615A992 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@ -367,7 +377,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -454,7 +464,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -504,7 +514,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -600,6 +610,20 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = 97C146E61CF9000F007C117D /* Project object */; rootObject = 97C146E61CF9000F007C117D /* Project object */;
} }

@ -5,6 +5,24 @@
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries> <BuildActionEntries>
<BuildActionEntry <BuildActionEntry
buildForTesting = "YES" buildForTesting = "YES"

@ -3,9 +3,8 @@ import Flutter
import Firebase import Firebase
import flutter_local_notifications import flutter_local_notifications
@main @main
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application( override func application(
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
@ -17,7 +16,11 @@ import flutter_local_notifications
if #available(iOS 10.0, *) { if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
} }
GeneratedPluginRegistrant.register(with: self) // GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
} }

@ -2,6 +2,27 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
@ -56,9 +77,11 @@
<key>NSLocationWhenInUseUsageDescription</key> <key>NSLocationWhenInUseUsageDescription</key>
<string>This App requires access to your location to mark your attendance.</string> <string>This App requires access to your location to mark your attendance.</string>
<key>NSMicrophoneUsageDescription</key> <key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to for call.</string> <string>This app requires microphone access for calls and speech-to-text input.</string>
<key>NSPhotoLibraryUsageDescription</key> <key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photo library access to select image as document &amp; upload it.</string> <string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>This app requires speech recognition to convert your voice to text.</string>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>NSSpeechRecognitionUsageDescription</key> <key>NSSpeechRecognitionUsageDescription</key>

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -37,24 +36,16 @@ APIException _throwAPIException(Response response) {
case 200: case 200:
APIError? apiError; APIError? apiError;
if (response.body != null && response.body.isNotEmpty) { if (response.body != null && response.body.isNotEmpty) {
try {
var jsonError = jsonDecode(response.body); var jsonError = jsonDecode(response.body);
print(jsonError); print(jsonError);
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']); apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
} catch (_) {
log("Non-JSON 200 error body: ${response.body}");
}
} }
return APIException(APIException.BAD_REQUEST, error: apiError); return APIException(APIException.BAD_REQUEST, error: apiError);
case 400: case 400:
APIError? apiError; APIError? apiError;
if (response.body != null && response.body.isNotEmpty) { if (response.body != null && response.body.isNotEmpty) {
try {
var jsonError = jsonDecode(response.body); var jsonError = jsonDecode(response.body);
apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']); apiError = APIError(jsonError['ErrorCode'], jsonError['ErrorMessage'], jsonError['ErrorType'], jsonError['ErrorStatusCode']);
} catch (_) {
log("Non-JSON 400 error body: ${response.body}");
}
} }
return APIException(APIException.BAD_REQUEST, error: apiError); return APIException(APIException.BAD_REQUEST, error: apiError);
case 401: case 401:
@ -95,14 +86,15 @@ class ApiClient {
_headers.addAll(headers); _headers.addAll(headers);
} }
if (!kReleaseMode) { if (!kReleaseMode) {
log("Url: $url"); print("Url:$url");
var bodyJson = json.encode(jsonObject); var bodyJson = json.encode(jsonObject);
log("body:$bodyJson"); print("body:$bodyJson");
} }
var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData); var response = await postJsonForResponse(url, jsonObject, token: token, queryParameters: queryParameters, headers: _headers, retryTimes: retryTimes, isFormData: isFormData);
try { try {
if (!kReleaseMode) { if (!kReleaseMode) {
log("res: " + response.body); logger.i("Url: " + url);
logger.i("res: " + response.body);
} }
var jsonData = jsonDecode(response.body); var jsonData = jsonDecode(response.body);
@ -129,7 +121,6 @@ class ApiClient {
if (ex is APIException) { if (ex is APIException) {
rethrow; rethrow;
} else { } else {
log("BAD_RESPONSE_FORMAT for $url -> body: ${response.body}");
throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex); throw APIException(APIException.BAD_RESPONSE_FORMAT, arguments: ex);
} }
} }

@ -6,7 +6,6 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
import 'package:mobile_device_identifier/mobile_device_identifier.dart';
import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/consts.dart';
@ -37,7 +36,7 @@ class DashboardApiClient {
Future<GetAttendanceTracking?> getAttendanceTracking() async { Future<GetAttendanceTracking?> getAttendanceTracking() async {
String url = "${ApiConsts.erpRest}GET_Attendance_Tracking"; String url = "${ApiConsts.erpRest}GET_Attendance_Tracking";
Map<String, dynamic> postParams = <String, dynamic>{}; Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
@ -51,7 +50,7 @@ class DashboardApiClient {
Future<GenericResponseModel?> getOpenNotifications() async { Future<GenericResponseModel?> getOpenNotifications() async {
String url = "${ApiConsts.erpRest}GET_OPEN_NOTIFICATIONS"; String url = "${ApiConsts.erpRest}GET_OPEN_NOTIFICATIONS";
Map<String, dynamic> postParams = <String, dynamic>{}; Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
@ -65,7 +64,7 @@ class DashboardApiClient {
Future<GenericResponseModel?> getCOCNotifications() async { Future<GenericResponseModel?> getCOCNotifications() async {
String url = "${ApiConsts.cocRest}Mohemm_ITG_ReviewerAdmin_Pending_Tasks"; String url = "${ApiConsts.cocRest}Mohemm_ITG_ReviewerAdmin_Pending_Tasks";
Map<String, dynamic> postParams = <String, dynamic>{"Date": DateUtil.getISODateFormat(DateTime.now()), "EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER}; Map<String, dynamic> postParams = {"Date": DateUtil.getISODateFormat(DateTime.now()), "EmployeeNumber": AppState().memberInformationList?.eMPLOYEENUMBER};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
@ -79,7 +78,7 @@ class DashboardApiClient {
Future<ItgFormsModel?> getItgFormsPendingTask() async { Future<ItgFormsModel?> getItgFormsPendingTask() async {
String url = "${ApiConsts.cocRest}ITGFormsPendingTasks"; String url = "${ApiConsts.cocRest}ITGFormsPendingTasks";
Map<String, dynamic> postParams = <String, dynamic>{}; Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
@ -93,13 +92,13 @@ class DashboardApiClient {
Future<List<GetAccrualBalancesList>> getAccrualBalances(String effectiveDate, {String? empID}) async { Future<List<GetAccrualBalancesList>> getAccrualBalances(String effectiveDate, {String? empID}) async {
String url = "${ApiConsts.erpRest}GET_ACCRUAL_BALANCES"; String url = "${ApiConsts.erpRest}GET_ACCRUAL_BALANCES";
Map<String, dynamic> postParams = <String, dynamic>{"P_EFFECTIVE_DATE": effectiveDate}; Map<String, dynamic> postParams = {"P_EFFECTIVE_DATE": effectiveDate};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID; if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID;
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json); GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.getAccrualBalancesList ?? <GetAccrualBalancesList>[]; return responseData.getAccrualBalancesList ?? [];
}, },
url, url,
postParams, postParams,
@ -108,13 +107,13 @@ class DashboardApiClient {
Future<List<GetTicketAccuralBalanceModel>> getTicketAccuralBalance(String effectiveDate, {String? empID}) async { Future<List<GetTicketAccuralBalanceModel>> getTicketAccuralBalance(String effectiveDate, {String? empID}) async {
String url = "${ApiConsts.erpRest}GET_TICKET_ACCRUAL_BALANCES"; String url = "${ApiConsts.erpRest}GET_TICKET_ACCRUAL_BALANCES";
Map<String, dynamic> postParams = <String, dynamic>{"P_EFFECTIVE_DATE": effectiveDate}; Map<String, dynamic> postParams = {"P_EFFECTIVE_DATE": effectiveDate};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID; if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID;
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json); GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.getAccrualBalancesTicketList ?? <GetTicketAccuralBalanceModel>[]; return responseData.getAccrualBalancesTicketList ?? [];
}, },
url, url,
postParams, postParams,
@ -123,7 +122,7 @@ class DashboardApiClient {
Future<GenericResponseModel?> getOpenMissingSwipes() async { Future<GenericResponseModel?> getOpenMissingSwipes() async {
String url = "${ApiConsts.erpRest}GET_OPEN_MISSING_SWIPES"; String url = "${ApiConsts.erpRest}GET_OPEN_MISSING_SWIPES";
Map<String, dynamic> postParams = <String, dynamic>{}; Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
@ -138,7 +137,7 @@ class DashboardApiClient {
//Menus List //Menus List
Future<List<ListMenu>> getListMenu() async { Future<List<ListMenu>> getListMenu() async {
String url = "${ApiConsts.erpRest}GET_MENU"; String url = "${ApiConsts.erpRest}GET_MENU";
Map<String, dynamic> postParams = <String, dynamic>{}; Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject(
(json) { (json) {
@ -276,7 +275,7 @@ class DashboardApiClient {
String QRValue = "", String QRValue = "",
String payrollCode = "", String payrollCode = "",
}) async { }) async {
var _mobileDeviceIdentifierPlugin = MobileDeviceIdentifier();
Codec<String, String> stringToBase64 = utf8.fuse(base64); Codec<String, String> stringToBase64 = utf8.fuse(base64);
String url = "${ApiConsts.swpRest}AuthenticateAndSwipeUserSupportNFC"; String url = "${ApiConsts.swpRest}AuthenticateAndSwipeUserSupportNFC";
Uuid uuid = Uuid(); Uuid uuid = Uuid();
@ -284,7 +283,7 @@ class DashboardApiClient {
// Generate a v4 (random) id // Generate a v4 (random) id
if (Platform.isAndroid) { if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo; AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo;
deviceId = await _mobileDeviceIdentifierPlugin.getDeviceId() ?? androidInfo.id; deviceId = await androidInfo.id;
deviceId = stringToBase64.encode(deviceId); deviceId = stringToBase64.encode(deviceId);
deviceId = deviceId.length >= 50 ? deviceId.substring(0, 48) : deviceId; deviceId = deviceId.length >= 50 ? deviceId.substring(0, 48) : deviceId;
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
@ -502,4 +501,32 @@ class DashboardApiClient {
postParams, postParams,
); );
} }
Future<GenericResponseModel?> getChatBotSession() async {
String url = "${ApiConsts.erpRest}Atlas_InitiateChat";
Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData;
},
url,
postParams,
);
}
Future<GenericResponseModel?> sendChatBotMessage({required AtlasInitiateChatResponse atlasChatTokenResponse, required String atlasChatText}) async {
String url = "${ApiConsts.erpRest}Atlas_ContinueChat";
Map<String, dynamic> postParams = <String, dynamic>{"Atlas_AccessToken": atlasChatTokenResponse.atlasAccessToken, "Atlas_ChatSessionID": atlasChatTokenResponse.atlasChatSessionId, "Atlas_ChatText": atlasChatText};
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
(json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData;
},
url,
postParams,
);
}
} }

@ -1,19 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/basic_member_information_model.dart'; import 'package:mohem_flutter_app/models/basic_member_information_model.dart';
import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart'; import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/dependent/get_mobile_login_info_dependent_model.dart';
import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart'; import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart'; import 'package:mohem_flutter_app/models/member_login_list_model.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
class LoginApiClient { class LoginApiClient {
static final LoginApiClient _instance = LoginApiClient._internal(); static final LoginApiClient _instance = LoginApiClient._internal();
@ -22,84 +16,19 @@ class LoginApiClient {
factory LoginApiClient() => _instance; factory LoginApiClient() => _instance;
static String? _cachedSignature; Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType) async {
static Future<String?> getSignature() async {
if (!Platform.isAndroid) return null;
if (_cachedSignature != null && _cachedSignature!.isNotEmpty) {
return _cachedSignature;
}
try {
String? sig = await SmsVerification.getAppSignature();
if (sig != null && sig.isNotEmpty) {
_cachedSignature = sig;
}
return sig;
} catch (e) {
debugPrint("Error getting app signature: $e");
return null;
}
}
Future<GetMobileLoginInfoListModel?> getMobileLoginInfoNEW(String deviceToken, String deviceType, {int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW"; String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW";
Map<String, dynamic> postParams = {}; Map<String, dynamic> postParams = {};
postParams["DeviceToken"] = deviceToken; postParams["DeviceToken"] = deviceToken;
postParams["DeviceType"] = deviceType; postParams["DeviceType"] = deviceType;
postParams["MemberType"] = memberType ?? ApiConsts.selectedMemberType.value;
return await ApiClient().postJsonForObject((json) { return await ApiClient().postJsonForObject((json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return (responseData.mohemmGetMobileLoginInfoList?.length ?? 0) > 0 ? (responseData.mohemmGetMobileLoginInfoList!.first) : null; return (responseData.mohemmGetMobileLoginInfoList?.length ?? 0) > 0 ? (responseData.mohemmGetMobileLoginInfoList!.first) : null;
}, url, postParams); }, url, postParams);
} }
Future<GetMobileLoginInfoDependentModel?> getMobileLoginInfoNEWDependent(String deviceToken, String deviceType, {int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_GetMobileLoginInfo_NEW_Dependent";
String effectiveDeviceToken = deviceToken;
if (effectiveDeviceToken.isEmpty) {
effectiveDeviceToken = AppState().getDeviceToken ?? "";
}
// if (effectiveDeviceToken.isEmpty && !AppState().getIsHuawei) {
// try {
// await Firebase.initializeApp();
// String? tok = await FirebaseMessaging.instance.getToken();
// if (tok != null && tok.isNotEmpty) {
// effectiveDeviceToken = tok;
// AppState().setDeviceToken = tok;
// }
// } catch (_) {}
// }
Map<String, dynamic> postParams = {};
postParams["DeviceToken"] = effectiveDeviceToken;
postParams["DeviceType"] = deviceType;
postParams["MemberType"] = memberType ?? MemberType.dependent.value;
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("Mohemm_GetMobileLoginInfo_NEW_Dependent Response: $json");
}
List? list = json['Mohemm_GetMobileLoginInfo_NEW_DependentList'] ??
json['Mohemm_GetMobileLoginInfo_NEW_Dependent'] ??
json['Mohemm_GetMobileLoginInfo_DependentList'] ??
json['Mohemm_GetMobileLoginInfoDependentList'] ??
json['Mohemm_GetMobileLoginInfoList'] ??
json['GetMobileLoginInfoList'] ??
json['Response'] ??
json['Data'];
if (list != null && list.isNotEmpty && list.first is Map<String, dynamic>) {
return GetMobileLoginInfoDependentModel.fromJson(list.first as Map<String, dynamic>);
}
if (json is Map<String, dynamic> &&
(json['DependentID'] != null || json['DepedentName'] != null || json['DOB'] != null || json['NationalID'] != null || json['LoginType'] != null)) {
return GetMobileLoginInfoDependentModel.fromJson(json);
}
return null;
}, url, postParams);
}
Future<GenericResponseModel?> insertMobileLoginInfoNEW( Future<GenericResponseModel?> insertMobileLoginInfoNEW(
String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType, String email, int sessionId, String employeeName, int loginType, String mobileNumber, String userName, String deviceToken, String deviceType) async {
{int? memberType}) async {
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo"; String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo";
Map<String, dynamic> postParams = { Map<String, dynamic> postParams = {
"MobileNumber": mobileNumber, "MobileNumber": mobileNumber,
@ -110,8 +39,7 @@ class LoginApiClient {
"LoginType": loginType, "LoginType": loginType,
"EmployeeName": employeeName, "EmployeeName": employeeName,
"P_SESSION_ID": sessionId, "P_SESSION_ID": sessionId,
"P_EMAIL_ADDRESS": email, "P_EMAIL_ADDRESS": email
"MemberType": memberType ?? ApiConsts.selectedMemberType.value,
}; };
postParams["DeviceToken"] = deviceToken; postParams["DeviceToken"] = deviceToken;
postParams["DeviceType"] = deviceType; postParams["DeviceType"] = deviceType;
@ -123,71 +51,6 @@ class LoginApiClient {
}, url, postParams); }, url, postParams);
} }
Future<GenericResponseModel?> insertMobileLoginInfoDependent({
required int loginType,
String? nationalId,
String? dob,
String? dependentName,
String? mobileNumber,
String? logInTokenID,
String? tokenID,
int? sessionId,
String? email,
String? deviceToken,
String? deviceType,
int? companyId,
int? versionId,
int? channel,
int? memberType,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_InsertMobileLoginInfo_Dependent";
String devType = deviceType ?? (Platform.isAndroid ? "android" : "ios");
String devToken = (deviceToken != null && deviceToken.isNotEmpty)
? deviceToken
: (AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? ""));
String effectiveToken = (tokenID != null && tokenID.isNotEmpty)
? tokenID
: (AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID ?? "");
Map<String, dynamic> postParams = {
"VersionID": versionId ?? 5,
"Channel": channel ?? 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MemberType": memberType ?? 1,
"MobileType": devType,
"MobileNumber": mobileNumber ?? "",
"UserName": nationalId ?? "",
"P_USER_NAME": nationalId ?? "",
"P_DEP_NATIONAL_ID": nationalId ?? "",
"P_DEP_BIRTH_DATE": dob ?? "",
"P_DEPENDENT_NAME": dependentName ?? "",
"EmployeeName": dependentName ?? "",
"LogInTokenID": logInTokenID ?? (AppState().postParamsObject?.logInTokenID ?? ""),
"CompanyID": companyId ?? 1,
"DeviceType": devType,
"DeviceToken": devToken,
"LoginType": loginType,
"P_SESSION_ID": sessionId ?? (AppState().postParamsObject?.pSessionId ?? 0),
"TokenID": effectiveToken,
"P_EMAIL_ADDRESS": email ?? "",
};
if (kDebugMode) {
print("Mohemm_InsertMobileLoginInfo (Dependent) Request: $postParams");
}
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("Mohemm_InsertMobileLoginInfo (Dependent) Response: $json");
}
GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
AppState().setLogged = true;
return responseData;
}, url, postParams);
}
Future<CheckMobileAppVersionModel> checkMobileAppVersion() async { Future<CheckMobileAppVersionModel> checkMobileAppVersion() async {
String url = "${ApiConsts.utilitiesRest}CheckMobileAppVersion"; String url = "${ApiConsts.utilitiesRest}CheckMobileAppVersion";
Map<String, dynamic> postParams = {}; Map<String, dynamic> postParams = {};
@ -206,15 +69,8 @@ class LoginApiClient {
}, url, postParams); }, url, postParams);
} }
Future<BasicMemberInformationModel?> mohemmSendActivationCodeByOTPNotificationType( Future<BasicMemberInformationModel?> mohemmSendActivationCodeByOTPNotificationType(int isMobileFingerPrint, String? mobileNumber, int optSendType, String? pUserName) async {
int isMobileFingerPrint,
String? mobileNumber,
int optSendType,
String? pUserName, {
String? smsSignature,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_SendActivationCodebyOTPNotificationType"; String url = "${ApiConsts.erpRest}Mohemm_SendActivationCodebyOTPNotificationType";
String? sig = smsSignature ?? await getSignature();
Map<String, dynamic> postParams = { Map<String, dynamic> postParams = {
"IsMobileFingerPrint": isMobileFingerPrint, "IsMobileFingerPrint": isMobileFingerPrint,
"MobileNumber": mobileNumber, "MobileNumber": mobileNumber,
@ -224,8 +80,6 @@ class LoginApiClient {
"NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterLoginServiceGUID, "NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterLoginServiceGUID,
}; };
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
postParams["smsSignature"] = sig ?? "";
postParams["SMSSignature"] = sig ?? "";
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams); return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
} }
@ -243,7 +97,6 @@ class LoginApiClient {
AppState().postParamsObject?.pUserName = AppState().getUserName; AppState().postParamsObject?.pUserName = AppState().getUserName;
AppState().postParamsObject?.pSelectedEmployeeNumber = AppState().getUserName; AppState().postParamsObject?.pSelectedEmployeeNumber = AppState().getUserName;
AppState().postParamsObject?.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE; AppState().postParamsObject?.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsInitConfig.setPLegislationCode = responseData.basicMemberInformation!.pLEGISLATIONCODE;
AppState().postParamsObject?.setPayrollCodeStr = responseData.memberInformationList!.first.pAYROLLCODE; AppState().postParamsObject?.setPayrollCodeStr = responseData.memberInformationList!.first.pAYROLLCODE;
AppState().setBusinessCardPrivilege = responseData.businessCardPrivilege ?? false; AppState().setBusinessCardPrivilege = responseData.businessCardPrivilege ?? false;
return responseData; return responseData;
@ -257,9 +110,8 @@ class LoginApiClient {
return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams); return await ApiClient().postJsonForObject((json) => GenericResponseModel.fromJson(json).basicMemberInformation, url, postParams);
} }
Future<GenericResponseModel?> sendPublicActivationCode(String? mobileNumber, String? pUsername, {String? smsSignature}) async { Future<GenericResponseModel?> sendPublicActivationCode(String? mobileNumber, String? pUsername) async {
String url = "${ApiConsts.erpRest}SendPublicActivationCode"; String url = "${ApiConsts.erpRest}SendPublicActivationCode";
String? sig = smsSignature ?? await getSignature();
Map<String, dynamic> postParams = { Map<String, dynamic> postParams = {
"MobileNumber": mobileNumber, "MobileNumber": mobileNumber,
"P_MOBILE_NUMBER": mobileNumber, "P_MOBILE_NUMBER": mobileNumber,
@ -268,8 +120,6 @@ class LoginApiClient {
"NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterForgotPasswordServiceGUID, "NotificationCenterAppSeviceGUID": ApiConsts.notificationCenterForgotPasswordServiceGUID,
}; };
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
postParams["smsSignature"] = sig ?? "";
postParams["SMSSignature"] = sig ?? "";
return await ApiClient().postJsonForObject((json) { return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json); GenericResponseModel responseData = GenericResponseModel.fromJson(json);
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID; AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
@ -319,152 +169,4 @@ class LoginApiClient {
return responseData; return responseData;
}, url, postParams); }, url, postParams);
} }
Future<GenericResponseModel?> memberLoginDependent({
required String nationalId,
required String birthDate,
String? deviceToken,
String? deviceType,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}MemberLogin_Dependent";
String devType = deviceType ?? (Platform.isAndroid ? "android" : "ios");
String devToken = deviceToken ?? "";
if (devToken.isEmpty) {
devToken = AppState().getIsHuawei
? AppState().getHuaweiPushToken
: (AppState().getDeviceToken ?? "");
}
if (devToken.isEmpty && !AppState().getIsHuawei) {
try {
String savedToken = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (savedToken.isNotEmpty) {
devToken = savedToken;
}
} catch (_) {}
}
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"DeviceToken": devToken,
"DeviceType": devType,
"MobileType": devType,
"P_DEP_NATIONAL_ID": nationalId,
"P_DEP_BIRTH_DATE": birthDate,
};
if (kDebugMode) {
print("MemberLogin_Dependent Request: $postParams");
}
return await ApiClient().postJsonForObject((json) {
if (kDebugMode) {
print("MemberLogin_Dependent Response: $json");
}
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
if (responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty) {
AppState().postParamsObject?.setLogInTokenID = responseData.logInTokenID;
}
String? authToken = (responseData.authenticationTokenID != null && responseData.authenticationTokenID!.trim().isNotEmpty)
? responseData.authenticationTokenID!.trim()
: null;
if (authToken != null && authToken.isNotEmpty) {
AppState().postParamsObject?.setTokenID = authToken;
AppState().setAuthenticationTokenID = authToken;
}
if (responseData.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = responseData.getWalaplusDepInfoModel;
if (responseData.getWalaplusDepInfoModel?.pDependentName != null &&
responseData.getWalaplusDepInfoModel!.pDependentName!.trim().isNotEmpty) {
AppState().setDependentName = responseData.getWalaplusDepInfoModel!.pDependentName!.trim();
}
}
AppState().setUserName = nationalId;
AppState().postParamsObject?.setUserName = nationalId;
AppState().postParamsObject?.setPUserName = nationalId;
AppState().postParamsObject?.channel = 31;
AppState().postParamsObject?.versionID = 20;
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> mohemmSendOTPDependent({
required String nationalId,
required String mobileNumber,
required String logInTokenID,
BuildContext? context,
String? smsSignature,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_SendOTP_Dependent";
String? sig = smsSignature ?? await getSignature();
Map<String, dynamic> postParams = {
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
"OTP_SendType": 1,
"MobileNumber": mobileNumber,
"IsMobileFingerPrint": 0,
"P_DEP_NATIONAL_ID": nationalId,
"P_USER_NAME": nationalId,
"LogInTokenID": logInTokenID,
"smsSignature": sig ?? "",
"SMSSignature": sig ?? "",
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData;
}, url, postParams);
}
Future<GenericResponseModel?> mohemmCheckOTPDependent({
required String logInTokenID,
required String activationCode,
required String nationalId,
required String mobileNumber,
BuildContext? context,
}) async {
String url = "${ApiConsts.erpRest}Mohemm_CheckOTP_Dependent";
Map<String, dynamic> postParams = {
"LogInTokenID": logInTokenID,
"activationCode": activationCode,
"P_USER_NAME": nationalId,
"P_DEP_NATIONAL_ID": nationalId,
"MobileNumber": mobileNumber,
"VersionID": 20,
"Channel": 31,
"LanguageID": context != null ? AppState().getLanguageID(context) : 2,
"MobileType": Platform.isAndroid ? "android" : "ios",
};
return await ApiClient().postJsonForObject((json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json);
String effectiveToken = (responseData.authenticationTokenID != null && responseData.authenticationTokenID!.trim().isNotEmpty)
? responseData.authenticationTokenID!.trim()
: (AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID ?? "");
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().setAuthenticationTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (responseData.logInTokenID != null && responseData.logInTokenID!.isNotEmpty)
? responseData.logInTokenID!
: logInTokenID;
AppState().postParamsObject?.setUserName = nationalId;
AppState().postParamsObject?.setPUserName = nationalId;
AppState().postParamsObject?.setMobileNumer = mobileNumber;
AppState().postParamsObject?.channel = 31;
AppState().postParamsObject?.versionID = 20;
AppState().setUserName = nationalId;
AppState().isAuthenticated = true;
AppState().setLogged = true;
if (responseData.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = responseData.getWalaplusDepInfoModel;
if (responseData.getWalaplusDepInfoModel?.pDependentName != null &&
responseData.getWalaplusDepInfoModel!.pDependentName!.trim().isNotEmpty) {
AppState().setDependentName = responseData.getWalaplusDepInfoModel!.pDependentName!.trim();
}
}
return responseData;
}, url, postParams);
}
} }

@ -55,9 +55,9 @@ class MyAttendanceApiClient {
}, url, postParams); }, url, postParams);
} }
Future<GenericResponseModel> getCEIDffStructure(String pFunctionName, String? empID, int? relationID) async { Future<GenericResponseModel> getCEIDffStructure(String pFunctionName, String? empID) async {
String url = "${ApiConsts.erpRest}GET_CEI_DFF_STRUCTURE"; String url = "${ApiConsts.erpRest}GET_CEI_DFF_STRUCTURE";
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": pFunctionName,"P_CONTACT_RELATIONSHIP_ID": relationID}; Map<String, dynamic> postParams = {"P_FUNCTION_NAME": pFunctionName};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID; if (empID != null) postParams["P_SELECTED_EMPLOYEE_NUMBER"] = empID;
return await ApiClient().postJsonForObject((json) { return await ApiClient().postJsonForObject((json) {

@ -1,17 +1,10 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_categories_list.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_categories_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/widgets/location/Location.dart' as location;
class OffersAndDiscountsApiClient { class OffersAndDiscountsApiClient {
static final OffersAndDiscountsApiClient _instance = OffersAndDiscountsApiClient._internal(); static final OffersAndDiscountsApiClient _instance = OffersAndDiscountsApiClient._internal();
@ -75,413 +68,4 @@ class OffersAndDiscountsApiClient {
postParams, postParams,
); );
} }
String _resolveUserName() {
String? pUserName = AppState().getUserName;
if (pUserName == null || pUserName.isEmpty) {
pUserName = AppState().postParamsObject?.userName ?? AppState().postParamsObject?.pUserName ?? AppState().memberInformationList?.eMPLOYEENUMBER;
}
return pUserName ?? "";
}
// Same Location.isEnabled -> Location.havePermission -> Location.getCurrentLocation pattern
// used by mark_attendance_widget.dart (e.g. Wifi attendance), wrapped as a Future since this
// is a background enrichment of the request, not a blocking user action - any failure (service
// disabled, permission denied, error) just resolves to null so the retailer fetch still proceeds
// without WP_Geo_Location rather than showing permission dialogs here.
Future<Position?> _getCurrentPositionSilently(BuildContext context) async {
Completer<bool> enabledCompleter = Completer<bool>();
location.Location.isEnabled((bool isEnabled) {
if (!enabledCompleter.isCompleted) enabledCompleter.complete(isEnabled);
});
bool isEnabled = await enabledCompleter.future;
debugPrint("_getCurrentPositionSilently: location service isEnabled=$isEnabled");
if (!isEnabled) return null;
Completer<bool> permissionCompleter = Completer<bool>();
location.Location.havePermission((bool hasPermission) {
if (!permissionCompleter.isCompleted) permissionCompleter.complete(hasPermission);
});
bool hasPermission = await permissionCompleter.future;
debugPrint("_getCurrentPositionSilently: havePermission=$hasPermission");
if (!hasPermission) return null;
Completer<Position?> positionCompleter = Completer<Position?>();
location.Location.getCurrentLocation(
(Position position, bool isMocked) {
debugPrint("_getCurrentPositionSilently: got position lat=${position.latitude}, lng=${position.longitude}, isMocked=$isMocked");
if (!positionCompleter.isCompleted) positionCompleter.complete(position);
},
() {
debugPrint("_getCurrentPositionSilently: getCurrentLocation error callback fired");
if (!positionCompleter.isCompleted) positionCompleter.complete(null);
},
context,
);
return positionCompleter.future;
}
String _resolveDepNationalId() {
if (!ApiConsts.isDependetLogin) {
return "";
}
return AppState().getUserName ?? "";
}
String _resolveMobileNumber({String? overrideMobile}) {
if (overrideMobile != null && overrideMobile.trim().isNotEmpty) {
return overrideMobile.trim();
}
String? mobile = AppState().postParamsObject?.mobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberInformationList?.eMPLOYEEMOBILENUMBER;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().walaplusDepInfoModel?.pEmpMobileNumber;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberLoginList?.pMOBILENUMBER;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
mobile = AppState().memberInformationList?.mobileNumberWithZipCode;
if (mobile != null && mobile.trim().isNotEmpty) {
return mobile.trim();
}
return "";
}
Map<String, dynamic> _getBaseWalaPlusParams() {
Map<String, dynamic> base = {};
base.addAll(AppState().postParamsJson);
if (ApiConsts.isDependetLogin) {
String? token = AppState().postParamsObject?.tokenID ?? AppState().authenticationTokenID;
if (token != null && token.isNotEmpty) {
base["TokenID"] = token;
}
String? logInToken = AppState().postParamsObject?.logInTokenID;
if (logInToken != null && logInToken.isNotEmpty) {
base["LogInTokenID"] = logInToken;
}
}
_cleanWalaPlusParams(base);
return base;
}
void _cleanWalaPlusParams(Map<String, dynamic> map) {
map.remove("P_EMAIL_ADDRESS");
map.remove("P_SESSION_ID");
map.remove("PayrollCodeStr");
map.remove("LegislationCodeStr");
map.remove("P_SELECTED_EMPLOYEE_NUMBER");
map.removeWhere((key, value) => value == null);
}
Future<List<WalaPlusCategoryModel>> getWalaPlusCategories({BuildContext? context, String? mobileNumber}) async {
String url = ApiConsts.walaPlusCategories;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
(response) {
List<WalaPlusCategoryModel> categories = [];
if (response != null) {
dynamic cats = response['WalaPlus_Categories'] ?? response['walaPlus_Categories'] ?? response['WalaPlusCategories'] ?? response['result'];
if (cats is String) {
try {
cats = json.decode(cats);
} catch (e) {
debugPrint("Error decoding WalaPlus_Categories: $e");
}
}
if (cats is List) {
cats.forEach((v) {
if (v is Map<String, dynamic>) {
categories.add(WalaPlusCategoryModel.fromJson(v));
} else if (v is Map) {
categories.add(WalaPlusCategoryModel.fromJson(Map<String, dynamic>.from(v)));
}
});
}
}
return categories;
},
url,
postParams,
);
}
// Confirmed with backend team: WP_Offer_Type accepts "online" and "store".
static const String walaPlusOfferTypeOnline = "online";
static const String walaPlusOfferTypeInStore = "store";
// Maps the screen's internal filter key ("online"/"instore"/"all") to the WP_Offer_Type value
// the backend expects. Returns null for "all" (or anything unrecognized) so callers can skip
// sending WP_Offer_Type entirely.
static String? mapOfferTypeFilter(String internalKey) {
switch (internalKey) {
case "online":
return walaPlusOfferTypeOnline;
case "instore":
return walaPlusOfferTypeInStore;
default:
return null;
}
}
Future<WalaPlusRetailersResponse?> getWalaPlusRetailers({
String? categoryId,
int page = 1,
int perPage = 10,
String? geoLocation,
String? offerType,
BuildContext? context,
String? mobileNumber,
}) async {
String url = ApiConsts.walaPlusRetailers;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
String effectiveCatId = (categoryId == "all" || categoryId == "0" || categoryId == null) ? "" : categoryId;
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
postParams["WP_CategoryID"] = effectiveCatId;
if (offerType != null && offerType.trim().isNotEmpty) {
postParams["WP_Offer_Type"] = offerType.trim();
}
postParams["WP_Page"] = page;
postParams["WP_Per_page"] = perPage;
if (geoLocation != null && geoLocation.isNotEmpty) {
postParams["WP_Geo_Location"] = geoLocation;
} else if (context != null) {
try {
Position? position = await _getCurrentPositionSilently(context);
if (position != null) {
postParams["WP_Geo_Location"] = "${position.latitude},${position.longitude}";
}
} catch (e) {
debugPrint("getWalaPlusRetailers: unable to get current location: $e");
}
}
_cleanWalaPlusParams(postParams);
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
dynamic val = response['WalaPlus_Retailers'] ?? response['walaPlus_Retailers'] ?? response['WalaPlusRetailers'] ?? response['WP_Retailers'] ?? response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_Retailers: $e");
}
}
if (val is Map<String, dynamic>) {
return WalaPlusRetailersResponse.fromJson(val);
} else if (val is Map) {
return WalaPlusRetailersResponse.fromJson(Map<String, dynamic>.from(val));
} else if (val is List) {
List<WalaPlusRetailerModel> retailers = [];
for (var v in val) {
if (v is Map<String, dynamic>) {
retailers.add(WalaPlusRetailerModel.fromJson(v));
} else if (v is Map) {
retailers.add(WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(v)));
}
}
return WalaPlusRetailersResponse(retailers: retailers, page: 1, pagesCount: 1, perPage: retailers.length);
}
}
return null;
},
url,
postParams,
);
}
Future<WalaPlusRetailerModel?> getWalaPlusRetailerById({required String retailerId, BuildContext? context, String? mobileNumber}) async {
String url = ApiConsts.walaPlusRetailersByID;
String pUserName = _resolveUserName();
String pMobileNumber = _resolveMobileNumber(overrideMobile: mobileNumber);
String pDepNationalId = _resolveDepNationalId();
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_RetailerID"] = retailerId;
postParams["RetailerID"] = retailerId;
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
if (pMobileNumber.isNotEmpty) {
postParams["MobileNumber"] = pMobileNumber;
}
postParams["MemberType"] = ApiConsts.isDependetLogin ? 2 : 1;
postParams["CompanyID"] = 1;
if (context != null) {
try {
Position? position = await _getCurrentPositionSilently(context);
log("context position: $position");
if (position != null) {
postParams["WP_Geo_Location"] = "${position.latitude},${position.longitude}";
}
} catch (e) {
debugPrint("getWalaPlusRetailerById: unable to get current location: $e");
}
}
_cleanWalaPlusParams(postParams);
debugPrint("getWalaPlusRetailerById: calling $url with WP_RetailerID: $retailerId");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint("getWalaPlusRetailerById response keys: ${response is Map ? response.keys.toList() : 'non-map'}");
dynamic val = response['WalaPlus_RetailersByID'] ?? response['walaPlus_RetailersByID'] ?? response['WalaPlusRetailersByID'] ?? response['WP_RetailersByID'] ?? response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_RetailersByID JSON string: $e");
}
}
WalaPlusRetailerModel? model;
if (val is List && val.isNotEmpty) {
var item = val.first;
if (item is Map<String, dynamic>) {
model = WalaPlusRetailerModel.fromJson(item);
} else if (item is Map) {
model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(item));
}
} else if (val is Map) {
Map<String, dynamic> map = (val is Map<String, dynamic>) ? val : Map<String, dynamic>.from(val);
if (map.containsKey('retailer')) {
var byIdResponse = WalaPlusRetailersByIDResponse.fromJson(map);
model = byIdResponse.primaryRetailer ?? WalaPlusRetailerModel.fromJson(map);
} else {
model = WalaPlusRetailerModel.fromJson(map);
}
if ((model?.offers == null || model!.offers!.isEmpty) && (map.containsKey('offers') || map.containsKey('Offers'))) {
model?.offers = WalaPlusRetailerModel.parseOffersList(map['offers'] ?? map['Offers']);
}
if ((model?.locations == null || model!.locations!.isEmpty) &&
(map.containsKey('locations') || map.containsKey('Locations') || map.containsKey('location') || map.containsKey('Location'))) {
model?.locations = WalaPlusRetailerModel.parseLocationsList(map['locations'] ?? map['Locations'] ?? map['location'] ?? map['Location']);
}
} else if (response is Map<String, dynamic> && response.containsKey('name')) {
model = WalaPlusRetailerModel.fromJson(response);
}
if (model != null) {
if (response is Map) {
if ((model.offers == null || model.offers!.isEmpty) && (response.containsKey('offers') || response.containsKey('Offers'))) {
model.offers = WalaPlusRetailerModel.parseOffersList(response['offers'] ?? response['Offers']);
}
if ((model.locations == null || model.locations!.isEmpty) &&
(response.containsKey('locations') || response.containsKey('Locations') || response.containsKey('location') || response.containsKey('Location'))) {
model.locations = WalaPlusRetailerModel.parseLocationsList(response['locations'] ?? response['Locations'] ?? response['location'] ?? response['Location']);
}
}
debugPrint("getWalaPlusRetailerById: parsed retailer '${model.name}', offers: ${model.offers?.length ?? 0}, locations: ${model.locations?.length ?? 0}");
return model;
}
}
return null;
},
url,
postParams,
);
}
String _resolveUserReference({String? overrideUserReference}) {
if (overrideUserReference != null && overrideUserReference.trim().isNotEmpty) {
return overrideUserReference.trim();
}
String? ref = AppState().memberInformationList?.eMPLOYEENUMBER?.toString();
if (ref != null && ref.trim().isNotEmpty) {
return ref.trim();
}
String? pUserName = AppState().getUserName;
if (pUserName != null && pUserName.trim().isNotEmpty) {
return pUserName.trim();
}
return "";
}
Future<Map<String, dynamic>?> redeemWalaPlusOffer({required String offerId, String? offerType, String? userReference, BuildContext? context}) async {
String url = ApiConsts.walaPlusOfferRedeem;
String pUserName = _resolveUserName();
String pUserReference = _resolveUserReference(overrideUserReference: userReference);
Map<String, dynamic> postParams = _getBaseWalaPlusParams();
postParams["WP_User_Reference"] = pUserReference;
postParams["WP_Offer_Id"] = offerId;
postParams["WP_Offer_Type"] = (offerType != null && offerType.trim().isNotEmpty) ? offerType.trim() : "promo";
postParams["UserName"] = pUserName;
postParams["P_USER_NAME"] = pUserName;
if (ApiConsts.isDependetLogin) {
String pDepNationalId = _resolveDepNationalId();
if (pDepNationalId.isNotEmpty) {
postParams["P_DEP_NATIONAL_ID"] = pDepNationalId;
}
postParams["MemberType"] = 2;
} else {
postParams["P_DEP_NATIONAL_ID"] = "";
postParams["MemberType"] = 1;
}
_cleanWalaPlusParams(postParams);
debugPrint("redeemWalaPlusOffer calling $url with params: $postParams");
return await ApiClient().postJsonForObject(
(response) {
if (response != null) {
debugPrint("redeemWalaPlusOffer response: $response");
dynamic val = response['WalaPlus_OfferRedeem'] ?? response['walaPlus_OfferRedeem'] ?? response['WalaPlusOfferRedeem'] ?? response['result'];
if (val is String) {
try {
val = json.decode(val);
} catch (e) {
debugPrint("Error decoding WalaPlus_OfferRedeem: $e");
}
}
if (val is Map<String, dynamic>) {
return val;
} else if (val is Map) {
return Map<String, dynamic>.from(val);
}
}
return null;
},
url,
postParams,
);
}
} }

@ -1,11 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:mohem_flutter_app/api/api_client.dart'; import 'package:mohem_flutter_app/api/api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/consts.dart'; import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/date_uitl.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/get_approves_list_model.dart'; import 'package:mohem_flutter_app/models/get_approves_list_model.dart';
import 'package:mohem_flutter_app/models/get_contact_dff_structure_list.dart'; import 'package:mohem_flutter_app/models/get_contact_dff_structure_list.dart';
@ -26,29 +24,8 @@ import 'package:mohem_flutter_app/models/profile/submit_address_transaction.dart
import 'package:mohem_flutter_app/models/profile/submit_basic_details_transaction_model.dart'; import 'package:mohem_flutter_app/models/profile/submit_basic_details_transaction_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_contact_transaction_list_model.dart'; import 'package:mohem_flutter_app/models/profile/submit_contact_transaction_list_model.dart';
import 'package:mohem_flutter_app/models/profile/submit_phone_transactions.dart'; import 'package:mohem_flutter_app/models/profile/submit_phone_transactions.dart';
import 'package:mohem_flutter_app/models/profile/patient_info_model.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/start_phone_approval_process_model.dart'; import 'package:mohem_flutter_app/models/start_phone_approval_process_model.dart';
// The ERP WCF service's DataContractJsonSerializer rejects any DateTime-typed field unless
// it's wrapped as "/Date(ms)/" - confirmed by its own error on this exact call:
// "DateTime content '6/23/1989 12:00:00 AM' does not start with '/Date(' and end with ')/'
// as required for JSON." GET_EMPLOYEE_CONTACTS hands DATE_OF_BIRTH back as a plain
// "M/d/yyyy h:mm:ss a" string, so it has to be converted before going back out as P_BIRTH_DATE.
String? _toWcfJsonDate(rawDate) {
if (rawDate == null) return null;
String raw = rawDate.toString().trim();
if (raw.isEmpty) return null;
if (raw.contains('/Date(')) return raw;
try {
DateTime parsed = DateUtil.convertSimpleStringDateToDate(raw);
int utcMillis = DateTime.utc(parsed.year, parsed.month, parsed.day).millisecondsSinceEpoch;
return "/Date($utcMillis)/";
} catch (_) {
return null;
}
}
class ProfileApiClient { class ProfileApiClient {
static final ProfileApiClient _instance = ProfileApiClient._internal(); static final ProfileApiClient _instance = ProfileApiClient._internal();
@ -56,140 +33,99 @@ class ProfileApiClient {
factory ProfileApiClient() => _instance; factory ProfileApiClient() => _instance;
// GET_EMP_BUSINESS_CARD_INFO requires the literal TokenID below to validate the call -
// it is not the usual session TokenID, so we don't merge AppState().postParamsJson here.
Future<GenericResponseModel?> getEmployeeBusinessCardInfo() async {
String url = "${ApiConsts.erpRest}GET_EMP_BUSINESS_CARD_INFO";
String deviceToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? "");
Map<String, dynamic> postParams = {
"VersionID": AppState().postParamsObject?.versionID,
"Channel": AppState().postParamsObject?.channel,
"LanguageID": AppState().postParamsObject?.languageID,
"TokenID": "GET_EMP_BUSINESS_CARD_INFO",
"P_USER_NAME": AppState().getUserName ?? AppState().postParamsObject?.userName ?? AppState().postParamsObject?.pUserName,
"DeviceToken": deviceToken,
"DeviceType": Platform.isAndroid ? "android" : "ios",
};
print(jsonEncode(postParams));
return await ApiClient().postJsonForObject(
(json) {
return GenericResponseModel.fromJson(json);
},
url,
postParams,
);
}
Future<List<GetEmployeeContactsList>> getEmployeeContacts() async { Future<List<GetEmployeeContactsList>> getEmployeeContacts() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_CONTACTS"; String url = "${ApiConsts.erpRest}GET_EMPLOYEE_CONTACTS";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeContactsList ?? []; return responseData.getEmployeeContactsList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<GetEmployeeBasicDetailsList>> getEmployeeBasicDetails() async { Future<List<GetEmployeeBasicDetailsList>> getEmployeeBasicDetails() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_BASIC_DETAILS"; String url = "${ApiConsts.erpRest}GET_EMPLOYEE_BASIC_DETAILS";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeBasicDetailsList ?? []; return responseData.getEmployeeBasicDetailsList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<GetEmployeePhonesList>> getEmployeePhones() async { Future<List<GetEmployeePhonesList>> getEmployeePhones() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_PHONES"; String url = "${ApiConsts.erpRest}GET_EMPLOYEE_PHONES";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeePhonesList ?? []; return responseData.getEmployeePhonesList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<GetEmployeeAddressList>> getEmployeeAddress() async { Future<List<GetEmployeeAddressList>> getEmployeeAddress() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_ADDRESS"; String url = "${ApiConsts.erpRest}GET_EMPLOYEE_ADDRESS";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getEmployeeAddressList ?? []; return responseData.getEmployeeAddressList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<EmployeeDocumentsList>> getEmployeeDocuments() async { Future<List<EmployeeDocumentsList>> getEmployeeDocuments() async {
String url = "${ApiConsts.erpRest}GET_EMPLOYEE_DOCUMENTS"; String url = "${ApiConsts.erpRest}GET_EMPLOYEE_DOCUMENTS";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.employeeDocumentsList ?? []; return responseData.employeeDocumentsList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future updateEmpImage(img) async { Future updateEmpImage(img) async {
String url = "${ApiConsts.erpRest}UPDATE_EMPLOYEE_IMAGE"; String url = "${ApiConsts.erpRest}UPDATE_EMPLOYEE_IMAGE";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_IMAGE": img}; Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_IMAGE": img};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
// GenericResponseModel? responseData = GenericResponseModel.fromJson(json); // GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return json['UpdateEmployeeImageList']; return json['UpdateEmployeeImageList'];
}, }, url, postParams);
url,
postParams,
);
} }
Future<GenericResponseModel> getDffStructure(String pFunctionName, String uRL, String requestType) async { Future<GenericResponseModel> getDffStructure(String pFunctionName, String uRL, String requestType) async {
String url = ApiConsts.erpRest + uRL; String url = ApiConsts.erpRest + uRL;
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_REQUEST_TYPE": pFunctionName, "P_FUNCTION_NAME": pFunctionName, "P_COUNTRY_CODE": "SA"}; Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_REQUEST_TYPE": pFunctionName, "P_FUNCTION_NAME": pFunctionName, "P_COUNTRY_CODE": "SA"};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData; return responseData;
}, }, url, postParams);
url,
postParams,
);
} }
Future<GenericResponseModel> getColStructure(String pFunctionName, String uRL, String requestType) async { Future<GenericResponseModel> getColStructure(String pFunctionName, String uRL, String requestType) async {
String url = ApiConsts.erpRest + uRL; String url = ApiConsts.erpRest + uRL;
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_FUNCTION_NAME": pFunctionName, "P_REQUEST_TYPE": requestType}; Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_FUNCTION_NAME": pFunctionName, "P_REQUEST_TYPE": requestType};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData; return responseData;
}, }, url, postParams);
url,
postParams,
);
} }
Future<ESERVICESVS> getValueSetValues(String pSegmentName, String pDescFlexContextCode, String pDescFlexName, List<Map<String, dynamic>> list) async { Future<ESERVICESVS> getValueSetValues(String pSegmentName, String pDescFlexContextCode, String pDescFlexName, List<Map<String, dynamic>> list) async {
@ -206,38 +142,41 @@ class ProfileApiClient {
"GetValueSetValuesTBL": list, "GetValueSetValuesTBL": list,
}; };
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getValueSetValuesList!.first; return responseData.getValueSetValuesList!.first;
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<GetPhoneNumberTypesModel>> getPhoneNumberTypes() async { Future<List<GetPhoneNumberTypesModel>> getPhoneNumberTypes() async {
String url = "${ApiConsts.erpRest}GET_OBJECT_VALUES"; String url = "${ApiConsts.erpRest}GET_OBJECT_VALUES";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_OBJECT_NAME": "PHONE_TYPE", "P_OBJECT_TYPE": "LOOKUP"}; Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_OBJECT_NAME": "PHONE_TYPE", "P_OBJECT_TYPE": "LOOKUP"};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getObjectValuesList ?? []; return responseData.getObjectValuesList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future<SubmitContactTransactionList?> submetConatctTransaction(String pFunctionName, String actionType, int contactRelationId, String date) async { Future<SubmitContactTransactionList?> submetConatctTransaction(
String pFunctionName,
String actionType,
int contactRelationId,
String date,
) async {
String url = "${ApiConsts.erpRest}SUBMIT_CONTACT_TRANSACTION"; String url = "${ApiConsts.erpRest}SUBMIT_CONTACT_TRANSACTION";
Map<String, dynamic> postParams = { Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E", "P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999, "P_SELECTED_RESP_ID": -999,
"P_FUNCTION_NAME": pFunctionName, "P_FUNCTION_NAME": pFunctionName,
"EITTransactionTBL": [ "EITTransactionTBL": [
{"TRANSACTION_NUMBER": 1, "NAME": "DATE_END", "VARCHAR2_VALUE": null, "NUMBER_VALUE": null, "DATE_VALUE": date}, {
"TRANSACTION_NUMBER": 1,
"NAME": "DATE_END",
"VARCHAR2_VALUE": null,
"NUMBER_VALUE": null,
"DATE_VALUE": date,
}
], ],
"P_CONTACT_RELATIONSHIP_ID": contactRelationId, "P_CONTACT_RELATIONSHIP_ID": contactRelationId,
"P_ACTION": actionType, "P_ACTION": actionType,
@ -245,29 +184,28 @@ class ProfileApiClient {
"LegislationCodeStr": "SA", "LegislationCodeStr": "SA",
}; };
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.submitContactTransactionList; return responseData.submitContactTransactionList;
}, }, url, postParams);
url,
postParams,
);
} }
Future<GetApprovesList?> getApproves(String action, String comments, int transactionId, String itemKey) async { Future<GetApprovesList?> getApproves(String action, String comments, int transactionId, String itemKey) async {
String url = "${ApiConsts.erpRest}GET_APPROVERS_LIST"; String url = "${ApiConsts.erpRest}GET_APPROVERS_LIST";
Map<String, dynamic> postParams = {"P_AME_TRANSACTION_TYPE": "SSHRMS", "P_PAGE_LIMIT": 1000, "P_PAGE_NUM": 1, "P_TRANSACTION_ID": transactionId, "P_ACTION_MODE": action, "P_COMMENTS": comments}; Map<String, dynamic> postParams = {
"P_AME_TRANSACTION_TYPE": "SSHRMS",
"P_PAGE_LIMIT": 1000,
"P_PAGE_NUM": 1,
"P_TRANSACTION_ID": transactionId,
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
// return responseData.getApprovesList; // return responseData.getApprovesList;
return (responseData.getApprovesList?.length ?? 0) > 0 ? responseData.getApprovesList!.first : null; return (responseData.getApprovesList?.length ?? 0) > 0 ? responseData.getApprovesList!.first : null;
}, }, url, postParams);
url,
postParams,
);
} }
Future<SubmitPhonesTransactionList> submitPhoneNumbers(List<GetEmployeePhonesList> empList) async { Future<SubmitPhonesTransactionList> submitPhoneNumbers(List<GetEmployeePhonesList> empList) async {
@ -276,49 +214,50 @@ class ProfileApiClient {
"P_MENU_TYPE": "E", "P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999, "P_SELECTED_RESP_ID": -999,
"P_FUNCTION_NAME": "HR_PERINFO_SS", "P_FUNCTION_NAME": "HR_PERINFO_SS",
"P_MBL_PHONES_TBL": "P_MBL_PHONES_TBL": empList.map((element) {
empList.map((element) {
return element.toJson(); return element.toJson();
}).toList(), }).toList()
}; };
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return SubmitPhonesTransactionList.fromJson(responseData.submitPhonesTransactionList ?? {}); return SubmitPhonesTransactionList.fromJson(responseData.submitPhonesTransactionList ?? {});
}, }, url, postParams);
url,
postParams,
);
} }
Future<StartPhoneApprovalProcess?> startPhoneApprovalProcess(String action, String comments, String itemKey, int transactionId) async { Future<StartPhoneApprovalProcess?> startPhoneApprovalProcess(String action, String comments, String itemKey, int transactionId) async {
String url = "${ApiConsts.erpRest}START_PHONES_APPROVAL_PROCESS"; String url = "${ApiConsts.erpRest}START_PHONES_APPROVAL_PROCESS";
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_ACTION_MODE": action, "P_COMMENTS": comments, "P_ITEM_KEY": itemKey, "P_TRANSACTION_ID": transactionId}; Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
"P_MENU_TYPE": "E",
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
"P_ITEM_KEY": itemKey,
"P_TRANSACTION_ID": transactionId,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startPhonesApprovalProcessList; return responseData.startPhonesApprovalProcessList;
}, }, url, postParams);
url,
postParams,
);
} }
Future<StartAddressApprovalProcess?> startAddressApprovalProcess(String action, String comments, String itemKey, int transactionId) async { Future<StartAddressApprovalProcess?> startAddressApprovalProcess(String action, String comments, String itemKey, int transactionId) async {
String url = "${ApiConsts.erpRest}START_ADDRESS_APPROVAL_PROCESS"; String url = "${ApiConsts.erpRest}START_ADDRESS_APPROVAL_PROCESS";
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_ACTION_MODE": action, "P_COMMENTS": comments, "P_ITEM_KEY": itemKey, "P_TRANSACTION_ID": transactionId}; Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
"P_MENU_TYPE": "E",
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
"P_ITEM_KEY": itemKey,
"P_TRANSACTION_ID": transactionId,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startAddressApprovalProcessList; return responseData.startAddressApprovalProcessList;
}, }, url, postParams);
url,
postParams,
);
} }
Future<SubmitBasicDetailsTransactionList> submitBasicDetailsTransaction(String pDescFlexContextCode, String pFunctionName, List<Map<String, dynamic>> list, int correctOrNew) async { Future<SubmitBasicDetailsTransactionList> submitBasicDetailsTransaction(String pDescFlexContextCode, String pFunctionName, List<Map<String, dynamic>> list, int correctOrNew) async {
@ -334,38 +273,31 @@ class ProfileApiClient {
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
postParams["EITTransactionTBL"] = list; postParams["EITTransactionTBL"] = list;
postParams["EITTransactionTBLModel"] = list; postParams["EITTransactionTBLModel"] = list;
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json); GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.submitBasicDetTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson()); return responseData.submitBasicDetTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson());
}, }, url, postParams);
url,
postParams,
);
} }
Future<StartAddressApprovalProcess?> startBasicDetailsApprovalProcess(String action, String comments, String itemKey, int transactionId) async { Future<StartAddressApprovalProcess?> startBasicDetailsApprovalProcess(String action, String comments, String itemKey, int transactionId) async {
String url = "${ApiConsts.erpRest}START_BASIC_DET_APPR_PROCESS"; String url = "${ApiConsts.erpRest}START_BASIC_DET_APPR_PROCESS";
Map<String, dynamic> postParams = {"P_SELECTED_RESP_ID": -999, "P_MENU_TYPE": "E", "P_ACTION_MODE": action, "P_COMMENTS": comments, "P_ITEM_KEY": itemKey, "P_TRANSACTION_ID": transactionId}; Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999,
"P_MENU_TYPE": "E",
"P_ACTION_MODE": action,
"P_COMMENTS": comments,
"P_ITEM_KEY": itemKey,
"P_TRANSACTION_ID": transactionId,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.startAddressApprovalProcessList; return responseData.startAddressApprovalProcessList;
}, }, url, postParams);
url,
postParams,
);
} }
Future<SubmitAddressTransaction> submitAddressTransaction( Future<SubmitAddressTransaction> submitAddressTransaction(
String pDescFlexContextCode, String pDescFlexContextCode, String pFunctionName, List<Map<String, dynamic>> list, int correctOrNew, String countryCode, String effectiveDate) async {
String pFunctionName,
List<Map<String, dynamic>> list,
int correctOrNew,
String countryCode,
String effectiveDate,
) async {
String url = "${ApiConsts.erpRest}SUBMIT_ADDRESS_TRANSACTION"; String url = "${ApiConsts.erpRest}SUBMIT_ADDRESS_TRANSACTION";
Map<String, dynamic> postParams = { Map<String, dynamic> postParams = {
"P_SELECTED_RESP_ID": -999, "P_SELECTED_RESP_ID": -999,
@ -382,28 +314,23 @@ class ProfileApiClient {
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
postParams["EITTransactionTBL"] = list; postParams["EITTransactionTBL"] = list;
// postParams["EITTransactionTBLModel"] = list; // postParams["EITTransactionTBLModel"] = list;
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel responseData = GenericResponseModel.fromJson(json); GenericResponseModel responseData = GenericResponseModel.fromJson(json);
return responseData.submitAddressTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson()); return responseData.submitAddressTransactionList!; //ESERVICESDV.fromJson(responseData.getDefaultValueList!.toJson());
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<GetCountriesListModel>> getCountriesList() async { Future<List<GetCountriesListModel>> getCountriesList() async {
String url = "${ApiConsts.erpRest}GET_COUNTRIES"; String url = "${ApiConsts.erpRest}GET_COUNTRIES";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getCountriesList ?? []; return responseData.getCountriesList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
// add & update family member // add & update family member
@ -411,75 +338,73 @@ class ProfileApiClient {
String url = "${ApiConsts.erpRest}GET_BASIC_DET_DFF_STRUCTURE"; String url = "${ApiConsts.erpRest}GET_BASIC_DET_DFF_STRUCTURE";
Map<String, dynamic> postParams = {"P_FUNCTION_NAME": "HR_PERINFO_SS", "P_MENU_TYPE": "E", "P_REQUEST_TYPE": "CONTACT", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {"P_FUNCTION_NAME": "HR_PERINFO_SS", "P_MENU_TYPE": "E", "P_REQUEST_TYPE": "CONTACT", "P_SELECTED_RESP_ID": -999};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getBasicDetDffStructureList ?? []; return responseData.getBasicDetDffStructureList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
// add & update family member // add & update family member
Future<List<GetContactColsStructureList>> getContactColsStructureList(String actionType) async { Future<List<GetContactColsStructureList>> getContactColsStructureList(String actionType) async {
String url = "${ApiConsts.erpRest}GET_CONTACT_COLS_STRUCTURE"; String url = "${ApiConsts.erpRest}GET_CONTACT_COLS_STRUCTURE";
Map<String, dynamic> postParams = {"P_ACTION": actionType, "P_FUNCTION_NAME": "HR_PERINFO_SS"}; Map<String, dynamic> postParams = {
"P_ACTION": actionType,
"P_FUNCTION_NAME": "HR_PERINFO_SS",
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactColsStructureList ?? []; return responseData.getContactColsStructureList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
// update family member // update family member
Future<List<GetContactDetailsList>> getContactDetailsList(int contactRelationId) async { Future<List<GetContactDetailsList>> getContactDetailsList(int contactRelationId) async {
String url = "${ApiConsts.erpRest}GET_CONTACT_DETAILS"; String url = "${ApiConsts.erpRest}GET_CONTACT_DETAILS";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_CONTACT_RELATIONSHIP_ID": contactRelationId}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_CONTACT_RELATIONSHIP_ID": contactRelationId,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactDetailsList ?? []; return responseData.getContactDetailsList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
// add & update family member // add & update family member
Future<List<GetContactDffStructureList>> getContactDffStructureList() async { Future<List<GetContactDffStructureList>> getContactDffStructureList() async {
String url = "${ApiConsts.erpRest}GET_CONTACT_DFF_STRUCTURE"; String url = "${ApiConsts.erpRest}GET_CONTACT_DFF_STRUCTURE";
Map<String, dynamic> postParams = {"P_DESC_FLEX_CONTEXT_CODE": "HMG_ADD_CONTACT_DETAILS", "P_FUNCTION_NAME": "HR_PERINFO_SS", "P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999}; Map<String, dynamic> postParams = {
"P_DESC_FLEX_CONTEXT_CODE": "HMG_ADD_CONTACT_DETAILS",
"P_FUNCTION_NAME": "HR_PERINFO_SS",
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getContactDffStructureList ?? []; return responseData.getContactDffStructureList ?? [];
}, }, url, postParams);
url,
postParams,
);
} }
Future<SubmitContactTransactionList?> submitContactTransactionAddAndUpdate(String actionType, relationId, List<Map<String, dynamic>> list) async { Future<SubmitContactTransactionList?> submitContactTransactionAddAndUpdate(String actionType, relationId, List<Map<String, dynamic>> list) async {
String url = "${ApiConsts.erpRest}SUBMIT_CONTACT_TRANSACTION"; String url = "${ApiConsts.erpRest}SUBMIT_CONTACT_TRANSACTION";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_FUNCTION_NAME": "HR_PERINFO_SS", "P_ACTION": actionType, "P_CONTACT_RELATIONSHIP_ID": relationId}; Map<String, dynamic> postParams = {
"P_MENU_TYPE": "E",
"P_SELECTED_RESP_ID": -999,
"P_FUNCTION_NAME": "HR_PERINFO_SS",
"P_ACTION": actionType,
"P_CONTACT_RELATIONSHIP_ID": relationId,
};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
postParams["EITTransactionTBL"] = list; postParams["EITTransactionTBL"] = list;
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.submitContactTransactionList; return responseData.submitContactTransactionList;
}, }, url, postParams);
url,
postParams,
);
} }
Future<List<GetPerformanceAppraisalList>> getPerformanceAppraisal() async { Future<List<GetPerformanceAppraisalList>> getPerformanceAppraisal() async {
@ -487,90 +412,9 @@ class ProfileApiClient {
Map<String, dynamic> postParams = {}; Map<String, dynamic> postParams = {};
postParams.addAll(AppState().postParamsJson); postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject( return await ApiClient().postJsonForObject((json) {
(json) {
GenericResponseModel? responseData = GenericResponseModel.fromJson(json); GenericResponseModel? responseData = GenericResponseModel.fromJson(json);
return responseData.getPerformanceAppraisalList ?? []; return responseData.getPerformanceAppraisalList ?? [];
}, }, url, postParams);
url,
postParams,
);
}
Future<PatientInfoModel?> getPatientInfo({required String nationalId, required String dob, String? logInTokenID}) async {
// NHIC Service GetPatientInfo only works on Production (hmgwebservices.com)
// String url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo";
String url = "${ApiConsts.erpRest}GetMemberInfo";
String token = logInTokenID ?? AppState().postParamsObject?.logInTokenID ?? "";
Map<String, dynamic> postParams = {
"PatientIdentificationID": int.tryParse(nationalId.trim()) ?? nationalId.trim(),
"PatientOutSA": 0,
"DOB": dob.trim(),
"LogInTokenID": token,
"PatientMobileNumber": AppState().postParamsObject?.mobileNumber, // added this as per e-services changed
"TokenID": AppState().postParamsObject?.tokenID, // added this as per e-services changed
"LanguageID": AppState().postParamsObject?.languageID, // added this as per e-services changed
"Channel": AppState().postParamsObject?.channel,
"VersionID": AppState().postParamsObject?.versionID,
"UserName": AppState().postParamsObject?.userName,
};
print(jsonEncode(postParams));
return await ApiClient().postJsonForObject(
(json) {
return PatientInfoModel.fromJson(json);
},
url,
postParams,
);
}
Future<GenericResponseModel?> processContactMazayaAccess({
required String accessAction,
int? contactRelationshipId,
int? contactPersonId,
int? personId,
// Same shape as the NHIC-style patient-info call: PatientIdentificationID is the
// dependent's national ID (GetEmployeeContactsList.nATIONALIDENTIFIER), PatientOutSA is
// always 0, and the other two are passed straight through from that same contact's
// P_BIRTH_DATE / P_NHIC_VERIFIED_FLAG response fields.
String? patientIdentificationId,
patientBirthDate,
String? nhicVerifiedFlag,
}) async {
String url = "${ApiConsts.erpRest}PROCESS_CONTACT_MAZAYA_ACCESS";
String action = accessAction.trim().toLowerCase() == "revoke" ? "Revoke" : "Grant";
bool isGrant = action == "Grant";
Map<String, dynamic> postParams = {"P_MENU_TYPE": "E", "P_SELECTED_RESP_ID": -999, "P_ACCESS_ACTION": action};
if (contactRelationshipId != null) {
postParams["P_CONTACT_RELATIONSHIP_ID"] = contactRelationshipId;
}
if (contactPersonId != null) {
postParams["P_CONTACT_PERSON_ID"] = contactPersonId;
}
if (personId != null) {
postParams["P_PERSON_ID"] = personId;
}
// Only the Grant call carries these - Revoke's payload stays exactly as it was before
// this feature. P_BIRTH_DATE/P_NHIC_VERIFIED_FLAG are always sent (even as null, if the
// source contact from GET_EMPLOYEE_CONTACTS didn't have a value) once we're granting.
if (isGrant) {
postParams["PatientOutSA"] = 0;
postParams["P_BIRTH_DATE"] = _toWcfJsonDate(patientBirthDate);
postParams["P_NHIC_VERIFIED_FLAG"] = nhicVerifiedFlag?.trim();
if (patientIdentificationId != null && patientIdentificationId.trim().isNotEmpty) {
// National ID comes formatted with dashes (e.g. "2-5616-1545-7") - the backend wants
// the plain digit string (e.g. "2561615457").
postParams["PatientIdentificationID"] = patientIdentificationId.replaceAll('-', '').trim();
}
}
postParams.addAll(AppState().postParamsJson);
return await ApiClient().postJsonForObject(
(json) {
return GenericResponseModel.fromJson(json);
},
url,
postParams,
);
} }
} }

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart'; import 'package:mohem_flutter_app/models/chat/get_user_login_token_model.dart';
import 'package:mohem_flutter_app/models/itg_forms_models/request_detail_model.dart'; import 'package:mohem_flutter_app/models/itg_forms_models/request_detail_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/member_information_list_model.dart'; import 'package:mohem_flutter_app/models/member_information_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart'; import 'package:mohem_flutter_app/models/member_login_list_model.dart';
import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart'; import 'package:mohem_flutter_app/models/my_team/get_employee_subordinates_list.dart';
@ -93,58 +92,11 @@ class AppState {
final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 10.1, mobileType: Platform.isAndroid ? "android" : "ios"); final PostParamsModel _postParamsInitConfig = PostParamsModel(channel: 31, versionID: 10.1, mobileType: Platform.isAndroid ? "android" : "ios");
PostParamsModel get postParamsInitConfig {
if (_postParamsInitConfig.pLegislationCode == null) {
if (_postParams?.pLegislationCode != null) {
_postParamsInitConfig.pLegislationCode = _postParams?.pLegislationCode;
} else if (_memberLoginList?.pLEGISLATIONCODE != null) {
_postParamsInitConfig.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE;
}
}
return _postParamsInitConfig;
}
GetWalaplusDepInfoModel? _walaplusDepInfoModel;
GetWalaplusDepInfoModel? get walaplusDepInfoModel => _walaplusDepInfoModel;
set setWalaplusDepInfoModel(GetWalaplusDepInfoModel? model) => _walaplusDepInfoModel = model;
String? _dependentName;
String? get dependentName => _dependentName ?? _walaplusDepInfoModel?.pDependentName;
set setDependentName(String? name) => _dependentName = name;
int? _dependentLoginType;
int? get dependentLoginType => _dependentLoginType;
set setDependentLoginType(int? type) => _dependentLoginType = type;
String? _dependentDob;
String? get dependentDob => _dependentDob;
set setDependentDob(String? dob) => _dependentDob = dob;
String? _authenticationTokenID;
String? get authenticationTokenID => _authenticationTokenID;
set setAuthenticationTokenID(String? token) => _authenticationTokenID = token;
void setPostParamsInitConfig() { void setPostParamsInitConfig() {
isAuthenticated = false; isAuthenticated = false;
isLogged = false; isLogged = false;
_memberLoginList = null; _memberLoginList = null;
_memberInformationList = null; _memberInformationList = null;
_walaplusDepInfoModel = null;
_dependentName = null;
_dependentLoginType = null;
_dependentDob = null;
_authenticationTokenID = null;
_postParamsInitConfig.pLegislationCode = null;
_postParams = _postParamsInitConfig; _postParams = _postParamsInitConfig;
} }
@ -158,23 +110,9 @@ class AppState {
this._postParams = _postParams; this._postParams = _postParams;
} }
bool isArabic(context) { bool isArabic(context) => EasyLocalization.of(context)?.locale.languageCode == "ar";
try {
if (context != null) {
return EasyLocalization.of(context)?.locale.languageCode == "ar";
}
} catch (_) {}
return false;
}
int getLanguageID(context) { int getLanguageID(context) => EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2;
try {
if (context != null) {
return EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2;
}
} catch (_) {}
return 2;
}
String? _username; String? _username;
@ -191,13 +129,7 @@ class AppState {
MemberLoginListModel? get memberLoginList => _memberLoginList; MemberLoginListModel? get memberLoginList => _memberLoginList;
set setMemberLoginListModel(MemberLoginListModel? _memberLoginList) { set setMemberLoginListModel(MemberLoginListModel? _memberLoginList) => this._memberLoginList = _memberLoginList;
this._memberLoginList = _memberLoginList;
if (_memberLoginList?.pLEGISLATIONCODE != null) {
_postParamsInitConfig.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE?.trim().toUpperCase();
_postParams?.pLegislationCode = _memberLoginList?.pLEGISLATIONCODE?.trim().toUpperCase();
}
}
MemberInformationListModel? _memberInformationList; MemberInformationListModel? _memberInformationList;

@ -67,4 +67,5 @@ class MyColors {
static const Color lightGreyIconColor = Color(0xff919191); static const Color lightGreyIconColor = Color(0xff919191);
static const Color selectedBorderColor = Color(0xff37A4BE); static const Color selectedBorderColor = Color(0xff37A4BE);
static const Color mazayaRedColor = Color(0xffED1C2B); static const Color mazayaRedColor = Color(0xffED1C2B);
static const Color hintTextColor = Color(0xff9AA4B2);
} }

@ -1,10 +1,10 @@
import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart';
class ApiConsts { class ApiConsts {
// static String baseUrl = "http://10.201.214.101:2027"; // Local server // static String baseUrl = "http://10.200.204.11"; // Local server
// static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server // static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server
static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.201.204.101:2024"; // static String baseUrl = "http://10.201.204.101:2024";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd // static String baseUrl = "https://webservices.hmg.com"; // PreProd
// static String baseUrl = "https://hmgwebservices.com"; // Live server // static String baseUrl = "https://hmgwebservices.com"; // Live server
@ -12,13 +12,13 @@ class ApiConsts {
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server // static String baseUrl = "https://mohemm.hmg.com"; // New Live server
static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server static String ssoBaseUrl = "https://sso.hmg.com"; // New Live server
// //
// static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver
// static String baseUrl = "http://10.201.214.101:2027/";
// static String baseUrl = "http://10.20.200.111:1010/"; // static String baseUrl = "http://10.20.200.111:1010/";
// static String baseUrl = "https://mohemm.hmg.com"; // New Live server // static String baseUrl = "https://mohemm.hmg.com"; // New Live server
//static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT //static String eLearningBaseUrl = "https://elearning.hmg.com/moodle_dev/api/"; // Elearning server UAT
static String eLearningBaseUrl = "https://elearning.hmg.com/moodle/api/"; // Elearning server live static String eLearningBaseUrl = "https://elearning.hmg.com/moodle/api/"; // Elearning server live
static String enSATLiveURL = "https://ensat.hmg.com/auth/autologin"; // ENSAT server live static String enSATLiveURL = "https://ensat.hmg.com/auth/autologin"; // ENSAT server live
// static String baseUrl = "http://10.20.200.111:1010/"; // static String baseUrl = "http://10.20.200.111:1010/";
// static String baseUrl = "https://webservices.hmg.com"; // PreProd // static String baseUrl = "https://webservices.hmg.com"; // PreProd
@ -34,19 +34,7 @@ class ApiConsts {
static String user = baseUrlServices + "api/User/"; static String user = baseUrlServices + "api/User/";
static String cocRest = baseUrlServices + "COCWS.svc/REST/"; static String cocRest = baseUrlServices + "COCWS.svc/REST/";
// Dependent Login Endpoints static String ssoAuthRedirection = "https://sso-uat.hmg.com/api/auth/connect";
static String memberLoginDependent = erpRest + "MemberLogin_Dependent";
static String mohemmSendOTPDependent = erpRest + "Mohemm_SendOTP_Dependent";
static String mohemmCheckOTPDependent = erpRest + "Mohemm_CheckOTP_Dependent";
// WalaPlus Endpoints
static String walaPlusCategories = erpRest + "WalaPlus_Categories";
static String walaPlusRetailers = erpRest + "WalaPlus_Retailers";
static String walaPlusRetailersByID = erpRest + "WalaPlus_RetailersByID";
static String walaPlusOfferRedeem = erpRest + "WalaPlus_OfferRedeem";
static String processContactMazayaAccess = erpRest + "PROCESS_CONTACT_MAZAYA_ACCESS";
static String ssoAuthRedirection = ssoBaseUrl + "/api/auth/connect";
//Chat //Chat
static String chatServerBaseUrl = "https://apiderichat.hmg.com/"; static String chatServerBaseUrl = "https://apiderichat.hmg.com/";
@ -74,7 +62,6 @@ class ApiConsts {
static String marathonBaseUrlUAT = "https://marathoon.com/uatservice/api/"; static String marathonBaseUrlUAT = "https://marathoon.com/uatservice/api/";
static String marathonBaseUrl = marathonBaseUrlLive; static String marathonBaseUrl = marathonBaseUrlLive;
// static String marathonBaseUrl = marathonBaseUrlUAT; // static String marathonBaseUrl = marathonBaseUrlUAT;
static String marathonBaseUrlServices = "https://marathoon.com/service/"; static String marathonBaseUrlServices = "https://marathoon.com/service/";
static String marathonParticipantLoginUrl = marathonBaseUrl + "auth/participantlogin"; static String marathonParticipantLoginUrl = marathonBaseUrl + "auth/participantlogin";
@ -96,36 +83,6 @@ class ApiConsts {
static String notificationCenterAppGUID = "VDZreDY5TTNmTDVudzc4aDkwU3ZqcW9OcmI0"; static String notificationCenterAppGUID = "VDZreDY5TTNmTDVudzc4aDkwU3ZqcW9OcmI0";
static String notificationCenterLoginServiceGUID = "D8BD5C76-4B50-4122-81D0-C3AA25DB08A2"; static String notificationCenterLoginServiceGUID = "D8BD5C76-4B50-4122-81D0-C3AA25DB08A2";
static String notificationCenterForgotPasswordServiceGUID = "5CCCC2C9-EEA0-4207-9BE9-EE1FBBFFE8D0"; static String notificationCenterForgotPasswordServiceGUID = "5CCCC2C9-EEA0-4207-9BE9-EE1FBBFFE8D0";
static MemberType selectedMemberType = MemberType.employee;
static bool get ISDependetLogin => selectedMemberType == MemberType.dependent;
static set ISDependetLogin(bool value) {
selectedMemberType = value ? MemberType.dependent : MemberType.employee;
}
static bool get isDependetLogin => ISDependetLogin;
static set isDependetLogin(bool value) => ISDependetLogin = value;
}
enum MemberType {
employee(1),
dependent(2);
final int value;
const MemberType(this.value);
bool get isEmployee => this == MemberType.employee;
bool get isDependent => this == MemberType.dependent;
static MemberType fromValue(int? value) {
if (value == 2) return MemberType.dependent;
return MemberType.employee;
}
} }
class SharedPrefsConsts { class SharedPrefsConsts {

@ -22,7 +22,7 @@ class DateUtil {
return DateTime.now(); return DateTime.now();
} }
static DateTime convertStringToDate(String date) { static DateTime convertStringToDate(String? date) {
// /Date(1585774800000+0300)/ // /Date(1585774800000+0300)/
if (date != null) { if (date != null) {
const start = "/Date("; const start = "/Date(";
@ -34,9 +34,10 @@ class DateUtil {
date.substring(startIndex + start.length, endIndex), date.substring(startIndex + start.length, endIndex),
), ),
); );
} else } else {
return DateTime.now(); return DateTime.now();
} }
}
static DateTime convertSimpleStringDateToDate(String date, {bool isITG = false}) { static DateTime convertSimpleStringDateToDate(String date, {bool isITG = false}) {
// print(date.toUpperCase()); // print(date.toUpperCase());

@ -1,7 +1,6 @@
import 'dart:developer'; import 'dart:developer';
import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
class MyInAppBrowser extends InAppBrowser { class MyInAppBrowser extends InAppBrowser {
final Function onExitCallback; final Function onExitCallback;
@ -31,21 +30,4 @@ class MyInAppBrowser extends InAppBrowser {
void onLoadError(Uri? url, int code, String message) { void onLoadError(Uri? url, int code, String message) {
log("Can't load $url.. Error: $message"); log("Can't load $url.. Error: $message");
} }
@override
Future<PermissionResponse?> onPermissionRequest(PermissionRequest permissionRequest) async {
log("onPermissionRequest: ${permissionRequest.resources}");
try {
var status = await Permission.camera.status;
if (!status.isGranted) {
await Permission.camera.request();
}
} catch (e) {
log("Error checking/requesting camera permission: $e");
}
return PermissionResponse(
resources: permissionRequest.resources,
action: PermissionResponseAction.GRANT,
);
}
} }

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
@ -12,6 +13,7 @@ import 'package:mohem_flutter_app/classes/date_uitl.dart';
import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/models/get_notifications_response_model.dart'; import 'package:mohem_flutter_app/models/get_notifications_response_model.dart';
import 'package:mohem_flutter_app/ui/notifications/notification_details_page.dart'; import 'package:mohem_flutter_app/ui/notifications/notification_details_page.dart';
import 'package:permission_handler/permission_handler.dart';
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
@ -27,14 +29,20 @@ class AppNotifications {
String _huaweiToken = ''; String _huaweiToken = '';
late BuildContext context; late BuildContext context;
bool _isInitialized = false;
Future<void> requestPermissions() async { Future<void> requestPermissions() async {
if (Platform.isIOS) { if (Platform.isIOS) {
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()?.requestPermissions(alert: true, badge: true, sound: true); await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()?.requestPermissions(alert: true, badge: true, sound: true);
} else if (Platform.isAndroid) { } else if (Platform.isAndroid) {
AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>(); AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
await androidImplementation?.requestNotificationsPermission(); bool? granted = await androidImplementation?.requestNotificationsPermission();
if (granted == false) {
if (kDebugMode) {
print("-------------------- Permission Granted ------------------------");
print(granted);
}
await Permission.notification.request();
}
} }
} }
@ -45,11 +53,13 @@ class AppNotifications {
this.context = context; this.context = context;
if (!_isInitialized) {
_isInitialized = true;
await requestPermissions(); await requestPermissions();
}
AppState().setDeviceToken = firebaseToken; AppState().setDeviceToken = firebaseToken;
await Permission.notification.isDenied.then((bool value) {
if (value) {
Permission.notification.request();
}
});
RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage();
if (initialMessage != null) _handleMessage(initialMessage); if (initialMessage != null) _handleMessage(initialMessage);
@ -70,7 +80,7 @@ class AppNotifications {
// await hmsApiAvailability.isHMSAvailable().then((value) async { // await hmsApiAvailability.isHMSAvailable().then((value) async {
if (!(await Utils.isGoogleServicesAvailable())) { if (!(await Utils.isGoogleServicesAvailable())) {
huawei_push.Push.enableLogger(); huawei_push.Push.enableLogger();
await huawei_push.Push.setAutoInitEnabled(true); var result = await huawei_push.Push.setAutoInitEnabled(true);
huawei_push.Push.onNotificationOpenedApp.listen((message) { huawei_push.Push.onNotificationOpenedApp.listen((message) {
// newMessage(toFirebaseRemoteMessage(message)); // newMessage(toFirebaseRemoteMessage(message));

@ -20,9 +20,10 @@ import 'package:mohem_flutter_app/provider/chat_provider_model.dart';
import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart'; import 'package:mohem_flutter_app/widgets/dialogs/confirm_dialog.dart';
import 'package:mohem_flutter_app/widgets/loading_dialog.dart'; import 'package:mohem_flutter_app/widgets/loading_dialog.dart';
import 'package:nfc_manager/nfc_manager.dart'; import 'package:nfc_manager/nfc_manager.dart';
import 'package:nfc_manager/platform_tags.dart'; import 'package:nfc_manager/nfc_manager_android.dart';
import 'package:nfc_manager/nfc_manager_ios.dart';
// import 'package:nfc_manager/platform_tags.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
// ignore_for_file: avoid_annotating_with_dynamic // ignore_for_file: avoid_annotating_with_dynamic
@ -95,61 +96,9 @@ class Utils {
return await prefs.setString(key, value); return await prefs.setString(key, value);
} }
static bool isMazayaAccessRevoked(dynamic error) {
if (error == null) return false;
String str = "";
if (error is APIException && error.error != null) {
String errCode = error.error?.errorCode?.toString() ?? "";
String errMsg = error.error?.errorMessage?.toString() ?? "";
str = "$errCode $errMsg ${error.message}";
if (errCode.trim().toUpperCase() == "E4") {
return true;
}
} else {
str = error.toString();
}
String lower = str.toLowerCase();
if (lower.contains("no active grant") ||
lower.contains("grant request") ||
lower.contains("grant was found") ||
lower.contains("grant_request") ||
lower.contains("e4:") ||
lower.contains("e4 -") ||
lower.contains("e4 :")) {
return true;
}
if (str.contains("طلب منح") ||
str.contains("منح نشط") ||
str.contains("لا يوجد طلب منح") ||
str.contains("تم إلغاء وصول مزايا") ||
str.contains("إلغاء مزايا")) {
return true;
}
return false;
}
static void handleException(dynamic exception, cxt, Function(String)? onErrorMessage) { static void handleException(dynamic exception, cxt, Function(String)? onErrorMessage) {
if (isMazayaAccessRevoked(exception)) {
String depName = AppState().dependentName ?? "";
if (depName.isEmpty) {
depName = AppState().walaplusDepInfoModel?.pDependentName ?? "";
}
if (cxt != null) {
Navigator.push(
cxt,
MaterialPageRoute(
builder: (context) => MazayaAccessResultScreen(
isGranted: false,
dependentName: depName,
),
),
);
return;
}
}
String errorMessage; String errorMessage;
if (exception is APIException && exception.error?.errorType == 4) { if (exception.error.errorType != null && exception.error.errorType == 4) {
Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.appUpdateScreen, (_) => false, arguments: exception.error?.errorMessage); Navigator.pushNamedAndRemoveUntil(cxt, AppRoutes.appUpdateScreen, (_) => false, arguments: exception.error?.errorMessage);
} else { } else {
if (exception is APIException) { if (exception is APIException) {
@ -402,15 +351,34 @@ class Utils {
NfcManager.instance NfcManager.instance
.startSession( .startSession(
onDiscovered: (NfcTag tag) async { onDiscovered: (NfcTag tag) async {
MifareUltralight f; // MifareUltralight f;
// if (Platform.isAndroid) {
// print(tag);
// f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
// } else {
// f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
// }
Uint8List? id;
if (Platform.isAndroid) { if (Platform.isAndroid) {
print(tag); id = NfcTagAndroid.from(tag)?.id;
f = MifareUltralight(tag: tag, identifier: tag.data["nfca"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22); } else if (Platform.isIOS) {
} else { id = MiFareIos.from(tag)?.identifier;
f = MifareUltralight(tag: tag, identifier: tag.data["mifare"]["identifier"], type: 2, maxTransceiveLength: 252, timeout: 22);
} }
String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
NfcManager.instance.stopSession(); if (id == null) {
await NfcManager.instance.stopSession();
return;
}
String identifier =
id.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
print(identifier);
await NfcManager.instance.stopSession();
// String identifier = f.identifier.map((e) => e.toRadixString(16).padLeft(2, '0')).join('');
// NfcManager.instance.stopSession();
onRead(identifier); onRead(identifier);
}, },
pollingOptions: {NfcPollingOption.iso14443}, pollingOptions: {NfcPollingOption.iso14443},
@ -446,11 +414,7 @@ class Utils {
AppState().isLogged = false; AppState().isLogged = false;
AppState().setPostParamsInitConfig(); AppState().setPostParamsInitConfig();
if (chatData != null) { if (chatData != null) {
try {
chatData.disposeData(); chatData.disposeData();
} catch (e) {
}
} }
// SharedPreferences prefs = await SharedPreferences.getInstance(); // SharedPreferences prefs = await SharedPreferences.getInstance();
// await prefs.clear(); // await prefs.clear();

@ -13,9 +13,8 @@ import 'package:mohem_flutter_app/ui/chat/manage_group.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_create_request.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_ovr_home.dart';
import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart'; import 'package:mohem_flutter_app/ui/etqan_ovr/etqan_request_detailed.dart';
import 'package:mohem_flutter_app/ui/chatbot/chatbot_screen.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart'; import 'package:mohem_flutter_app/ui/landing/dashboard_screen.dart';
import 'package:mohem_flutter_app/ui/landing/dashboard_screen_for_dependet.dart';
import 'package:mohem_flutter_app/ui/landing/wala_plus_retailer_details_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/change_itg_ad_password_screen.dart';
import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart'; import 'package:mohem_flutter_app/ui/landing/itg/its_add_screen_video_image.dart';
import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart'; import 'package:mohem_flutter_app/ui/landing/itg/survey_screen.dart';
@ -46,11 +45,9 @@ import 'package:mohem_flutter_app/ui/my_team/subordinate_leave.dart';
import 'package:mohem_flutter_app/ui/my_team/team_members.dart'; import 'package:mohem_flutter_app/ui/my_team/team_members.dart';
import 'package:mohem_flutter_app/ui/my_team/view_attendance.dart'; import 'package:mohem_flutter_app/ui/my_team/view_attendance.dart';
import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart'; import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart';
import 'package:mohem_flutter_app/ui/parking_qr/parking_qr.dart';
import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart'; import 'package:mohem_flutter_app/ui/payslip/monthly_pay_slip_screen.dart';
import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart'; import 'package:mohem_flutter_app/ui/performance/performance_appraisal_screen.dart';
import 'package:mohem_flutter_app/ui/profile/add_dependent_verify_screen.dart';
import 'package:mohem_flutter_app/ui/profile/dependent_details_screen.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart'; import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart';
import 'package:mohem_flutter_app/ui/profile/basic_details.dart'; import 'package:mohem_flutter_app/ui/profile/basic_details.dart';
import 'package:mohem_flutter_app/ui/profile/contact_details.dart'; import 'package:mohem_flutter_app/ui/profile/contact_details.dart';
@ -64,6 +61,7 @@ import 'package:mohem_flutter_app/ui/screens/announcements/announcement_details.
import 'package:mohem_flutter_app/ui/screens/announcements/announcements.dart'; import 'package:mohem_flutter_app/ui/screens/announcements/announcements.dart';
import 'package:mohem_flutter_app/ui/screens/child_education/child_education_assistance.dart'; import 'package:mohem_flutter_app/ui/screens/child_education/child_education_assistance.dart';
import 'package:mohem_flutter_app/ui/screens/courses/courses_screen.dart'; import 'package:mohem_flutter_app/ui/screens/courses/courses_screen.dart';
// import 'package:mohem_flutter_app/ui/my_attendance/work_from_home_screen.dart'; // import 'package:mohem_flutter_app/ui/my_attendance/work_from_home_screen.dart';
import 'package:mohem_flutter_app/ui/screens/eit/add_eit.dart'; import 'package:mohem_flutter_app/ui/screens/eit/add_eit.dart';
import 'package:mohem_flutter_app/ui/screens/event_activity/event_activity.dart'; import 'package:mohem_flutter_app/ui/screens/event_activity/event_activity.dart';
@ -88,7 +86,6 @@ import 'package:mohem_flutter_app/ui/work_list/itg_detail_screen.dart';
import 'package:mohem_flutter_app/ui/work_list/work_list_screen.dart'; import 'package:mohem_flutter_app/ui/work_list/work_list_screen.dart';
import 'package:mohem_flutter_app/ui/work_list/worklist_detail_screen.dart'; import 'package:mohem_flutter_app/ui/work_list/worklist_detail_screen.dart';
import 'package:mohem_flutter_app/ui/work_list/worklist_settings.dart'; import 'package:mohem_flutter_app/ui/work_list/worklist_settings.dart';
import 'package:mohem_flutter_app/widgets/app_in_app_webview_screen.dart';
class AppRoutes { class AppRoutes {
static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); static GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
@ -103,8 +100,6 @@ class AppRoutes {
static const String newPassword = "/newPassword"; static const String newPassword = "/newPassword";
static const String loginVerification = "/loginVerification"; static const String loginVerification = "/loginVerification";
static const String dashboard = "/dashboard"; static const String dashboard = "/dashboard";
static const String dashboardScreenForDependet = "/dashboardScreenForDependet";
static const String walaPlusRetailerDetails = "/walaPlusRetailerDetails";
static const String todayAttendance = "/todayAttendance"; static const String todayAttendance = "/todayAttendance";
static const String subMenuScreen = "/submenuScreen"; static const String subMenuScreen = "/submenuScreen";
static const String addEitScreen = "/addeitScreen"; static const String addEitScreen = "/addeitScreen";
@ -150,9 +145,6 @@ class AppRoutes {
static const String familyMembers = "/familyMembers"; static const String familyMembers = "/familyMembers";
static const String deleteFamilyMember = "/deleteFamilyMember"; static const String deleteFamilyMember = "/deleteFamilyMember";
static const String addUpdateFamilyMember = "/addUpdateFamilyMember"; static const String addUpdateFamilyMember = "/addUpdateFamilyMember";
static const String addDependentVerify = "/addDependentVerify";
static const String dependentDetails = "/dependentDetails";
static const String mazayaAccessResult = "/mazayaAccessResult";
// Mowadhafhi // Mowadhafhi
static const String mowadhafhi = "/mowadhafhi"; static const String mowadhafhi = "/mowadhafhi";
@ -210,6 +202,7 @@ class AppRoutes {
static const String chat = "/chat"; static const String chat = "/chat";
static const String chatDetailed = "/chatDetailed"; static const String chatDetailed = "/chatDetailed";
static const String chatFavoriteUsers = "/chatFavoriteUsers"; static const String chatFavoriteUsers = "/chatFavoriteUsers";
static const String chatBotHome = "/chatBotHome";
//Group Chat //Group Chat
static const String manageGroup = "/manageGroup"; static const String manageGroup = "/manageGroup";
@ -236,25 +229,12 @@ class AppRoutes {
//Parking QR //Parking QR
static const String parkingQr = "/parkingQrScreen"; static const String parkingQr = "/parkingQrScreen";
static const String appInAppWebView = "/appInAppWebView";
static final Map<String, WidgetBuilder> routes = { static final Map<String, WidgetBuilder> routes = {
login: (BuildContext context) => LoginScreen(), login: (BuildContext context) => LoginScreen(),
verifyLogin: (BuildContext context) => VerifyLoginScreen(), verifyLogin: (BuildContext context) => VerifyLoginScreen(),
verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(), verifyLastLogin: (BuildContext context) => VerifyLastLoginScreen(),
dashboard: (BuildContext context) => DashboardScreen(), dashboard: (BuildContext context) => DashboardScreen(),
dashboardScreenForDependet: (BuildContext context) => const DashboardScreenForDependet(),
walaPlusRetailerDetails: (BuildContext context) => const WalaPlusRetailerDetailsScreen(),
appInAppWebView: (BuildContext context) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is Map<String, dynamic>) {
return AppInAppWebViewScreen(
url: args['url'] ?? "",
title: args['title'],
);
}
return const Scaffold();
},
survey: (BuildContext context) => SurveyScreen(), survey: (BuildContext context) => SurveyScreen(),
advertisement: (BuildContext context) => ITGAdsScreen(), advertisement: (BuildContext context) => ITGAdsScreen(),
@ -262,8 +242,8 @@ class AppRoutes {
newPassword: (BuildContext context) => NewPasswordScreen(), newPassword: (BuildContext context) => NewPasswordScreen(),
forgotPassword: (BuildContext context) => ForgotPasswordScreen(), forgotPassword: (BuildContext context) => ForgotPasswordScreen(),
todayAttendance: (BuildContext context) => TodayAttendanceScreen2(), todayAttendance: (BuildContext context) => TodayAttendanceScreen2(),
//eit
//eit
addEitScreen: (BuildContext context) => AddEITScreen(), addEitScreen: (BuildContext context) => AddEITScreen(),
//Work List //Work List
@ -274,7 +254,6 @@ class AppRoutes {
worklistSettings: (BuildContext context) => WorklistSettings(), worklistSettings: (BuildContext context) => WorklistSettings(),
// Leave Balance // Leave Balance
leaveBalance: (BuildContext context) => LeaveBalance(), leaveBalance: (BuildContext context) => LeaveBalance(),
addLeaveBalance: (BuildContext context) => AddLeaveBalanceScreen(), addLeaveBalance: (BuildContext context) => AddLeaveBalanceScreen(),
@ -305,9 +284,6 @@ class AppRoutes {
deleteFamilyMember: (BuildContext context) => DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int), deleteFamilyMember: (BuildContext context) => DeleteFamilyMember(ModalRoute.of(context)!.settings.arguments as int),
requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(), requestSubmitScreen: (BuildContext context) => RequestSubmitScreen(),
addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(), addUpdateFamilyMember: (BuildContext context) => AddUpdateFamilyMember(),
addDependentVerify: (BuildContext context) => const AddDependentVerifyScreen(),
dependentDetails: (BuildContext context) => const DependentDetailsScreen(),
mazayaAccessResult: (BuildContext context) => const MazayaAccessResultScreen(),
//mowadhafhi //mowadhafhi
mowadhafhi: (BuildContext context) => MowadhafhiHome(), mowadhafhi: (BuildContext context) => MowadhafhiHome(),
@ -357,6 +333,7 @@ class AppRoutes {
chat: (BuildContext context) => ChatHome(), chat: (BuildContext context) => ChatHome(),
chatDetailed: (BuildContext context) => ChatDetailScreen(), chatDetailed: (BuildContext context) => ChatDetailScreen(),
chatFavoriteUsers: (BuildContext context) => ChatFavoriteUsersScreen(), chatFavoriteUsers: (BuildContext context) => ChatFavoriteUsersScreen(),
chatBotHome: (BuildContext context) => ChatBotScreen(),
//Group Chat //Group Chat
manageGroup: (BuildContext context) => ManageGroupScreen(), manageGroup: (BuildContext context) => ManageGroupScreen(),
@ -379,9 +356,11 @@ class AppRoutes {
etqanOvr: (BuildContext context) => EtqanOvrHome(), etqanOvr: (BuildContext context) => EtqanOvrHome(),
etqanCreateRequest: (BuildContext context) => EtqanOvrCreateRequest(), etqanCreateRequest: (BuildContext context) => EtqanOvrCreateRequest(),
etqanGetIncidientRequest: (BuildContext context) => EtqanOvrRequestDetailed(), etqanGetIncidientRequest: (BuildContext context) => EtqanOvrRequestDetailed(),
activityScreen:(BuildContext context) => const EventActivityScreen(),
//Parking QR //Parking QR
parkingQr: (BuildContext context) => const ParkingQrScreen(), parkingQr: (BuildContext context) => const ParkingQrScreen(),
activityScreen: (BuildContext context) => const EventActivityScreen(),
}; };
} }

@ -1,17 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:easy_localization/src/public_ext.dart'; import 'package:easy_localization/src/public_ext.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:mohem_flutter_app/widgets/otp_widget.dart'; import 'package:mohem_flutter_app/widgets/otp_widget.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
final ValueNotifier<String> otpFieldClear = ValueNotifier<String>(""); final ValueNotifier<String> otpFieldClear = ValueNotifier<String>("");
@ -27,6 +24,10 @@ class OtpDialog {
Future<Null>? timer; Future<Null>? timer;
static BuildContext? _context;
static bool? _loading;
OtpDialog(this.context, this.type, this.mobileNo, this.onSuccess, this.onFailure, {required this.onResendCode}); OtpDialog(this.context, this.type, this.mobileNo, this.onSuccess, this.onFailure, {required this.onResendCode});
GlobalKey? verifyAccountForm = GlobalKey<FormState>(); GlobalKey? verifyAccountForm = GlobalKey<FormState>();
@ -38,7 +39,12 @@ class OtpDialog {
TextEditingController digit3 = TextEditingController(text: ""); TextEditingController digit3 = TextEditingController(text: "");
TextEditingController digit4 = TextEditingController(text: ""); TextEditingController digit4 = TextEditingController(text: "");
Map verifyAccountFormValue = {'digit1': '', 'digit2': '', 'digit3': '', 'digit4': ''}; Map verifyAccountFormValue = {
'digit1': '',
'digit2': '',
'digit3': '',
'digit4': '',
};
final focusD1 = FocusNode(); final focusD1 = FocusNode();
final focusD2 = FocusNode(); final focusD2 = FocusNode();
final focusD3 = FocusNode(); final focusD3 = FocusNode();
@ -47,16 +53,14 @@ class OtpDialog {
// ProjectViewModel projectProvider; // ProjectViewModel projectProvider;
String displayTime = ''; String displayTime = '';
String? _code;
dynamic setState; dynamic setState;
bool stopTimer = false; bool stopTimer = false;
bool isCodeExpired = false;
// static String signature; // static String signature;
void displayDialog(BuildContext context) async { void displayDialog(BuildContext context) async {
otpFieldClear.value = ""; return showDialog(
_startListeningForSms();
showDialog(
context: context, context: context,
barrierColor: Colors.black.withOpacity(0.63), barrierColor: Colors.black.withOpacity(0.63),
builder: (context) { builder: (context) {
@ -66,8 +70,7 @@ class OtpDialog {
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
insetPadding: const EdgeInsets.only(left: 21, right: 21), insetPadding: const EdgeInsets.only(left: 21, right: 21),
child: StatefulBuilder( child: StatefulBuilder(builder: (context, setState) {
builder: (context, setState) {
if (displayTime == '') { if (displayTime == '') {
startTimer(setState); startTimer(setState);
} }
@ -93,20 +96,7 @@ class OtpDialog {
LocaleKeys.otp.tr().toText14(), LocaleKeys.otp.tr().toText14(),
LocaleKeys.verification.tr().toText24(isBold: true), LocaleKeys.verification.tr().toText24(isBold: true),
6.height, 6.height,
Builder( (LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + ' xxxxxxxx' + mobileNo.toString().substring(mobileNo.toString().length - 3)).toText16(),
builder: (context) {
String maskedNumber = "";
if (mobileNo != null) {
String numStr = mobileNo.toString();
if (numStr.length >= 3) {
maskedNumber = ' xxxxxxxx' + numStr.substring(numStr.length - 3);
} else {
maskedNumber = ' ' + numStr;
}
}
return (LocaleKeys.pleaseEnterTheVerificationCodeSentTo.tr() + maskedNumber).toText16();
},
),
18.height, 18.height,
ValueListenableBuilder<String>( ValueListenableBuilder<String>(
builder: (BuildContext context, String value, Widget? child) { builder: (BuildContext context, String value, Widget? child) {
@ -138,12 +128,21 @@ class OtpDialog {
), ),
), ),
10.height, 10.height,
isCodeExpired stopTimer
? Row( ? Row(
children: [ children: [
Expanded(child: LocaleKeys.codeExpire.tr().toText16(color: MyColors.redColor)), Expanded(
child: LocaleKeys.codeExpire.tr().toText16(
color: MyColors.redColor,
),
),
12.width, 12.width,
Image.asset("assets/icons/ic_alarm.png", width: 20, height: 20, color: MyColors.redColor), Image.asset(
"assets/icons/ic_alarm.png",
width: 20,
height: 20,
color: MyColors.redColor,
),
], ],
) )
: RichText( : RichText(
@ -151,7 +150,10 @@ class OtpDialog {
text: LocaleKeys.theVerificationCodeWillExpireIn.tr() + '\n', text: LocaleKeys.theVerificationCodeWillExpireIn.tr() + '\n',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: -0.48), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: -0.48),
children: <TextSpan>[ children: <TextSpan>[
TextSpan(text: displayTime, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.textMixColor, letterSpacing: -0.48)), TextSpan(
text: displayTime,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: MyColors.textMixColor, letterSpacing: -0.48),
),
], ],
), ),
), ),
@ -161,40 +163,57 @@ class OtpDialog {
valueListenable: otpFieldClear, valueListenable: otpFieldClear,
), ),
18.height, 18.height,
DefaultButton(isCodeExpired ? LocaleKeys.resend.tr() : LocaleKeys.cancel.tr(), () async { DefaultButton(
if (isCodeExpired) { stopTimer ? LocaleKeys.resend.tr() : LocaleKeys.cancel.tr(),
() async {
if (stopTimer) {
hideSMSBox(context); hideSMSBox(context);
onResendCode(); onResendCode();
} else { } else {
stopTimer = true; stopTimer = true;
// onFailure();
hideSMSBox(context); hideSMSBox(context);
} }
}, colors: isCodeExpired ? null : [MyColors.pinkDarkColor, MyColors.pinkDarkColor]), },
colors: stopTimer
? null
: [
MyColors.pinkDarkColor,
MyColors.pinkDarkColor,
], ],
), ),
); ],
},
), ),
); );
}, }),
).then((_) { );
stopTimer = true;
_stopListeningForSms();
}); });
} }
InputDecoration buildInputDecoration(BuildContext context) { InputDecoration buildInputDecoration(BuildContext context) {
return InputDecoration( return InputDecoration(
counterText: " ", counterText: " ",
enabledBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(10)), borderSide: BorderSide(color: Colors.black)), enabledBorder: const OutlineInputBorder(
focusedBorder: OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Theme.of(context).primaryColor)), borderRadius: BorderRadius.all(Radius.circular(10)),
errorBorder: OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Colors.red)), borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Theme.of(context).primaryColor),
),
errorBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.red),
),
// focusedErrorBorder: OutlineInputBorder( // focusedErrorBorder: OutlineInputBorder(
// borderRadius: const BorderRadius.all(Radius.circular(10.0)), // borderRadius: const BorderRadius.all(Radius.circular(10.0)),
// borderSide: BorderSide(color: Colors.red), // borderSide: BorderSide(color: Colors.red),
// // borderSide: BorderSide(color: Theme.of(context).colorScheme.error), // // borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
// ), // ),
focusedErrorBorder: OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(10.0)), borderSide: BorderSide(color: Theme.of(context).colorScheme.error)), focusedErrorBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Theme.of(context).colorScheme.error),
),
); );
} }
@ -221,120 +240,40 @@ class OtpDialog {
} }
void startTimer(setState) { void startTimer(setState) {
if (stopTimer) return;
remainingTime--; remainingTime--;
if (stopTimer) return; if (stopTimer) return;
try {
setState(() { setState(() {
displayTime = getSecondsAsDigitalClock(remainingTime); displayTime = getSecondsAsDigitalClock(remainingTime);
}); });
} catch (_) {}
timer = Future.delayed(const Duration(seconds: 1), () { timer = Future.delayed(const Duration(seconds: 1), () {
if (stopTimer) return;
if (remainingTime > 0) { if (remainingTime > 0) {
startTimer(setState); startTimer(setState);
} else { } else {
if (stopTimer) return;
try {
setState(() { setState(() {
isCodeExpired = true;
stopTimer = true; stopTimer = true;
}); });
} catch (_) {}
_stopListeningForSms();
} }
}); });
} }
void hideSMSBox(context) { void hideSMSBox(context) {
stopTimer = true;
_stopListeningForSms();
onFailure(); onFailure();
} }
void _onOtpCallBack(String otpCode, bool? isAutofill) { void _onOtpCallBack(String otpCode, bool? isAutofill) {
if (isCodeExpired) return;
if (otpCode.length == 4) { if (otpCode.length == 4) {
_stopListeningForSms(); // stopTimer = true;
otpFieldClear.value = otpCode; otpFieldClear.value = otpCode;
onSuccess(otpCode, _pinPutController); onSuccess(otpCode, _pinPutController);
} }
} }
void _startListeningForSms() { // static getSignature() async {
if (!Platform.isAndroid) return; // // if (Platform.isAndroid) {
try { // // return await SmsRetriever.getAppSignature();
SmsVerification.startListeningSms() // // } else {
.then((String? message) { // // return null;
if (stopTimer || isCodeExpired) return; // // }
if (message != null && message.isNotEmpty) { // }
debugPrint("SMS OTP Auto-Verify received message: $message");
String? otpCode = extractOtpCode(message);
if (otpCode != null && otpCode.length == 4) {
debugPrint("Auto-filling OTP: $otpCode");
_pinPutController.text = otpCode;
_pinPutController.selection = TextSelection.collapsed(offset: otpCode.length);
_onOtpCallBack(otpCode, true);
}
}
})
.catchError((e) {
debugPrint("Error in startListeningSms: $e");
});
} catch (e) {
debugPrint("Error starting SMS listening: $e");
}
}
void _stopListeningForSms() {
if (!Platform.isAndroid) return;
try {
SmsVerification.stopListening();
} catch (e) {
debugPrint("Error stopping SMS listening: $e");
}
}
static String? extractOtpCode(String? message) {
if (message == null || message.trim().isEmpty) return null;
String normalized = _normalizeDigits(message);
// 1. Try matching with OTP context keyword if available
RegExp contextRegex = RegExp(r'(?:code|otp|رمز|كود|تفعيل|تحقق)[^\d]{0,15}(\d{4})\b', caseSensitive: false);
Match? contextMatch = contextRegex.firstMatch(normalized);
if (contextMatch != null && contextMatch.group(1) != null) {
return contextMatch.group(1);
}
// 2. Try exact 4-digit word boundary match
Iterable<Match> matches = RegExp(r'\b\d{4}\b').allMatches(normalized);
if (matches.isNotEmpty) {
return matches.first.group(0);
}
// 3. Fallback
try {
Iterable<Match> allDigits = RegExp(r'\d{4}').allMatches(normalized);
if (allDigits.isNotEmpty) {
return allDigits.first.group(0);
}
} catch (_) {}
return null;
}
static String _normalizeDigits(String input) {
const String arabic = '٠١٢٣٤٥٦٧٨٩';
const String english = '0123456789';
String res = input;
for (int i = 0; i < arabic.length; i++) {
res = res.replaceAll(arabic[i], english[i]);
}
return res;
}
static Future<String?> getSignature() async {
return await LoginApiClient.getSignature();
}
} }

@ -68,11 +68,10 @@ extension EmailValidator on String {
style: TextStyle(fontSize: 10, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), style: TextStyle(fontSize: 10, fontStyle: fontStyle ?? FontStyle.normal, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4),
); );
Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, TextOverflow? overflow}) => Text( Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: (maxLine > 0) ? maxLine : null, maxLines: (maxLine > 0) ? maxLine : null,
overflow: overflow,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600),
@ -82,24 +81,21 @@ extension EmailValidator on String {
), ),
); );
Widget toText12({Color? color, FontWeight? weight, bool isUnderLine = false, Color? underlineColor, double underlineGap = 2, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) { Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) => Text(
Widget text = Text(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: maxLine == 0 ? null : maxLine, maxLines: maxLine == 0 ? null : maxLine,
style: TextStyle(fontSize: 12, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -0.72, overflow: TextOverflow.visible), style: TextStyle(
); fontSize: 12,
fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
if (!isUnderLine) return text; color: color ?? MyColors.darkTextColor,
letterSpacing: -0.72,
return Container( decoration: isUnderLine ? TextDecoration.underline : null,
padding: EdgeInsets.only(bottom: underlineGap), overflow: TextOverflow.visible
decoration: BoxDecoration(border: Border(bottom: BorderSide(color: underlineColor ?? color ?? MyColors.darkTextColor, width: 1))), ),
child: text,
); );
}
Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine, TextOverflow? overflow}) => AutoSizeText( Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int? maxLine,TextOverflow? overflow}) => AutoSizeText(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
maxLines: maxLine == 0 ? null : maxLine, maxLines: maxLine == 0 ? null : maxLine,
@ -114,7 +110,16 @@ extension EmailValidator on String {
), ),
); );
Widget toTextAuto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0, double fontSize = 12, double letterSpacing = -0.72, double height = 1}) => Widget toTextAuto({
Color? color,
bool isUnderLine = false,
bool isBold = false,
bool isCenter = false,
int maxLine = 0,
double fontSize = 12,
double letterSpacing = -0.72,
double height = 1,
}) =>
AutoSizeText( AutoSizeText(
this, this,
textAlign: isCenter ? TextAlign.center : null, textAlign: isCenter ? TextAlign.center : null,
@ -129,34 +134,24 @@ extension EmailValidator on String {
), ),
); );
Widget toText13({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int? maxlines, TextOverflow? overflow}) => Text( Widget toText13({Color? color, bool isUnderLine = false}) => Text(
this, this,
maxLines: maxlines, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.52, decoration: isUnderLine ? TextDecoration.underline : null),
overflow: overflow,
style: TextStyle(
fontSize: 13,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600),
color: color ?? MyColors.darkTextColor,
letterSpacing: -0.52,
decoration: isUnderLine ? TextDecoration.underline : null,
),
); );
Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines, TextAlign? textAlign, bool isCenter = false, TextOverflow? overflow}) => Text( Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? weight, int? maxlines, TextAlign? textAlign, bool isCenter = false}) => Text(
this, this,
textAlign: isCenter ? TextAlign.center : textAlign, textAlign: isCenter ? TextAlign.center : (textAlign ?? TextAlign.left),
maxLines: maxlines, maxLines: maxlines,
style: TextStyle( style: TextStyle(
overflow: overflow,
color: color ?? MyColors.darkTextColor, color: color ?? MyColors.darkTextColor,
fontSize: 14, fontSize: 14,
letterSpacing: -0.48, letterSpacing: -0.48,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600),
decoration: isUnderLine ? TextDecoration.underline : null, decoration: isUnderLine ? TextDecoration.underline : null),
),
); );
Widget toText16({Color? color, FontWeight? weight, bool isUnderLine = false, bool isBold = false, int? maxlines, double? height, bool isCentered = false}) => Text( Widget toText16({Color? color, bool isUnderLine = false, bool isBold = false, int? maxlines, double? height, bool isCentered = false}) => Text(
this, this,
maxLines: maxlines, maxLines: maxlines,
textAlign: isCentered ? TextAlign.center : null, textAlign: isCentered ? TextAlign.center : null,
@ -165,34 +160,39 @@ extension EmailValidator on String {
fontSize: 16, fontSize: 16,
letterSpacing: -0.64, letterSpacing: -0.64,
height: height, height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), fontWeight: isBold ? FontWeight.bold : FontWeight.w600,
decoration: isUnderLine ? TextDecoration.underline : null, decoration: isUnderLine ? TextDecoration.underline : null,
), ),
); );
Widget toText17({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text( Widget toText17({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this, this,
textAlign: isCentered ? TextAlign.center : null, textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)), style: TextStyle(color: color ?? MyColors.darkTextColor, fontSize: 17, letterSpacing: -0.68, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
); );
Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text( Widget toText18({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this, this,
textAlign: isCentered ? TextAlign.center : null, textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(fontSize: 18, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -1.08), style: TextStyle(fontSize: 18, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.08),
); );
Widget toText19({Color? color, bool isBold = false}) => Widget toText19({Color? color, bool isBold = false}) => Text(
Text(this, style: TextStyle(fontSize: 19, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.14)); this,
style: TextStyle(fontSize: 19, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -1.14),
);
Widget toText20({Color? color, FontWeight? weight, bool isBold = false, bool isCentered = false}) => Text( Widget toText20({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this, this,
textAlign: isCentered ? TextAlign.center : null, textAlign: isCentered ? TextAlign.center : null,
style: TextStyle(fontSize: 20, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600), color: color ?? MyColors.darkTextColor, letterSpacing: -0.4), style: TextStyle(fontSize: 20, fontWeight: isBold ? FontWeight.bold : FontWeight.w600, color: color ?? MyColors.darkTextColor, letterSpacing: -0.4),
); );
Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text(
Text(this, maxLines: maxlines, style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600))); this,
maxLines: maxlines,
style: TextStyle(color: color ?? MyColors.grey3AColor, fontSize: 21, letterSpacing: -0.84, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.w600)),
);
Widget toText22({Color? color, bool isBold = false, bool isCentered = false}) => Text( Widget toText22({Color? color, bool isBold = false, bool isCentered = false}) => Text(
this, this,
@ -224,8 +224,10 @@ extension EmailValidator on String {
style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600), style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 32, letterSpacing: -1.92, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
); );
Widget toText44({Color? color, bool isBold = false}) => Widget toText44({Color? color, bool isBold = false}) => Text(
Text(this, style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 44, letterSpacing: -2.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600)); this,
style: TextStyle(height: 32 / 32, color: color ?? MyColors.darkTextColor, fontSize: 44, letterSpacing: -2.64, fontWeight: isBold ? FontWeight.bold : FontWeight.w600),
);
Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) {
String upper = ""; String upper = "";
@ -249,7 +251,14 @@ extension EmailValidator on String {
lower = lowerHeading; lower = lowerHeading;
} }
return Column(crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [if (upper.isNotEmpty) upper.toText12(), lower.toText24(isBold: true)]); return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (upper.isNotEmpty) upper.toText12(),
lower.toText24(isBold: true),
],
);
} }
bool isValidEmail() { bool isValidEmail() {

@ -336,8 +336,6 @@ class CodegenLoader extends AssetLoader{
"createdFor": "انشاء لأجل", "createdFor": "انشاء لأجل",
"requestCreatedSuccessfully": "تم انشاء الطلب بنجاح", "requestCreatedSuccessfully": "تم انشاء الطلب بنجاح",
"search": "بحث", "search": "بحث",
"filterEnterPrDetailsHere": "ادخل تفاصيل طلب الشراء هنا",
"filterEnterPoDetailsHere": "ادخل تفاصيل أمر الشراء هنا",
"wantToReject": "هل انت متأكد تريد الرفض", "wantToReject": "هل انت متأكد تريد الرفض",
"requestType": "نوع الطلب", "requestType": "نوع الطلب",
"employeeDigitalID": "هويةالموظف الرقمية", "employeeDigitalID": "هويةالموظف الرقمية",
@ -397,7 +395,7 @@ class CodegenLoader extends AssetLoader{
"browseCategories": "تصفح الفئات", "browseCategories": "تصفح الفئات",
"searchItems": "عناصر البحث", "searchItems": "عناصر البحث",
"offerAndDiscounts": "العروض والخصومات", "offerAndDiscounts": "العروض والخصومات",
"offerValid": "العرض ساري", "offerValid": "العرض صالح",
"offerExpired": "انتهى العرض", "offerExpired": "انتهى العرض",
"whatAreYouOffering": "ما الذي تعرضه؟", "whatAreYouOffering": "ما الذي تعرضه؟",
"selectCategory": "اختر الفئة", "selectCategory": "اختر الفئة",
@ -618,15 +616,6 @@ class CodegenLoader extends AssetLoader{
"addUsers": "إضافة مستخدمين إلى المجموعة", "addUsers": "إضافة مستخدمين إلى المجموعة",
"admin": "مشرف", "admin": "مشرف",
"areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟", "areYouSureWantTodelete": "هل أنت متأكد أنك تريد الحذف؟",
"confirmGrantMazayaAccess": "هل أنت متأكد أنك تريد منح صلاحية مزايا لـ \"{}\"؟",
"confirmRevokeMazayaAccess": "هل أنت متأكد أنك تريد إلغاء صلاحية مزايا لـ \"{}\"؟",
"yes": "نعم",
"no": "لا",
"mazayaInviteInstructions": "يرجى تحميل تطبيق مهمم من الرابط أعلاه واستخدام بيانات الاعتماد التالية لتسجيل الدخول",
"cameraPermissionRequiredToScan": "يرجى منح صلاحية الكاميرا لمسح رمز الاستجابة السريعة",
"cameraPermissionPermanentlyDenied": "يرجى تفعيل صلاحية الكاميرا من إعدادات التطبيق > الصلاحيات",
"openSettings": "فتح الإعدادات",
"nationalIdOrIqamaFallback": "رقم الهوية الوطنية أو رقم الإقامة الخاص بك",
"Attendance": "الحضور", "Attendance": "الحضور",
"audioCall": "مكالمة صوتية", "audioCall": "مكالمة صوتية",
"editGroups": "تعديل المجموعة", "editGroups": "تعديل المجموعة",
@ -680,7 +669,6 @@ class CodegenLoader extends AssetLoader{
"noCoursesAvailable": "لا توجد دورات متاحة", "noCoursesAvailable": "لا توجد دورات متاحة",
"pullToRefresh": "اسحب للتحديث", "pullToRefresh": "اسحب للتحديث",
"courseUrlNotAvailable": "رابط الدورة غير متوفر", "courseUrlNotAvailable": "رابط الدورة غير متوفر",
"buyerDetails": "تفاصيل المشتري",
"etqanOVR": "الإبلاغ عن حادثة", "etqanOVR": "الإبلاغ عن حادثة",
"etqanRequest": "طلبات الحوادث", "etqanRequest": "طلبات الحوادث",
"selectProject": "حدد المشروع", "selectProject": "حدد المشروع",
@ -702,119 +690,16 @@ class CodegenLoader extends AssetLoader{
"priceSAR": "السعر (ريال سعودي)", "priceSAR": "السعر (ريال سعودي)",
"amountSAR": "المبلغ (ريال سعودي)", "amountSAR": "المبلغ (ريال سعودي)",
"amuLastThreeMonths": "AMU (آخر 3 أشهر)", "amuLastThreeMonths": "AMU (آخر 3 أشهر)",
"mazayaTitle": "مزايا", "buyerDetails": "تفاصيل المشتري",
"mazayaAccessGranted": "تم منح حق الوصول لمزايا", "chatBot": "المساعد الذكي",
"mazayaAccessGrantedDesc": "أصبح لدى {} حق وصول نشط الآن. شارك رابط التنزيل أدناه ليتمكن من تثبيت تطبيق مهم وإكمال الإعداد.", "helloAskMe": "مرحباً، اسألني",
"accessIsNowActive": "الوصول نشط الآن", "anything": "عن أي شيء...",
"accessIsNowActiveDesc": "يمكن لـ {} استخدام الرابط المرفق لتنزيل تطبيق مزايا ومتابعة الإعداد على جهازه.", "thinking": "جاري التفكير",
"dependentAccessActive": "وصول التابع نشط", "waitingForResponse": "في انتظار الرد...",
"shareableAppDownloadLink": "رابط تنزيل التطبيق للمشاركة", "typeAMessage": "اكتب رسالة...",
"shareableAppDownloadLinkDesc": "أرسل هذا الرابط إلى {} ليتمكن من تنزيل تطبيق مزايا ومتابعة الإعداد.", "initializingChatPleaseWait": "يرجى الانتظار، جاري تهيئة المحادثة...",
"downloadLink": "رابط التنزيل", "am": "ص",
"shareDownloadLink": "مشاركة رابط التنزيل", "pm": "م"
"copyLink": "نسخ الرابط",
"done": "تم",
"linkCopied": "تم نسخ الرابط بنجاح",
"mazayaAccessRevoked": "تم إلغاء الوصول لمزايا",
"mazayaAccessRevokedDesc": "قام الكفيل بإلغاء وصول التابع. لم يعد بالإمكان فتح العروض أو تسجيل الدخول بالبصمة.",
"contactYourSponsor": "تواصل مع الكفيل",
"contactYourSponsorDesc": "اطلب من كفيلك السماح بالوصول لمزايا مرة أخرى من تفاصيل التابعين. يمكن للكفيل فقط استعادة الوصول.",
"dependentAccessBlocked": "تم حظر وصول التابع",
"returnToSignIn": "العودة إلى تسجيل الدخول",
"allowMazayaAccess": "السماح بالوصول لمزايا",
"revokeMazayaAccess": "إلغاء الوصول لمزايا",
"active": "نشط",
"noAccess": "غير متاح",
"offerDetails": "تفاصيل العرض",
"offerIdNotAvailable": "معرف العرض غير متوفر",
"unableToRedeemOffer": "تعذر الحصول على رابط العرض، يرجى المحاولة لاحقاً",
"errorRedeemingOffer": "حدث خطأ أثناء استخدام العرض",
"useOffer": "استخدام العرض",
"online": "عبر الإنترنت",
"inStore": "في المتجر",
"ksa": "المملكة",
"riyadh": "الرياض",
"noInstructionsAvailable": "لا توجد طريقة استخدام محددة.",
"contactInfo": "معلومات التواصل",
"allCategories": "الكل",
"noOffersAvailable": "لا توجد عروض متاحة",
"addDependents": "إضافة تابعين",
"myDependents": "التابعون",
"offDiscount": "خصم {}",
"usageLabel": "الاستخدام: ",
"areYouSureWantToLogOut": "هل أنت متأكد من تسجيل الخروج؟",
"searchDependents": "البحث في التابعين",
"searchFamilyMembers": "البحث في أفراد العائلة",
"addAnotherUser": "إضافة مستخدم آخر",
"joinMazaya": "انضم إلى مزايا",
"joinMazayaSubtitle": "احصل على أفضل العروض والصفقات الحصرية في المطاعم والتسوق ومكافآت نمط الحياة.",
"continueAsEmployee": "المتابعة كموظف",
"continueAsEmployeeSubtitle": "سجل الدخول باستخدام اسم المستخدم وكلمة المرور الخاصة بالموظف.",
"pleaseEnterNationalId": "الرجاء إدخال رقم الهوية الوطنية",
"pleaseSelectDob": "الرجاء تحديد تاريخ الميلاد",
"unableToRetrieveDependentInfo": "تعذر الحصول على معلومات التابع",
"failedToSendOtp": "فشل إرسال رمز التحقق",
"verificationCodeResent": "تم إعادة إرسال رمز التحقق",
"nationalId": "الهوية الوطنية",
"addDependent": "إضافة تابع",
"stepVerify": "التحقق",
"stepBasic": "الأساسية",
"stepAdditional": "إضافية",
"findYourDependent": "البحث عن تابعك",
"findYourDependentDesc": "أدخل التفاصيل تماماً كما تظهر في الهوية الوطنية. سنقوم باسترداد السجل بأمان من مركز المعلومات الوطني (NHIC).",
"enter10DigitNationalId": "أدخل رقم الهوية المكون من 10 أرقام",
"nationalIdMustBe10Digits": "يجب أن يتكون رقم الهوية من 10 أرقام",
"dateFormatPlaceholder": "يوم / شهر / سنة",
"searchUser": "بحث عن المستخدم",
"dependentNotFoundNhic": "لم يتم العثور على سجل لهذا التابع في مركز المعلومات الوطني",
"basicInfo": "المعلومات الأساسية",
"basicInfoDesc": "بيانات الهوية المطلوبة المستردة من مركز المعلومات الوطني.",
"additionalInfo": "معلومات إضافية",
"additionalInfoDesc": "إضافة أو مراجعة بيانات الكفالة والأهلية.",
"select": "اختر",
"enterFieldPlaceholder": "أدخل {}",
"requiredField": "الحقل المطلوب",
"pleaseFillField": "الرجاء إدخال {}",
"reviewAndSubmit": "المراجعة والإرسال",
"reviewAndSubmitDesc": "يرجى مراجعة تفاصيل التابع بعناية قبل تأكيد الإرسال.",
"arabicName": "الاسم بالعربية",
"relationship": "صلة القرابة",
"sponsor": "الكفيل",
"dependentSponsorDetail": "تفاصيل كفيل التابع",
"insuranceEligibility": "أهلية التأمين",
"relationshipStartDate": "تاريخ بدء العلاقة",
"dependentAddedSuccessfully": "تم إرسال الطلب بنجاح",
"genderMale": "ذكر",
"genderFemale": "أنثى",
"religionIslam": "الإسلام",
"back": "السابق",
"submitRequest": "إرسال الطلب",
"enterDependentDetailsToContinue": "أدخل تفاصيل التابع للمتابعة.",
"enterBasicDetailsDesc": "يرجى إدخال البيانات الأساسية للتابع.",
"locations": "الفروع والمواقع",
"availableOffers": "العروض المتاحة",
"selectOfferToRedeem": "اختر العرض الذي ترغب في استخدامه",
"viewOnMap": "عرض على الخريطة",
"showAllLocations": "عرض جميع الفروع ({})",
"searchBranches": "البحث عن الفروع أو المدن...",
"allCities": "الكل",
"noBranchesFound": "لم يتم العثور على فروع مطابقة",
"browseAllBranches": "تصفح جميع الفروع ({}) في مختلف المدن",
"branchesCount": "{} فرع",
"kmAway": "على بعد {} كم",
"dependentDetails": "تفاصيل التابع",
"keyDetails": "البيانات الأساسية",
"verified": "موثق",
"idEnding": "الهوية تنتهي بـ {}",
"servicesAndActions": "الخدمات والإجراءات",
"startDependentFlowsDesc": "بدء إجراءات التابع وإدارة الوصول من مكان واحد.",
"mazayaAccess": "الوصول إلى مزايا",
"accessOff": "معطل",
"allowDependentMazayaOffersDesc": "السماح لـ {} باستخدام عروض مزايا بهويتها الموثقة.",
"loginMethod": "طريقة تسجيل الدخول",
"nationalIdAndDob": "الهوية الوطنية وتاريخ الميلاد",
"sponsorOtp": "رمز التحقق للكفيل",
"revokingPreventsAccessDesc": "إلغاء الوصول يمنع التابع من استخدام المزايا حتى تعيد السماح به مرة أخرى."
}; };
static const Map<String,dynamic> _en_US = { static const Map<String,dynamic> _en_US = {
"mohemm": "Mohemm", "mohemm": "Mohemm",
@ -1133,8 +1018,6 @@ static const Map<String,dynamic> _en_US = {
"requestType": "Request Type", "requestType": "Request Type",
"requestCreatedSuccessfully": "Request created successfully", "requestCreatedSuccessfully": "Request created successfully",
"search": "Search", "search": "Search",
"filterEnterPrDetailsHere": "Enter PR Details here",
"filterEnterPoDetailsHere": "Enter PO Details here",
"wantToReject": "Are you sure want to reject?", "wantToReject": "Are you sure want to reject?",
"employeeDigitalID": "Employee Digital ID", "employeeDigitalID": "Employee Digital ID",
"businessCard": "Business Card", "businessCard": "Business Card",
@ -1367,15 +1250,6 @@ static const Map<String,dynamic> _en_US = {
"manage": "Manage", "manage": "Manage",
"members": "Members", "members": "Members",
"areYouSureWantTodelete": "Are you sure want to delete?", "areYouSureWantTodelete": "Are you sure want to delete?",
"confirmGrantMazayaAccess": "Are you sure you want to grant Mazaya access to \"{}\"?",
"confirmRevokeMazayaAccess": "Are you sure you want to revoke Mazaya access to \"{}\"?",
"yes": "Yes",
"no": "No",
"mazayaInviteInstructions": "Please download the Mohemm app from the above link and use the following credentials to login",
"cameraPermissionRequiredToScan": "Please grant camera permission to scan the QR code",
"cameraPermissionPermanentlyDenied": "Please allow camera permission from the app settings > Permissions",
"openSettings": "Open Settings",
"nationalIdOrIqamaFallback": "Your National ID or Iqama Number",
"groupMembers": "Group Members", "groupMembers": "Group Members",
"manageGroup": "Manage Group", "manageGroup": "Manage Group",
"admin": "Admin", "admin": "Admin",
@ -1482,7 +1356,6 @@ static const Map<String,dynamic> _en_US = {
"noCoursesAvailable": "No Courses Available", "noCoursesAvailable": "No Courses Available",
"pullToRefresh": "Pull to refresh", "pullToRefresh": "Pull to refresh",
"courseUrlNotAvailable": "Course URL not available", "courseUrlNotAvailable": "Course URL not available",
"buyerDetails": "Buyer Details",
"etqanOVR": "Report An Incident", "etqanOVR": "Report An Incident",
"etqanRequest": "Incident Requests", "etqanRequest": "Incident Requests",
"selectProject": "Select Project", "selectProject": "Select Project",
@ -1504,119 +1377,16 @@ static const Map<String,dynamic> _en_US = {
"priceSAR": "Price (SAR)", "priceSAR": "Price (SAR)",
"amountSAR": "Amount (SAR)", "amountSAR": "Amount (SAR)",
"amuLastThreeMonths": "AMU (Last 3 months)", "amuLastThreeMonths": "AMU (Last 3 months)",
"mazayaTitle": "Mazaya", "buyerDetails": "Buyer Details",
"mazayaAccessGranted": "Mazaya access granted", "chatBot": "ChatBot",
"mazayaAccessGrantedDesc": "{} now has active access. Share the download link below so they can install the Mohem app and complete setup.", "helloAskMe": "Hello, ask me",
"accessIsNowActive": "Access is now active", "anything": "anything...",
"accessIsNowActiveDesc": "{} can use the provided link to download the Mazaya app and continue setup on their device.", "thinking": "Thinking",
"dependentAccessActive": "Dependent access active", "waitingForResponse": "Waiting for response...",
"shareableAppDownloadLink": "Shareable app-download link", "typeAMessage": "Type a message..",
"shareableAppDownloadLinkDesc": "Send this link to {} so they can download the Mazaya app and continue setup.", "initializingChatPleaseWait": "Please wait, initializing chat...",
"downloadLink": "Download link", "am": "AM",
"shareDownloadLink": "Share Download Link", "pm": "PM"
"copyLink": "Copy Link",
"done": "Done",
"linkCopied": "Link copied to clipboard",
"mazayaAccessRevoked": "Mazaya access revoked",
"mazayaAccessRevokedDesc": "Your sponsor has revoked your dependent access. You can no longer open offers or sign in with biometrics.",
"contactYourSponsor": "Contact your sponsor",
"contactYourSponsorDesc": "Ask your sponsor to allow Mazaya access again from Dependent Details. Only the sponsor can restore access.",
"dependentAccessBlocked": "Dependent access blocked",
"returnToSignIn": "Return to Sign In",
"allowMazayaAccess": "Allow Mazaya Access",
"revokeMazayaAccess": "Revoke Mazaya Access",
"active": "Active",
"noAccess": "No access",
"offerDetails": "Offer Details",
"offerIdNotAvailable": "Offer ID not available",
"unableToRedeemOffer": "Unable to redeem offer, please try again later",
"errorRedeemingOffer": "An error occurred while redeeming offer",
"useOffer": "Use Offer",
"online": "Online",
"inStore": "In Store",
"ksa": "KSA",
"riyadh": "Riyadh",
"noInstructionsAvailable": "No instructions available.",
"contactInfo": "Contact Information",
"allCategories": "All",
"noOffersAvailable": "No offers available",
"addDependents": "Add Dependents",
"myDependents": "My Dependents",
"offDiscount": "{} Off",
"usageLabel": "Usage: ",
"areYouSureWantToLogOut": "Are you sure you want to log out?",
"searchDependents": "Search dependents",
"searchFamilyMembers": "Search family members",
"addAnotherUser": "Add another user",
"joinMazaya": "Join Mazaya",
"joinMazayaSubtitle": "Get the best offers and exclusive deals across dining, shopping, and lifestyle rewards.",
"continueAsEmployee": "Continue as an employee",
"continueAsEmployeeSubtitle": "Sign in with your employee username and password.",
"pleaseEnterNationalId": "Please enter National ID",
"pleaseSelectDob": "Please select Date of Birth",
"unableToRetrieveDependentInfo": "Unable to retrieve dependent information",
"failedToSendOtp": "Failed to send verification code",
"verificationCodeResent": "Verification code resent",
"nationalId": "National ID",
"addDependent": "Add Dependent",
"stepVerify": "Verify",
"stepBasic": "Basic",
"stepAdditional": "Additional",
"findYourDependent": "Find your dependent",
"findYourDependentDesc": "Enter the details exactly as they appear on the National ID. We'll securely retrieve the record from NHIC.",
"enter10DigitNationalId": "Enter 10-digit National ID",
"nationalIdMustBe10Digits": "National ID must be 10 digits",
"dateFormatPlaceholder": "DD / MM / YYYY",
"searchUser": "Search User",
"dependentNotFoundNhic": "Sorry, error while getting dependent info from NHIC",
"basicInfo": "Basic Info",
"basicInfoDesc": "Required identity details fetched from NHIC.",
"additionalInfo": "Additional Info",
"additionalInfoDesc": "Add or review sponsorship and eligibility details.",
"select": "Select",
"enterFieldPlaceholder": "Enter {}",
"requiredField": "Required field",
"pleaseFillField": "Please fill {}",
"reviewAndSubmit": "Review & Submit",
"reviewAndSubmitDesc": "Please review the dependent details carefully before submitting.",
"arabicName": "Arabic Name",
"relationship": "Relationship",
"sponsor": "Sponsor",
"dependentSponsorDetail": "Dependent Sponsor Detail",
"insuranceEligibility": "Insurance Eligibility",
"relationshipStartDate": "Start Date",
"dependentAddedSuccessfully": "Dependent added successfully",
"genderMale": "Male",
"genderFemale": "Female",
"religionIslam": "Islam",
"back": "Back",
"submitRequest": "Submit",
"enterDependentDetailsToContinue": "Enter the dependent details to continue.",
"enterBasicDetailsDesc": "Please enter the dependent's basic identity details.",
"locations": "Locations",
"availableOffers": "Available Offers",
"selectOfferToRedeem": "Select an offer to use",
"viewOnMap": "View on map",
"showAllLocations": "Show all {} locations",
"searchBranches": "Search branches or cities...",
"allCities": "All",
"noBranchesFound": "No branches found",
"browseAllBranches": "Browse all {} branches across cities",
"branchesCount": "{} Branches",
"kmAway": "{} KM Away",
"dependentDetails": "Dependent Details",
"keyDetails": "Key details",
"verified": "Verified",
"idEnding": "ID ending {}",
"servicesAndActions": "Services & actions",
"startDependentFlowsDesc": "Start dependent flows and manage access from one place.",
"mazayaAccess": "Mazaya access",
"accessOff": "Off",
"allowDependentMazayaOffersDesc": "Allow {} to use Mazaya offers with her verified identity.",
"loginMethod": "Login method",
"nationalIdAndDob": "National ID & Date of Birth",
"sponsorOtp": "Sponsor OTP",
"revokingPreventsAccessDesc": "Revoking prevents all future dependent access until you allow it again."
}; };
static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; static const Map<String, Map<String,dynamic>> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US};
} }

@ -590,15 +590,6 @@ abstract class LocaleKeys {
static const addUsers = 'addUsers'; static const addUsers = 'addUsers';
static const admin = 'admin'; static const admin = 'admin';
static const areYouSureWantTodelete = 'areYouSureWantTodelete'; static const areYouSureWantTodelete = 'areYouSureWantTodelete';
static const confirmGrantMazayaAccess = 'confirmGrantMazayaAccess';
static const confirmRevokeMazayaAccess = 'confirmRevokeMazayaAccess';
static const yes = 'yes';
static const no = 'no';
static const mazayaInviteInstructions = 'mazayaInviteInstructions';
static const cameraPermissionRequiredToScan = 'cameraPermissionRequiredToScan';
static const cameraPermissionPermanentlyDenied = 'cameraPermissionPermanentlyDenied';
static const openSettings = 'openSettings';
static const nationalIdOrIqamaFallback = 'nationalIdOrIqamaFallback';
static const Attendance = 'Attendance'; static const Attendance = 'Attendance';
static const audioCall = 'audioCall'; static const audioCall = 'audioCall';
static const editGroups = 'editGroups'; static const editGroups = 'editGroups';
@ -674,118 +665,14 @@ abstract class LocaleKeys {
static const priceSAR = 'priceSAR'; static const priceSAR = 'priceSAR';
static const amountSAR = 'amountSAR'; static const amountSAR = 'amountSAR';
static const amuLastThreeMonths = 'amuLastThreeMonths'; static const amuLastThreeMonths = 'amuLastThreeMonths';
static const mazayaTitle = 'mazayaTitle'; static const chatBot = 'chatBot';
static const mazayaAccessGranted = 'mazayaAccessGranted'; static const helloAskMe = 'helloAskMe';
static const mazayaAccessGrantedDesc = 'mazayaAccessGrantedDesc'; static const anything = 'anything';
static const accessIsNowActive = 'accessIsNowActive'; static const thinking = 'thinking';
static const accessIsNowActiveDesc = 'accessIsNowActiveDesc'; static const waitingForResponse = 'waitingForResponse';
static const dependentAccessActive = 'dependentAccessActive'; static const typeAMessage = 'typeAMessage';
static const shareableAppDownloadLink = 'shareableAppDownloadLink'; static const initializingChatPleaseWait = 'initializingChatPleaseWait';
static const shareableAppDownloadLinkDesc = 'shareableAppDownloadLinkDesc'; static const am = 'am';
static const downloadLink = 'downloadLink'; static const pm = 'pm';
static const shareDownloadLink = 'shareDownloadLink';
static const copyLink = 'copyLink';
static const done = 'done';
static const linkCopied = 'linkCopied';
static const mazayaAccessRevoked = 'mazayaAccessRevoked';
static const mazayaAccessRevokedDesc = 'mazayaAccessRevokedDesc';
static const contactYourSponsor = 'contactYourSponsor';
static const contactYourSponsorDesc = 'contactYourSponsorDesc';
static const dependentAccessBlocked = 'dependentAccessBlocked';
static const returnToSignIn = 'returnToSignIn';
static const allowMazayaAccess = 'allowMazayaAccess';
static const revokeMazayaAccess = 'revokeMazayaAccess';
static const active = 'active';
static const noAccess = 'noAccess';
static const offerDetails = 'offerDetails';
static const offerIdNotAvailable = 'offerIdNotAvailable';
static const unableToRedeemOffer = 'unableToRedeemOffer';
static const errorRedeemingOffer = 'errorRedeemingOffer';
static const useOffer = 'useOffer';
static const online = 'online';
static const inStore = 'inStore';
static const ksa = 'ksa';
static const riyadh = 'riyadh';
static const noInstructionsAvailable = 'noInstructionsAvailable';
static const contactInfo = 'contactInfo';
static const allCategories = 'allCategories';
static const noOffersAvailable = 'noOffersAvailable';
static const addDependents = 'addDependents';
static const myDependents = 'myDependents';
static const offDiscount = 'offDiscount';
static const usageLabel = 'usageLabel';
static const areYouSureWantToLogOut = 'areYouSureWantToLogOut';
static const searchDependents = 'searchDependents';
static const searchFamilyMembers = 'searchFamilyMembers';
static const addAnotherUser = 'addAnotherUser';
static const joinMazaya = 'joinMazaya';
static const joinMazayaSubtitle = 'joinMazayaSubtitle';
static const continueAsEmployee = 'continueAsEmployee';
static const continueAsEmployeeSubtitle = 'continueAsEmployeeSubtitle';
static const pleaseEnterNationalId = 'pleaseEnterNationalId';
static const pleaseSelectDob = 'pleaseSelectDob';
static const unableToRetrieveDependentInfo = 'unableToRetrieveDependentInfo';
static const failedToSendOtp = 'failedToSendOtp';
static const verificationCodeResent = 'verificationCodeResent';
static const nationalId = 'nationalId';
static const addDependent = 'addDependent';
static const stepVerify = 'stepVerify';
static const stepBasic = 'stepBasic';
static const stepAdditional = 'stepAdditional';
static const findYourDependent = 'findYourDependent';
static const findYourDependentDesc = 'findYourDependentDesc';
static const enter10DigitNationalId = 'enter10DigitNationalId';
static const nationalIdMustBe10Digits = 'nationalIdMustBe10Digits';
static const dateFormatPlaceholder = 'dateFormatPlaceholder';
static const searchUser = 'searchUser';
static const dependentNotFoundNhic = 'dependentNotFoundNhic';
static const basicInfo = 'basicInfo';
static const basicInfoDesc = 'basicInfoDesc';
static const additionalInfo = 'additionalInfo';
static const additionalInfoDesc = 'additionalInfoDesc';
static const select = 'select';
static const enterFieldPlaceholder = 'enterFieldPlaceholder';
static const requiredField = 'requiredField';
static const pleaseFillField = 'pleaseFillField';
static const reviewAndSubmit = 'reviewAndSubmit';
static const reviewAndSubmitDesc = 'reviewAndSubmitDesc';
static const arabicName = 'arabicName';
static const relationship = 'relationship';
static const sponsor = 'sponsor';
static const dependentSponsorDetail = 'dependentSponsorDetail';
static const insuranceEligibility = 'insuranceEligibility';
static const relationshipStartDate = 'relationshipStartDate';
static const dependentAddedSuccessfully = 'dependentAddedSuccessfully';
static const genderMale = 'genderMale';
static const genderFemale = 'genderFemale';
static const religionIslam = 'religionIslam';
static const back = 'back';
static const submitRequest = 'submitRequest';
static const enterDependentDetailsToContinue = 'enterDependentDetailsToContinue';
static const enterBasicDetailsDesc = 'enterBasicDetailsDesc';
static const locations = 'locations';
static const availableOffers = 'availableOffers';
static const selectOfferToRedeem = 'selectOfferToRedeem';
static const viewOnMap = 'viewOnMap';
static const showAllLocations = 'showAllLocations';
static const searchBranches = 'searchBranches';
static const allCities = 'allCities';
static const noBranchesFound = 'noBranchesFound';
static const browseAllBranches = 'browseAllBranches';
static const branchesCount = 'branchesCount';
static const kmAway = 'kmAway';
static const dependentDetails = 'dependentDetails';
static const keyDetails = 'keyDetails';
static const verified = 'verified';
static const idEnding = 'idEnding';
static const servicesAndActions = 'servicesAndActions';
static const startDependentFlowsDesc = 'startDependentFlowsDesc';
static const mazayaAccess = 'mazayaAccess';
static const accessOff = 'accessOff';
static const allowDependentMazayaOffersDesc = 'allowDependentMazayaOffersDesc';
static const loginMethod = 'loginMethod';
static const nationalIdAndDob = 'nationalIdAndDob';
static const sponsorOtp = 'sponsorOtp';
static const revokingPreventsAccessDesc = 'revokingPreventsAccessDesc';
}
}

@ -8,7 +8,6 @@
import 'package:audio_session/audio_session_web.dart'; import 'package:audio_session/audio_session_web.dart';
import 'package:camera_web/camera_web.dart'; import 'package:camera_web/camera_web.dart';
import 'package:file_picker/_internal/file_picker_web.dart';
import 'package:firebase_core_web/firebase_core_web.dart'; import 'package:firebase_core_web/firebase_core_web.dart';
import 'package:firebase_messaging_web/firebase_messaging_web.dart'; import 'package:firebase_messaging_web/firebase_messaging_web.dart';
import 'package:fluttertoast/fluttertoast_web.dart'; import 'package:fluttertoast/fluttertoast_web.dart';
@ -27,7 +26,6 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void registerPlugins(Registrar registrar) { void registerPlugins(Registrar registrar) {
AudioSessionWeb.registerWith(registrar); AudioSessionWeb.registerWith(registrar);
CameraPlugin.registerWith(registrar); CameraPlugin.registerWith(registrar);
FilePickerWeb.registerWith(registrar);
FirebaseCoreWeb.registerWith(registrar); FirebaseCoreWeb.registerWith(registrar);
FirebaseMessagingWeb.registerWith(registrar); FirebaseMessagingWeb.registerWith(registrar);
FluttertoastWebPlugin.registerWith(registrar); FluttertoastWebPlugin.registerWith(registrar);

@ -19,6 +19,8 @@ import 'package:month_year_picker/month_year_picker.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart'; import 'package:provider/single_child_widget.dart';
import 'package:sizer/sizer.dart'; import 'package:sizer/sizer.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb;
// test uat account // test uat account
// username 199067 // username 199067
@ -77,7 +79,7 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Sizer( return Sizer(
builder: (BuildContext context, Orientation orientation, DeviceType deviceType) { builder: (BuildContext context, Orientation orientation, ScreenType deviceType){
PostParamsModel? obj = AppState().postParamsObject; PostParamsModel? obj = AppState().postParamsObject;
obj?.languageID = EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2; obj?.languageID = EasyLocalization.of(context)?.locale.languageCode == "ar" ? 1 : 2;
AppState().setPostParamsModel(obj!); AppState().setPostParamsModel(obj!);
@ -101,8 +103,9 @@ class MyApp extends StatelessWidget {
routes: AppRoutes.routes, routes: AppRoutes.routes,
), ),
); );
}, }
); );
} }
} }

@ -1,92 +0,0 @@
class GetMobileLoginInfoDependentModel {
int? iD;
String? dependentID;
String? depedentName;
String? dob;
int? channelID;
int? companyID;
String? deviceType;
String? deviceToken;
int? language;
int? gender;
int? loginType;
String? createdOn;
String? editedOn;
bool? businessCardPrivilege;
GetMobileLoginInfoDependentModel({
this.iD,
this.dependentID,
this.depedentName,
this.dob,
this.channelID,
this.companyID,
this.deviceType,
this.deviceToken,
this.language,
this.gender,
this.loginType,
this.createdOn,
this.editedOn,
this.businessCardPrivilege,
});
// Convenience aliases for existing logic
String? get nationalId => dependentID;
String? get birthDate => dob;
String? get employeeName => depedentName;
String? get dependentName => depedentName;
GetMobileLoginInfoDependentModel.fromJson(Map<String, dynamic> json) {
iD = json['ID'];
dependentID = (json['DependentID'] ??
json['DEPENDENT_ID'] ??
json['P_DEP_NATIONAL_ID'] ??
json['NationalID'] ??
json['NationalId'] ??
json['EmployeeID'] ??
json['UserName'])
?.toString();
depedentName = (json['DepedentName'] ??
json['DependentName'] ??
json['P_DEPENDENT_NAME'] ??
json['EmployeeName'])
?.toString();
dob = (json['DOB'] ??
json['BirthDate'] ??
json['DateOfBirth'] ??
json['P_DEP_BIRTH_DATE'] ??
json['P_BIRTH_DATE'] ??
json['Birth_Date'])
?.toString();
channelID = json['ChannelID'] != null ? int.tryParse(json['ChannelID'].toString()) : null;
companyID = json['CompanyID'] != null ? int.tryParse(json['CompanyID'].toString()) : null;
deviceType = json['DeviceType']?.toString();
deviceToken = json['DeviceToken']?.toString();
language = json['Language'] != null ? int.tryParse(json['Language'].toString()) : null;
gender = json['Gender'] != null ? int.tryParse(json['Gender'].toString()) : null;
loginType = json['LoginType'] != null ? int.tryParse(json['LoginType'].toString()) : null;
createdOn = json['CreatedOn']?.toString();
editedOn = json['EditedOn']?.toString();
businessCardPrivilege = json['BusinessCardPrivilege'];
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['ID'] = iD;
data['DependentID'] = dependentID;
data['DepedentName'] = depedentName;
data['DOB'] = dob;
data['ChannelID'] = channelID;
data['CompanyID'] = companyID;
data['DeviceType'] = deviceType;
data['DeviceToken'] = deviceToken;
data['Language'] = language;
data['Gender'] = gender;
data['LoginType'] = loginType;
data['CreatedOn'] = createdOn;
data['EditedOn'] = editedOn;
data['BusinessCardPrivilege'] = businessCardPrivilege;
return data;
}
}

@ -107,7 +107,6 @@ import 'package:mohem_flutter_app/models/vacation_rule/respond_attributes_list_m
import 'package:mohem_flutter_app/models/vacation_rule/vr_item_types_list_model.dart'; import 'package:mohem_flutter_app/models/vacation_rule/vr_item_types_list_model.dart';
import 'package:mohem_flutter_app/models/vacation_rule/wf_look_up_list_model.dart'; import 'package:mohem_flutter_app/models/vacation_rule/wf_look_up_list_model.dart';
import 'package:mohem_flutter_app/models/validate_eit_transaction_list_model.dart'; import 'package:mohem_flutter_app/models/validate_eit_transaction_list_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
import 'package:mohem_flutter_app/models/worklist/GetRFCEmployeeList.dart'; import 'package:mohem_flutter_app/models/worklist/GetRFCEmployeeList.dart';
import 'package:mohem_flutter_app/models/worklist/get_fad_notifications.dart'; import 'package:mohem_flutter_app/models/worklist/get_fad_notifications.dart';
import 'package:mohem_flutter_app/models/worklist/get_favorite_replacements_model.dart'; import 'package:mohem_flutter_app/models/worklist/get_favorite_replacements_model.dart';
@ -400,13 +399,9 @@ class GenericResponseModel {
bool? isSMSSent; bool? isSMSSent;
PortalDirectionData? portalDirectionData; PortalDirectionData? portalDirectionData;
String? Mohemm_ENSAT_GetItgToken_Response; String? Mohemm_ENSAT_GetItgToken_Response;
AtlasInitiateChatResponse? atlasInitiateChatResponse;
AtlasContinueChatResponseDetails? atlasContinueChatResponseDetails;
GetWalaplusDepInfoModel? getWalaplusDepInfoModel;
dynamic getWalaplusDepInfoModelInternal;
List<WalaPlusCategoryModel>? walaPlusCategories;
String? businessCardQROutside;
EmpBusinessCardInfoModel? empBusinessCardInfoModel;
GenericResponseModel({ GenericResponseModel({
this.date, this.date,
@ -680,11 +675,9 @@ class GenericResponseModel {
this.isSMSSent, this.isSMSSent,
this.getFADisposalNtfDetails, this.getFADisposalNtfDetails,
this.Mohemm_ENSAT_GetItgToken_Response, this.Mohemm_ENSAT_GetItgToken_Response,
this.getWalaplusDepInfoModel, this.atlasInitiateChatResponse,
this.getWalaplusDepInfoModelInternal, this.atlasContinueChatResponseDetails,
this.walaPlusCategories,
this.businessCardQROutside,
this.empBusinessCardInfoModel,
}); });
GenericResponseModel.fromJson(Map<String, dynamic> json) { GenericResponseModel.fromJson(Map<String, dynamic> json) {
@ -693,7 +686,7 @@ class GenericResponseModel {
serviceName = json['ServiceName']; serviceName = json['ServiceName'];
time = json['Time']; time = json['Time'];
androidLink = json['AndroidLink']; androidLink = json['AndroidLink'];
authenticationTokenID = json['AuthenticationTokenID']?.toString(); authenticationTokenID = json['AuthenticationTokenID'];
data = json['Data']; data = json['Data'];
dataw = json['Dataw']; dataw = json['Dataw'];
dietType = json['DietType']; dietType = json['DietType'];
@ -1478,20 +1471,11 @@ class GenericResponseModel {
ePharmacyGetItemOnHandList = json['ePharmacy_GetItemOnHandList']; ePharmacyGetItemOnHandList = json['ePharmacy_GetItemOnHandList'];
isActiveCode = json['isActiveCode']; isActiveCode = json['isActiveCode'];
isSMSSent = json['isSMSSent']; isSMSSent = json['isSMSSent'];
Mohemm_ENSAT_GetItgToken_Response = json['Mohemm_ENSAT_GetItgToken_Response']; atlasInitiateChatResponse = json["Atlas_InitiateChat_Response"] == null ? null : AtlasInitiateChatResponse.fromJson(json["Atlas_InitiateChat_Response"]);
atlasContinueChatResponseDetails = json["Atlas_ContinueChat_ResponseDetails"] == null ? null : AtlasContinueChatResponseDetails.fromJson(json["Atlas_ContinueChat_ResponseDetails"]);
getWalaplusDepInfoModel = json['GET_WALAPLUS_DEP_INFO_Model'] != null ? GetWalaplusDepInfoModel.fromJson(json['GET_WALAPLUS_DEP_INFO_Model']) : null;
getWalaplusDepInfoModelInternal = json['GET_WALAPLUS_DEP_INFO_Model_Internal'];
businessCardQROutside = json['BusinessCardQR_Outside'];
empBusinessCardInfoModel = json['GET_EMP_BUSINESS_CARD_INFO_Model'] != null ? EmpBusinessCardInfoModel.fromJson(json['GET_EMP_BUSINESS_CARD_INFO_Model']) : null;
if (json['WalaPlus_Categories'] != null) { Mohemm_ENSAT_GetItgToken_Response = json['Mohemm_ENSAT_GetItgToken_Response'];
walaPlusCategories = <WalaPlusCategoryModel>[];
json['WalaPlus_Categories'].forEach((v) {
walaPlusCategories!.add(WalaPlusCategoryModel.fromJson(v));
});
}
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -1977,22 +1961,10 @@ class GenericResponseModel {
data['ePharmacy_GetItemOnHandList'] = this.ePharmacyGetItemOnHandList; data['ePharmacy_GetItemOnHandList'] = this.ePharmacyGetItemOnHandList;
data['isActiveCode'] = this.isActiveCode; data['isActiveCode'] = this.isActiveCode;
data['isSMSSent'] = this.isSMSSent; data['isSMSSent'] = this.isSMSSent;
data['Mohemm_ENSAT_GetItgToken_Response'] = this.Mohemm_ENSAT_GetItgToken_Response; data["Atlas_InitiateChat_Response"] = atlasInitiateChatResponse?.toJson();
data["Atlas_ContinueChat_ResponseDetails"] = atlasContinueChatResponseDetails?.toJson();
data['GET_WALAPLUS_DEP_INFO_Model_Internal'] = this.getWalaplusDepInfoModelInternal;
if (this.getWalaplusDepInfoModel != null) {
data['GET_WALAPLUS_DEP_INFO_Model'] = this.getWalaplusDepInfoModel!.toJson();
}
data['BusinessCardQR_Outside'] = this.businessCardQROutside;
if (this.empBusinessCardInfoModel != null) {
data['GET_EMP_BUSINESS_CARD_INFO_Model'] = this.empBusinessCardInfoModel!.toJson();
}
if (walaPlusCategories != null) {
data['WalaPlus_Categories'] = walaPlusCategories!.map((WalaPlusCategoryModel v) => v.toJson()).toList();
}
data['Mohemm_ENSAT_GetItgToken_Response'] = this.Mohemm_ENSAT_GetItgToken_Response;
return data; return data;
} }
} }
@ -2019,46 +1991,137 @@ class TicketBookingResult {
TicketBookingResult(this.success, this.clientId); TicketBookingResult(this.success, this.clientId);
} }
class GetWalaplusDepInfoModel { class AtlasInitiateChatResponse {
String? pEmpEmailAddress; String? atlasAccessToken;
String? pEmpMobileNumber; String? atlasChatSessionId;
String? pDependentName; String? atlasTokenType;
AtlasInitiateChatResponse({this.atlasAccessToken, this.atlasChatSessionId, this.atlasTokenType});
factory AtlasInitiateChatResponse.fromRawJson(String str) => AtlasInitiateChatResponse.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AtlasInitiateChatResponse.fromJson(Map<String, dynamic> json) =>
AtlasInitiateChatResponse(atlasAccessToken: json["Atlas_AccessToken"], atlasChatSessionId: json["Atlas_ChatSessionID"], atlasTokenType: json["Atlas_TokenType"]);
Map<String, dynamic> toJson() => {"Atlas_AccessToken": atlasAccessToken, "Atlas_ChatSessionID": atlasChatSessionId, "Atlas_TokenType": atlasTokenType};
}
class AtlasContinueChatResponseDetails {
AiResponse? aiResponse;
QueryClassification? queryClassification;
String? sessionId;
AiResponse? userMessage;
AtlasContinueChatResponseDetails({
this.aiResponse,
this.queryClassification,
this.sessionId,
this.userMessage,
});
factory AtlasContinueChatResponseDetails.fromRawJson(String str) => AtlasContinueChatResponseDetails.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory AtlasContinueChatResponseDetails.fromJson(Map<String, dynamic> json) => AtlasContinueChatResponseDetails(
aiResponse: json["ai_response"] == null ? null : AiResponse.fromJson(json["ai_response"]),
queryClassification: json["query_classification"] == null ? null : QueryClassification.fromJson(json["query_classification"]),
sessionId: json["session_id"],
userMessage: json["user_message"] == null ? null : AiResponse.fromJson(json["user_message"]),
);
Map<String, dynamic> toJson() => {
"ai_response": aiResponse?.toJson(),
"query_classification": queryClassification?.toJson(),
"session_id": sessionId,
"user_message": userMessage?.toJson(),
};
}
class AiResponse {
DateTime? createdAt;
String? id;
StructuredData? structuredData;
String? text;
AiResponse({
this.createdAt,
this.id,
this.structuredData,
this.text,
});
factory AiResponse.fromRawJson(String str) => AiResponse.fromJson(json.decode(str));
GetWalaplusDepInfoModel({this.pEmpEmailAddress, this.pEmpMobileNumber, this.pDependentName}); String toRawJson() => json.encode(toJson());
factory GetWalaplusDepInfoModel.fromJson(Map<String, dynamic> json) => factory AiResponse.fromJson(Map<String, dynamic> json) => AiResponse(
GetWalaplusDepInfoModel(pEmpEmailAddress: json["P_EMP_EMAIL_ADDRESS"], pEmpMobileNumber: json["P_EMP_MOBILE_NUMBER"], pDependentName: json["P_DEPENDENT_NAME"]); createdAt: json["created_at"] == null ? null : DateTime.parse(json["created_at"]),
id: json["id"],
structuredData: json["structured_data"] == null ? null : StructuredData.fromJson(json["structured_data"]),
text: json["text"],
);
Map<String, dynamic> toJson() => {"P_EMP_EMAIL_ADDRESS": pEmpEmailAddress, "P_EMP_MOBILE_NUMBER": pEmpMobileNumber, "P_DEPENDENT_NAME": pDependentName}; Map<String, dynamic> toJson() => {
"created_at": createdAt?.toIso8601String(),
"id": id,
"structured_data": structuredData?.toJson(),
"text": text,
};
} }
class EmpBusinessCardInfoModel { class StructuredData {
String? pEmailAddress; String? answer;
String? pFirstName; List<dynamic>? sources;
String? pLastName; String? type;
String? pMobilePhoneNumber;
String? pPositionTitle; StructuredData({
String? pWorkExtensionNumber; this.answer,
this.sources,
EmpBusinessCardInfoModel({this.pEmailAddress, this.pFirstName, this.pLastName, this.pMobilePhoneNumber, this.pPositionTitle, this.pWorkExtensionNumber}); this.type,
});
String get fullName => [pFirstName, pLastName].where((e) => e != null && e.trim().isNotEmpty).join(" ");
factory StructuredData.fromRawJson(String str) => StructuredData.fromJson(json.decode(str));
factory EmpBusinessCardInfoModel.fromJson(Map<String, dynamic> json) => EmpBusinessCardInfoModel(
pEmailAddress: json["P_EMAIL_ADDRESS"], String toRawJson() => json.encode(toJson());
pFirstName: json["P_FIRST_NAME"],
pLastName: json["P_LAST_NAME"], factory StructuredData.fromJson(Map<String, dynamic> json) => StructuredData(
pMobilePhoneNumber: json["P_MOBILE_PHONE_NUMBER"], answer: json["answer"],
pPositionTitle: json["P_POSITION_TITLE"], sources: json["sources"] == null ? [] : List<dynamic>.from(json["sources"]!.map((x) => x)),
pWorkExtensionNumber: json["P_WORK_EXTENSION_NUMBER"], type: json["type"],
);
Map<String, dynamic> toJson() => {
"answer": answer,
"sources": sources == null ? [] : List<dynamic>.from(sources!.map((x) => x)),
"type": type,
};
}
class QueryClassification {
int? confidence;
String? type;
QueryClassification({
this.confidence,
this.type,
});
factory QueryClassification.fromRawJson(String str) => QueryClassification.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory QueryClassification.fromJson(Map<String, dynamic> json) => QueryClassification(
confidence: json["confidence"],
type: json["type"],
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"P_EMAIL_ADDRESS": pEmailAddress, "confidence": confidence,
"P_FIRST_NAME": pFirstName, "type": type,
"P_LAST_NAME": pLastName,
"P_MOBILE_PHONE_NUMBER": pMobilePhoneNumber,
"P_POSITION_TITLE": pPositionTitle,
"P_WORK_EXTENSION_NUMBER": pWorkExtensionNumber,
}; };
} }

@ -1,4 +1,3 @@
import 'package:flutter/foundation.dart';
class GetEmployeeContactsList { class GetEmployeeContactsList {
String? cONTACTNAME; String? cONTACTNAME;
@ -9,34 +8,16 @@ class GetEmployeeContactsList {
int? pERSONID; int? pERSONID;
String? pRIMARYCONTACTFLAG; String? pRIMARYCONTACTFLAG;
String? rELATIONSHIP; String? rELATIONSHIP;
String? mAZAYAACCESSFLG;
dynamic mazayaAccess;
String? nATIONALIDENTIFIER;
// Real ERP response fields - needed to call the grant-access API later (as P_BIRTH_DATE / GetEmployeeContactsList(
// P_NHIC_VERIFIED_FLAG request params). PatientIdentificationID/PatientOutSA are NOT {this.cONTACTNAME,
// response fields - they're constructed at call time (from nATIONALIDENTIFIER, and a
// hardcoded 0, respectively), so they don't belong on this model.
// Not a plain string per ERP - keep untyped so whatever shape comes back (epoch, nested
// date object, etc.) survives round-trip, instead of assuming a format.
dynamic pBIRTHDATE;
String? pNHICVERIFIEDFLAG;
GetEmployeeContactsList({
this.cONTACTNAME,
this.cONTACTPERSONID, this.cONTACTPERSONID,
this.cONTACTRELATIONSHIPID, this.cONTACTRELATIONSHIPID,
this.cONTACTTYPE, this.cONTACTTYPE,
this.dATEOFBIRTH, this.dATEOFBIRTH,
this.pERSONID, this.pERSONID,
this.pRIMARYCONTACTFLAG, this.pRIMARYCONTACTFLAG,
this.rELATIONSHIP, this.rELATIONSHIP});
this.mAZAYAACCESSFLG,
this.mazayaAccess,
this.nATIONALIDENTIFIER,
this.pBIRTHDATE,
this.pNHICVERIFIEDFLAG,
});
GetEmployeeContactsList.fromJson(Map<String, dynamic> json) { GetEmployeeContactsList.fromJson(Map<String, dynamic> json) {
cONTACTNAME = json['CONTACT_NAME']; cONTACTNAME = json['CONTACT_NAME'];
@ -47,41 +28,18 @@ class GetEmployeeContactsList {
pERSONID = json['PERSON_ID']; pERSONID = json['PERSON_ID'];
pRIMARYCONTACTFLAG = json['PRIMARY_CONTACT_FLAG']; pRIMARYCONTACTFLAG = json['PRIMARY_CONTACT_FLAG'];
rELATIONSHIP = json['RELATIONSHIP']; rELATIONSHIP = json['RELATIONSHIP'];
mAZAYAACCESSFLG = json['MAZAYA_ACCESS_FLG']?.toString() ?? json['MAZAYA_ACCESS']?.toString();
mazayaAccess = mAZAYAACCESSFLG;
nATIONALIDENTIFIER =
json['NATIONAL_IDENTIFIER']?.toString() ??
json['NATIONAL_ID']?.toString() ??
json['CIVIL_ID']?.toString() ??
json['IQAMA_NUMBER']?.toString() ??
json['CONTACT_NATIONAL_IDENTIFIER']?.toString() ??
json['CONTACT_NATIONAL_ID']?.toString() ??
json['CONTACT_IDENTIFICATION_ID']?.toString() ??
json['IDENTIFICATION_ID']?.toString() ??
json['NATIONALID']?.toString();
if (nATIONALIDENTIFIER == null || nATIONALIDENTIFIER!.trim().isEmpty) {
debugPrint("GetEmployeeContactsList: no national id match found, raw contact json: $json");
}
pBIRTHDATE = json['P_BIRTH_DATE'];
pNHICVERIFIEDFLAG = json['P_NHIC_VERIFIED_FLAG']?.toString();
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{}; Map<String, dynamic> data = new Map<String, dynamic>();
data['CONTACT_NAME'] = cONTACTNAME; data['CONTACT_NAME'] = this.cONTACTNAME;
data['CONTACT_PERSON_ID'] = cONTACTPERSONID; data['CONTACT_PERSON_ID'] = this.cONTACTPERSONID;
data['CONTACT_RELATIONSHIP_ID'] = cONTACTRELATIONSHIPID; data['CONTACT_RELATIONSHIP_ID'] = this.cONTACTRELATIONSHIPID;
data['CONTACT_TYPE'] = cONTACTTYPE; data['CONTACT_TYPE'] = this.cONTACTTYPE;
data['DATE_OF_BIRTH'] = dATEOFBIRTH; data['DATE_OF_BIRTH'] = this.dATEOFBIRTH;
data['PERSON_ID'] = pERSONID; data['PERSON_ID'] = this.pERSONID;
data['PRIMARY_CONTACT_FLAG'] = pRIMARYCONTACTFLAG; data['PRIMARY_CONTACT_FLAG'] = this.pRIMARYCONTACTFLAG;
data['RELATIONSHIP'] = rELATIONSHIP; data['RELATIONSHIP'] = this.rELATIONSHIP;
data['MAZAYA_ACCESS_FLG'] = mAZAYAACCESSFLG;
data['MAZAYA_ACCESS'] = mazayaAccess;
data['NATIONAL_IDENTIFIER'] = nATIONALIDENTIFIER;
data['P_BIRTH_DATE'] = pBIRTHDATE;
data['P_NHIC_VERIFIED_FLAG'] = pNHICVERIFIEDFLAG;
return data; return data;
} }
} }

@ -1,6 +1,6 @@
class GetMobileLoginInfoListModel { class GetMobileLoginInfoListModel {
int? iD; int? iD;
dynamic? employeeID; //do not change this String? employeeID;
int? channelID; int? channelID;
int? companyID; int? companyID;
String? deviceType; String? deviceType;

@ -1,66 +0,0 @@
class WalaPlusCategoryModel {
String? id;
String? image;
String? imageThumb;
String? name;
String? parentId;
WalaPlusCategoryModel({
this.id,
this.image,
this.imageThumb,
this.name,
this.parentId,
}) {
if ((image == null || image!.isEmpty) && id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
image = Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
}
}
WalaPlusCategoryModel.fromJson(Map<String, dynamic> json) {
id = (json['id'] ?? json['Id'] ?? json['ID'])?.toString();
name = (json['name'] ?? json['Name'])?.toString();
parentId = (json['parent_id'] ?? json['Parent_Id'] ?? json['parentId'])?.toString();
imageThumb = (json['image_thumb'] ?? json['Image_Thumb'] ?? json['imageThumb'])?.toString();
// Use category image URL from HMG middleware using ID from WalaPlus_Categories API:
// https://epharmacy.hmg.com/middleware/images/category/{id}.png
if (id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
image = Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
} else {
image = (json['image'] ?? json['Image'])?.toString();
}
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['image'] = image;
data['image_thumb'] = imageThumb;
data['name'] = name;
data['parent_id'] = parentId;
return data;
}
bool get isMainCategory => parentId == null || parentId!.isEmpty;
bool get isSvg => (image ?? '').toLowerCase().contains('.svg');
String get categoryImageUrl {
if (id != null && id!.trim().isNotEmpty && id != "all" && id != "0") {
String cleanId = id!.trim();
if (cleanId.toLowerCase().endsWith('.png')) {
cleanId = cleanId.substring(0, cleanId.length - 4);
}
return Uri.encodeFull("https://epharmacy.hmg.com/middleware/images/category/$cleanId.png");
}
return image ?? '';
}
}

@ -1,842 +0,0 @@
import 'dart:convert';
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
class WalaPlusRetailersResponse {
int? page;
int? pagesCount;
int? perPage;
List<WalaPlusRetailerModel>? retailers;
WalaPlusRetailersResponse({this.page, this.pagesCount, this.perPage, this.retailers});
factory WalaPlusRetailersResponse.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerModel> retailersList = [];
dynamic retailersData = json['retailers'] ?? json['Retailers'] ?? json['data'] ?? json['items'];
if (retailersData is String) {
try {
retailersData = jsonDecode(retailersData);
} catch (_) {}
}
if (retailersData is List) {
retailersData.forEach((v) {
if (v is Map<String, dynamic>) {
retailersList.add(WalaPlusRetailerModel.fromJson(v));
} else if (v is Map) {
retailersList.add(WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(v)));
}
});
}
return WalaPlusRetailersResponse(
page: json['page'] is int ? json['page'] : int.tryParse(json['page']?.toString() ?? json['WP_Page']?.toString() ?? '1'),
pagesCount: json['pages_count'] is int ? json['pages_count'] : int.tryParse(json['pages_count']?.toString() ?? json['pagesCount']?.toString() ?? json['totalPages']?.toString() ?? '1'),
perPage: json['per_page'] is int ? json['per_page'] : int.tryParse(json['per_page']?.toString() ?? json['perPage']?.toString() ?? json['WP_Per_page']?.toString() ?? '10'),
retailers: retailersList,
);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['page'] = page;
data['pages_count'] = pagesCount;
data['per_page'] = perPage;
if (retailers != null) {
data['retailers'] = retailers!.map((v) => v.toJson()).toList();
}
return data;
}
}
class WalaPlusRetailersByIDResponse {
int? page;
int? pagesCount;
int? perPage;
List<WalaPlusRetailerLocation>? locations;
List<WalaPlusOfferModel>? offers;
List<WalaPlusRetailerModel>? retailer;
WalaPlusRetailersByIDResponse({this.page, this.pagesCount, this.perPage, this.locations, this.offers, this.retailer});
factory WalaPlusRetailersByIDResponse.fromJson(Map<String, dynamic> json) {
List<WalaPlusRetailerLocation> locList = WalaPlusRetailerModel.parseLocationsList(json['locations'] ?? json['Locations'] ?? json['location'] ?? json['Location']);
List<WalaPlusOfferModel> offList = WalaPlusRetailerModel.parseOffersList(json['offers'] ?? json['Offers']);
List<WalaPlusRetailerModel> retList = [];
dynamic retData = json['retailer'] ?? json['Retailer'];
if (retData is List) {
for (var r in retData) {
if (r is Map<String, dynamic>) {
var model = WalaPlusRetailerModel.fromJson(r);
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
retList.add(model);
} else if (r is Map) {
var model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(r));
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
retList.add(model);
}
}
} else if (retData is Map) {
var model = WalaPlusRetailerModel.fromJson(Map<String, dynamic>.from(retData));
if (model.locations == null || model.locations!.isEmpty) model.locations = locList;
if (model.offers == null || model.offers!.isEmpty) model.offers = offList;
retList.add(model);
}
return WalaPlusRetailersByIDResponse(
page: json['page'] is int ? json['page'] : int.tryParse(json['page']?.toString() ?? json['WP_Page']?.toString() ?? ''),
pagesCount: json['pages_count'] is int ? json['pages_count'] : int.tryParse(json['pages_count']?.toString() ?? json['pagesCount']?.toString() ?? ''),
perPage: json['per_page'] is int ? json['per_page'] : int.tryParse(json['per_page']?.toString() ?? json['perPage']?.toString() ?? ''),
locations: locList,
offers: offList,
retailer: retList,
);
}
WalaPlusRetailerModel? get primaryRetailer {
if (retailer != null && retailer!.isNotEmpty) {
return retailer!.first;
}
return null;
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['page'] = page;
data['pages_count'] = pagesCount;
data['per_page'] = perPage;
if (locations != null) {
data['locations'] = locations!.map((v) => v.toJson()).toList();
}
if (offers != null) {
data['offers'] = offers!.map((v) => v.toJson()).toList();
}
if (retailer != null) {
data['retailer'] = retailer!.map((v) => v.toJson()).toList();
}
return data;
}
}
class WalaPlusRetailerModel {
String? id;
String? name;
String? offerTitle;
String? offerType;
String? description;
String? info;
String? image;
String? imageThumb;
String? logo;
String? logoThumb;
String? website;
String? type;
dynamic usage;
dynamic weight;
dynamic geoId;
String? pin;
String? barcodeStandard;
String? arHowToUse;
String? enHowToUse;
String? howToUse;
WalaPlusRetailerContact? contact;
dynamic offerWeight;
List<WalaPlusOfferModel>? offers;
List<WalaPlusRetailerLocation>? locations;
List<WalaPlusCategoryModel>? categories;
String? validity;
String? validTo;
String? validUntil;
String? endDate;
String? expiryDate;
String? duration;
String? email;
String? phone;
WalaPlusRetailerModel({
this.id,
this.name,
this.offerTitle,
this.offerType,
this.description,
this.info,
this.image,
this.imageThumb,
this.logo,
this.logoThumb,
this.website,
this.type,
this.usage,
this.weight,
this.geoId,
this.pin,
this.barcodeStandard,
this.arHowToUse,
this.enHowToUse,
this.howToUse,
this.contact,
this.offerWeight,
this.offers,
this.locations,
this.categories,
this.validity,
this.validTo,
this.validUntil,
this.endDate,
this.expiryDate,
this.duration,
this.email,
this.phone,
});
static List<WalaPlusOfferModel> parseOffersList(data) {
List<WalaPlusOfferModel> list = [];
if (data == null) return list;
if (data is String) {
try {
data = jsonDecode(data);
} catch (_) {}
}
if (data is List) {
for (var v in data) {
if (v is Map<String, dynamic>) {
list.add(WalaPlusOfferModel.fromJson(v));
} else if (v is Map) {
list.add(WalaPlusOfferModel.fromJson(Map<String, dynamic>.from(v)));
}
}
}
return list;
}
static List<WalaPlusRetailerLocation> parseLocationsList(data) {
List<WalaPlusRetailerLocation> list = [];
if (data == null) return list;
if (data is String) {
try {
data = jsonDecode(data);
} catch (_) {}
}
if (data is List) {
for (var v in data) {
if (v is Map<String, dynamic>) {
list.add(WalaPlusRetailerLocation.fromJson(v));
} else if (v is Map) {
list.add(WalaPlusRetailerLocation.fromJson(Map<String, dynamic>.from(v)));
}
}
}
return list;
}
factory WalaPlusRetailerModel.fromJson(Map<String, dynamic> json) {
Map<String, dynamic> targetJson = json;
dynamic offersSource = json['offers'] ?? json['Offers'];
dynamic locationsSource = json['locations'] ?? json['Locations'] ?? json['location'] ?? json['Location'];
// If json is the wrapper object from WalaPlus_RetailersByID containing 'retailer' key
if (json['retailer'] != null || json['Retailer'] != null) {
dynamic retailerData = json['retailer'] ?? json['Retailer'];
if (retailerData is List && retailerData.isNotEmpty) {
var first = retailerData.first;
if (first is Map<String, dynamic>) {
targetJson = first;
} else if (first is Map) {
targetJson = Map<String, dynamic>.from(first);
}
} else if (retailerData is Map<String, dynamic>) {
targetJson = retailerData;
} else if (retailerData is Map) {
targetJson = Map<String, dynamic>.from(retailerData);
}
if (locationsSource == null || (locationsSource is List && locationsSource.isEmpty)) {
locationsSource = targetJson['locations'] ?? targetJson['Locations'] ?? targetJson['location'] ?? targetJson['Location'];
}
if (offersSource == null || (offersSource is List && offersSource.isEmpty)) {
offersSource = targetJson['offers'] ?? targetJson['Offers'];
}
}
List<WalaPlusOfferModel> offersList = parseOffersList(offersSource ?? targetJson['offers'] ?? targetJson['Offers']);
List<WalaPlusRetailerLocation> locationsList = parseLocationsList(locationsSource ?? targetJson['locations'] ?? targetJson['Locations'] ?? targetJson['location'] ?? targetJson['Location']);
List<WalaPlusCategoryModel> categoriesList = [];
if (targetJson['categories'] != null && targetJson['categories'] is List) {
targetJson['categories'].forEach((v) {
categoriesList.add(WalaPlusCategoryModel.fromJson(v));
});
}
WalaPlusRetailerContact? contactObj;
if (targetJson['contact'] != null) {
if (targetJson['contact'] is Map) {
contactObj = WalaPlusRetailerContact.fromJson(Map<String, dynamic>.from(targetJson['contact']));
} else if (targetJson['contact'] is String && targetJson['contact'].toString().trim().isNotEmpty) {
contactObj = WalaPlusRetailerContact(website: targetJson['contact'].toString());
}
}
String? foundValidity =
targetJson['validity']?.toString() ??
targetJson['valid_to']?.toString() ??
targetJson['valid_until']?.toString() ??
targetJson['end_date']?.toString() ??
targetJson['expiry_date']?.toString() ??
targetJson['expire_date']?.toString() ??
targetJson['duration']?.toString() ??
targetJson['validity_period']?.toString() ??
targetJson['valid_duration']?.toString() ??
targetJson['offer_validity']?.toString();
if (foundValidity == null || foundValidity == "null" || foundValidity.trim().isEmpty) {
for (var entry in targetJson.entries) {
String k = entry.key.toLowerCase();
if ((k.contains('valid') || k.contains('expir') || k.contains('duration')) && !k.contains('isverificationcodevalid') && !k.contains('barcode')) {
if (entry.value != null && entry.value.toString().trim().isNotEmpty && entry.value.toString() != "null") {
foundValidity = entry.value.toString().trim();
break;
}
}
}
}
return WalaPlusRetailerModel(
id:
targetJson['id']?.toString() ??
targetJson['WP_RetailerID']?.toString() ??
targetJson['retailer_id']?.toString() ??
targetJson['RetailerID']?.toString() ??
targetJson['retailerId']?.toString(),
name: decodeUnicodeString(targetJson['name']),
offerTitle: decodeUnicodeString(targetJson['offer_title']),
offerType: targetJson['offer_type']?.toString(),
description: decodeUnicodeString(targetJson['description']),
info: decodeUnicodeString(targetJson['info']),
image: targetJson['image']?.toString(),
imageThumb: targetJson['image_thumb']?.toString(),
logo: targetJson['logo']?.toString(),
logoThumb: targetJson['logo_thumb']?.toString(),
website: targetJson['website']?.toString(),
type: targetJson['type']?.toString(),
usage: targetJson['usage'] is String ? decodeUnicodeString(targetJson['usage']) : targetJson['usage'],
weight: targetJson['weight'],
geoId: targetJson['geo_id'],
pin: targetJson['pin']?.toString(),
barcodeStandard: targetJson['barcode_standard']?.toString(),
arHowToUse: decodeUnicodeString(targetJson['ar_how_to_use']),
enHowToUse: decodeUnicodeString(targetJson['en_how_to_use']),
howToUse: decodeUnicodeString(targetJson['how_to_use']),
contact: contactObj,
offerWeight: targetJson['offer_weight'],
offers: offersList,
locations: locationsList,
categories: categoriesList,
validity: foundValidity,
validTo: targetJson['valid_to']?.toString() ?? targetJson['validTo']?.toString(),
validUntil: targetJson['valid_until']?.toString() ?? targetJson['validUntil']?.toString(),
endDate: targetJson['end_date']?.toString() ?? targetJson['endDate']?.toString(),
expiryDate: targetJson['expiry_date']?.toString() ?? targetJson['expiryDate']?.toString(),
duration: targetJson['duration']?.toString(),
email: targetJson['email']?.toString() ?? targetJson['contact_email']?.toString() ?? contactObj?.primaryEmail,
phone: targetJson['phone']?.toString() ?? targetJson['contact_phone']?.toString() ?? targetJson['mobile']?.toString() ?? contactObj?.primaryPhone,
);
}
static String? decodeUnicodeString(Object? val) {
if (val == null) return null;
String s = val.toString().trim();
if (s.isEmpty || s == "null") return null;
while ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
if (s.length >= 2) {
s = s.substring(1, s.length - 1).trim();
} else {
break;
}
}
s = s.replaceAll(r'\/', '/').replaceAll(r'\"', '"').replaceAll(r"\'", "'");
s = s.replaceAllMapped(RegExp(r'\\u([0-9a-fA-F]{4})'), (match) {
try {
var codeUnit = int.parse(match.group(1)!, radix: 16);
return String.fromCharCode(codeUnit);
} catch (_) {
return match.group(0)!;
}
});
s = s
.replaceAll(r'\u201c', '“')
.replaceAll(r'\u201d', '”')
.replaceAll(r'\u2019', '’')
.replaceAll(r'\u2018', '‘')
.replaceAll(r'\u00a0', ' ')
.replaceAll(r'\u2022', '•')
.replaceAll(r'\u2013', '–')
.replaceAll(r'\u2014', '—')
.replaceAll(r'\u200f', '')
.replaceAll(r'\u200e', '')
.replaceAll(r'\u061f', '؟')
.replaceAll(r'\u201', '“')
.replaceAll('&quot;', '"')
.replaceAll('&apos;', "'")
.replaceAll('&#39;', "'")
.replaceAll('&nbsp;', ' ')
.replaceAll('&amp;', '&');
return s;
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['name'] = name;
data['offer_title'] = offerTitle;
data['offer_type'] = offerType;
data['description'] = description;
data['info'] = info;
data['image'] = image;
data['image_thumb'] = imageThumb;
data['logo'] = logo;
data['logo_thumb'] = logoThumb;
data['website'] = website;
data['type'] = type;
data['usage'] = usage;
data['weight'] = weight;
data['geo_id'] = geoId;
data['pin'] = pin;
data['barcode_standard'] = barcodeStandard;
data['ar_how_to_use'] = arHowToUse;
data['en_how_to_use'] = enHowToUse;
data['how_to_use'] = howToUse;
data['contact'] = contact?.toJson();
data['offer_weight'] = offerWeight;
data['validity'] = validity;
data['valid_to'] = validTo;
data['valid_until'] = validUntil;
data['end_date'] = endDate;
data['expiry_date'] = expiryDate;
data['duration'] = duration;
data['email'] = email;
data['phone'] = phone;
if (offers != null) {
data['offers'] = offers!.map((v) => v.toJson()).toList();
}
if (locations != null) {
data['locations'] = locations!.map((v) => v.toJson()).toList();
}
if (categories != null) {
data['categories'] = categories!.map((v) => v.toJson()).toList();
}
return data;
}
String? get displayImage {
if (image != null && image!.trim().isNotEmpty && image != "null") return image!.trim();
if (imageThumb != null && imageThumb!.trim().isNotEmpty && imageThumb != "null") return imageThumb!.trim();
return null;
}
String? get displayLogo {
if (logo != null && logo!.trim().isNotEmpty && logo != "null") return logo!.trim();
if (logoThumb != null && logoThumb!.trim().isNotEmpty && logoThumb != "null") return logoThumb!.trim();
return null;
}
String? get resolvedWebsite {
if (contact?.website != null && contact!.website!.trim().isNotEmpty && contact!.website != "null") {
return contact!.website!.trim();
}
if (website != null && website!.trim().isNotEmpty && website != "null") {
return website!.trim();
}
return null;
}
bool get hasPhysicalLocation {
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if ((loc.cityNameEn != null && loc.cityNameEn!.trim().isNotEmpty && loc.cityNameEn != "null") ||
(loc.cityNameAr != null && loc.cityNameAr!.trim().isNotEmpty && loc.cityNameAr != "null") ||
(loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null")) {
return true;
}
}
}
return false;
}
String getDisplayCity({bool isAr = false}) {
if (locations != null && locations!.isNotEmpty) {
// 1. First pass: look for specific city name
for (var loc in locations!) {
String? cName = isAr ? loc.cityNameAr : loc.cityNameEn;
if (cName != null && cName.trim().isNotEmpty && cName != "null") {
return cName.trim();
}
}
// 2. Second pass: fallback to city if not generic country
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
String c = loc.city!.trim();
if (c.toLowerCase() != 'saudi arabia' && c != 'المملكة العربية السعودية') {
return c;
}
}
}
// 3. Third pass: any non-empty city
for (var loc in locations!) {
if (loc.city != null && loc.city!.trim().isNotEmpty && loc.city != "null") {
return loc.city!.trim();
}
}
}
return '';
}
String get displayCity => getDisplayCity(isAr: false);
String get displayUsage {
if (usage != null) {
String u = usage.toString().trim();
if (int.tryParse(u) != null) {
return 'usage:$u';
}
if (!u.contains('<') && !u.contains('{') && u.isNotEmpty && u.length <= 12 && u.toLowerCase() != 'online' && u.toLowerCase() != 'store') {
return 'usage:$u';
}
}
return '';
}
String getDisplayValidity({bool isAr = false}) {
if (validity != null && validity!.trim().isNotEmpty && validity != "null") {
return validity!.trim();
}
if (validTo != null && validTo!.trim().isNotEmpty && validTo != "null") {
return validTo!.trim();
}
if (validUntil != null && validUntil!.trim().isNotEmpty && validUntil != "null") {
return validUntil!.trim();
}
if (endDate != null && endDate!.trim().isNotEmpty && endDate != "null") {
return endDate!.trim();
}
if (expiryDate != null && expiryDate!.trim().isNotEmpty && expiryDate != "null") {
return expiryDate!.trim();
}
if (duration != null && duration!.trim().isNotEmpty && duration != "null") {
return duration!.trim();
}
return LocaleKeys.offerValid.tr();
}
String? get resolvedEmail {
if (email != null && email!.trim().isNotEmpty && email != "null") {
return email!.trim();
}
if (contact?.primaryEmail != null && contact!.primaryEmail!.trim().isNotEmpty && contact!.primaryEmail != "null") {
return contact!.primaryEmail!.trim();
}
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if (loc.contact != null && loc.contact!.contains('@')) {
return loc.contact!.trim();
}
}
}
String combined = "${description ?? ''} ${info ?? ''} ${howToUse ?? ''} ${arHowToUse ?? ''} ${enHowToUse ?? ''}";
RegExp emailRegex = RegExp(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}');
Match? match = emailRegex.firstMatch(combined);
if (match != null) {
return match.group(0);
}
return null;
}
String? get resolvedPhone {
if (phone != null && phone!.trim().isNotEmpty && phone != "null") {
return phone!.trim();
}
if (contact?.primaryPhone != null && contact!.primaryPhone!.trim().isNotEmpty && contact!.primaryPhone != "null") {
return contact!.primaryPhone!.trim();
}
if (locations != null && locations!.isNotEmpty) {
for (var loc in locations!) {
if (loc.contact != null && loc.contact!.trim().isNotEmpty && loc.contact != "null" && !loc.contact!.contains('@')) {
return loc.contact!.trim();
}
}
}
String combined = "${description ?? ''} ${info ?? ''} ${howToUse ?? ''} ${arHowToUse ?? ''} ${enHowToUse ?? ''}";
RegExp phoneRegex = RegExp(r'(?:\+?966|0)?(?:5\d{8}|1\d{7}|9200\d{5}|800\d{6,7})');
Match? match = phoneRegex.firstMatch(combined);
if (match != null) {
return match.group(0);
}
return null;
}
bool get isOnlineStore => (type?.trim().toLowerCase() == 'online');
bool get isInstore =>
(type?.trim().toLowerCase() == 'instore' ||
type?.trim().toLowerCase() == 'in_store' ||
type?.trim().toLowerCase() == 'in store' ||
type?.trim().toLowerCase() == 'store' ||
(type == null && locations != null && locations!.isNotEmpty));
String? get normalizedType {
if (type != null && type!.trim().isNotEmpty) {
String t = type!.trim().toLowerCase();
if (t == 'online') return 'online';
if (t == 'instore' || t == 'in_store' || t == 'in store' || t == 'store') return 'instore';
return t;
}
if (locations != null && locations!.isNotEmpty) {
return 'instore';
}
return null;
}
String? getDisplayType() {
String? norm = normalizedType;
if (norm == 'online') return LocaleKeys.online.tr();
if (norm == 'instore') return LocaleKeys.inStore.tr();
if (type != null && type!.trim().isNotEmpty) {
return type!.trim();
}
return null;
}
}
class WalaPlusRetailerContact {
String? email;
List<String>? emails;
String? phone;
List<String>? phones;
String? website;
String? address;
String? instagram;
String? twitter;
WalaPlusRetailerContact({this.email, this.emails, this.phone, this.phones, this.website, this.address, this.instagram, this.twitter});
factory WalaPlusRetailerContact.fromJson(Map<String, dynamic> json) {
List<String> emailsList = [];
if (json['emails'] != null && json['emails'] is List) {
json['emails'].forEach((v) => emailsList.add(v.toString()));
}
String? singleEmail = json['email']?.toString() ?? json['email_address']?.toString();
if (singleEmail != null && singleEmail.trim().isNotEmpty && singleEmail != "null") {
if (!emailsList.contains(singleEmail.trim())) {
emailsList.add(singleEmail.trim());
}
}
List<String> phonesList = [];
if (json['phones'] != null && json['phones'] is List) {
json['phones'].forEach((v) => phonesList.add(v.toString()));
}
String? singlePhone = json['phone']?.toString() ?? json['mobile']?.toString() ?? json['phone_number']?.toString();
if (singlePhone != null && singlePhone.trim().isNotEmpty && singlePhone != "null") {
if (!phonesList.contains(singlePhone.trim())) {
phonesList.add(singlePhone.trim());
}
}
return WalaPlusRetailerContact(
email: singleEmail,
emails: emailsList,
phone: singlePhone,
phones: phonesList,
website: WalaPlusRetailerModel.decodeUnicodeString(json['website']),
address: WalaPlusRetailerModel.decodeUnicodeString(json['address']),
instagram: json['instagram']?.toString(),
twitter: json['twitter']?.toString(),
);
}
Map<String, dynamic> toJson() {
return {'email': email, 'emails': emails, 'phone': phone, 'phones': phones, 'website': website, 'address': address, 'instagram': instagram, 'twitter': twitter};
}
String? get primaryEmail {
if (email != null && email!.trim().isNotEmpty && email != "null") return email!.trim();
if (emails != null && emails!.isNotEmpty) {
for (var e in emails!) {
if (e.trim().isNotEmpty && e != "null") return e.trim();
}
}
return null;
}
String? get primaryPhone {
if (phone != null && phone!.trim().isNotEmpty && phone != "null") return phone!.trim();
if (phones != null && phones!.isNotEmpty) {
for (var p in phones!) {
if (p.trim().isNotEmpty && p != "null") return p.trim();
}
}
return null;
}
}
class WalaPlusRetailerLocation {
dynamic id;
String? address;
String? city;
String? cityNameAr;
String? cityNameEn;
String? contact;
String? countryNameAr;
String? countryNameEn;
String? distance;
double? latitude;
double? longitude;
dynamic placeId;
WalaPlusRetailerLocation({
this.id,
this.address,
this.city,
this.cityNameAr,
this.cityNameEn,
this.contact,
this.countryNameAr,
this.countryNameEn,
this.distance,
this.latitude,
this.longitude,
this.placeId,
});
factory WalaPlusRetailerLocation.fromJson(Map<String, dynamic> json) {
return WalaPlusRetailerLocation(
id: json['id'],
address: WalaPlusRetailerModel.decodeUnicodeString(json['address']),
city: WalaPlusRetailerModel.decodeUnicodeString(json['city']),
cityNameAr: WalaPlusRetailerModel.decodeUnicodeString(json['city_name_ar']),
cityNameEn: WalaPlusRetailerModel.decodeUnicodeString(json['city_name_en']),
contact: json['contact']?.toString(),
countryNameAr: WalaPlusRetailerModel.decodeUnicodeString(json['country_name_ar']),
countryNameEn: WalaPlusRetailerModel.decodeUnicodeString(json['country_name_en']),
distance: WalaPlusRetailerModel.decodeUnicodeString(json['distance']),
latitude: json['latitude'] is num ? (json['latitude'] as num).toDouble() : double.tryParse(json['latitude']?.toString() ?? ''),
longitude: json['longitude'] is num ? (json['longitude'] as num).toDouble() : double.tryParse(json['longitude']?.toString() ?? ''),
placeId: json['place_id'],
);
}
Map<String, dynamic> toJson() {
Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['address'] = address;
data['city'] = city;
data['city_name_ar'] = cityNameAr;
data['city_name_en'] = cityNameEn;
data['contact'] = contact;
data['country_name_ar'] = countryNameAr;
data['country_name_en'] = countryNameEn;
data['distance'] = distance;
data['latitude'] = latitude;
data['longitude'] = longitude;
data['place_id'] = placeId;
return data;
}
// distance comes from the API as a raw numeric string (e.g. "14.729670498669968") -
// round to max 2 decimal places and append the localized "KM Away" label.
String? getDisplayDistance() {
if (distance == null) return null;
double? km = double.tryParse(distance!.trim());
if (km == null) return null;
String formatted = km.toStringAsFixed(2);
return LocaleKeys.kmAway.tr(args: [formatted]);
}
String getLocalizedCity(bool isAr) {
if (isAr) {
if (cityNameAr != null && cityNameAr!.trim().isNotEmpty && cityNameAr != "null") {
return cityNameAr!.trim();
}
} else {
if (cityNameEn != null && cityNameEn!.trim().isNotEmpty && cityNameEn != "null") {
return cityNameEn!.trim();
}
}
if (city != null && city!.trim().isNotEmpty && city != "null") {
return city!.trim();
}
return isAr ? (cityNameAr ?? "") : (cityNameEn ?? "");
}
String getLocalizedCountry(bool isAr) {
if (isAr) {
if (countryNameAr != null && countryNameAr!.trim().isNotEmpty && countryNameAr != "null") {
return countryNameAr!.trim();
}
} else {
if (countryNameEn != null && countryNameEn!.trim().isNotEmpty && countryNameEn != "null") {
return countryNameEn!.trim();
}
}
return "";
}
}
class WalaPlusOfferModel {
String? id;
String? retailerId;
String? title;
String? image;
String? imageThumb;
String? type;
String? description;
dynamic price;
dynamic usage;
String? expiredAt;
WalaPlusOfferModel({this.id, this.retailerId, this.title, this.image, this.imageThumb, this.type, this.description, this.price, this.usage, this.expiredAt});
factory WalaPlusOfferModel.fromJson(Map<String, dynamic> json) {
return WalaPlusOfferModel(
id: json['id']?.toString(),
retailerId: (json['retailer_id'] ?? json['retailerId'] ?? json['retailerID'])?.toString(),
title: WalaPlusRetailerModel.decodeUnicodeString(json['title']),
image: json['image']?.toString(),
imageThumb: (json['image_thumb'] ?? json['imageThumb'])?.toString(),
type: json['type']?.toString(),
description: WalaPlusRetailerModel.decodeUnicodeString(json['description']),
price: json['price'],
usage: json['usage'],
expiredAt: (json['expired_at'] ?? json['expiredAt'] ?? json['expiry_date'])?.toString(),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'retailer_id': retailerId,
'title': title,
'image': image,
'image_thumb': imageThumb,
'type': type,
'description': description,
'price': price,
'usage': usage,
'expired_at': expiredAt,
};
}
String? get displayImage {
if (image != null && image!.trim().isNotEmpty && image != "null") return image!.trim();
if (imageThumb != null && imageThumb!.trim().isNotEmpty && imageThumb != "null") return imageThumb!.trim();
return null;
}
}

@ -1,3 +1,5 @@
import 'package:mohem_flutter_app/app_state/app_state.dart';
class PostParamsModel { class PostParamsModel {
double? versionID; double? versionID;
int? channel; int? channel;
@ -13,7 +15,6 @@ class PostParamsModel {
String? payrollCodeStr; String? payrollCodeStr;
int? pSessionId; int? pSessionId;
String? userName; String? userName;
String? sMSSignature;
PostParamsModel({ PostParamsModel({
this.versionID, this.versionID,
@ -30,7 +31,6 @@ class PostParamsModel {
this.pSelectedEmployeeNumber, this.pSelectedEmployeeNumber,
this.payrollCodeStr, this.payrollCodeStr,
this.pLegislationCode, this.pLegislationCode,
this.sMSSignature,
}); });
PostParamsModel.fromJson(Map<String, dynamic> json) { PostParamsModel.fromJson(Map<String, dynamic> json) {
@ -40,7 +40,7 @@ class PostParamsModel {
mobileType = json['MobileType']; mobileType = json['MobileType'];
logInTokenID = json['LogInTokenID']; logInTokenID = json['LogInTokenID'];
payrollCodeStr = json['PayrollCodeStr']; payrollCodeStr = json['PayrollCodeStr'];
pLegislationCode = json['LegislationCodeStr']?.toString().trim().toUpperCase(); pLegislationCode = json['LegislationCodeStr'];
tokenID = json['TokenID']; tokenID = json['TokenID'];
} }
@ -92,9 +92,7 @@ class PostParamsModel {
set setPSelectedEmployeeNumber(String? v) => pSelectedEmployeeNumber = v; set setPSelectedEmployeeNumber(String? v) => pSelectedEmployeeNumber = v;
set setPLegislationCode(String? v) => pLegislationCode = v?.trim().toUpperCase(); set setPLegislationCode(String? v) => pLegislationCode = v;
set setPayrollCodeStr(String? v) => payrollCodeStr = v; set setPayrollCodeStr(String? v) => payrollCodeStr = v;
set setSMSSignature(String? v) => sMSSignature = v;
set setSmsSignature(String? v) => sMSSignature = v;
} }

@ -1,139 +0,0 @@
class PatientInfoModel {
int? age;
String? dateOfBirth;
String? firstNameAr;
String? firstNameEn;
String? secondNameAr;
String? secondNameEn;
String? thirdNameAr;
String? thirdNameEn;
String? lastNameAr;
String? lastNameEn;
String? gender;
String? healthId;
String? idNumber;
String? idType;
bool? isHijri;
String? maritalStatus;
String? maritalStatusCode;
String? nationalDateOfBirth;
String? nationality;
String? nationalityCode;
String? occupation;
String? patientStatus;
String? placeOfBirth;
int? messageStatus;
String? errorEndUserMessage;
String? errorMessage;
PatientInfoModel({
this.age,
this.dateOfBirth,
this.firstNameAr,
this.firstNameEn,
this.secondNameAr,
this.secondNameEn,
this.thirdNameAr,
this.thirdNameEn,
this.lastNameAr,
this.lastNameEn,
this.gender,
this.healthId,
this.idNumber,
this.idType,
this.isHijri,
this.maritalStatus,
this.maritalStatusCode,
this.nationalDateOfBirth,
this.nationality,
this.nationalityCode,
this.occupation,
this.patientStatus,
this.placeOfBirth,
this.messageStatus,
this.errorEndUserMessage,
this.errorMessage,
});
factory PatientInfoModel.fromJson(Map<String, dynamic> json) {
return PatientInfoModel(
age: json['Age'],
dateOfBirth: json['DateOfBirth'],
firstNameAr: json['FirstNameAr'],
firstNameEn: json['FirstNameEn'],
secondNameAr: json['SecondNameAr'],
secondNameEn: json['SecondNameEn'],
thirdNameAr: json['ThirdNameAr'],
thirdNameEn: json['ThirdNameEn'],
lastNameAr: json['LastNameAr'],
lastNameEn: json['LastNameEn'],
gender: json['Gender'],
healthId: json['HealthId'],
idNumber: json['IdNumber'],
idType: json['IdType'],
isHijri: json['IsHijri'],
maritalStatus: json['MaritalStatus'],
maritalStatusCode: json['MaritalStatusCode'],
nationalDateOfBirth: json['NationalDateOfBirth'],
nationality: json['Nationality'],
nationalityCode: json['NationalityCode'],
occupation: json['Occupation'],
patientStatus: json['PatientStatus']?.toString(),
placeOfBirth: json['PlaceofBirth'],
messageStatus: json['MessageStatus'],
errorEndUserMessage: json['ErrorEndUserMessage'],
errorMessage: json['ErrorMessage'],
);
}
Map<String, dynamic> toJson() {
return {
'Age': age,
'DateOfBirth': dateOfBirth,
'FirstNameAr': firstNameAr,
'FirstNameEn': firstNameEn,
'SecondNameAr': secondNameAr,
'SecondNameEn': secondNameEn,
'ThirdNameAr': thirdNameAr,
'ThirdNameEn': thirdNameEn,
'LastNameAr': lastNameAr,
'LastNameEn': lastNameEn,
'Gender': gender,
'HealthId': healthId,
'IdNumber': idNumber,
'IdType': idType,
'IsHijri': isHijri,
'MaritalStatus': maritalStatus,
'MaritalStatusCode': maritalStatusCode,
'NationalDateOfBirth': nationalDateOfBirth,
'Nationality': nationality,
'NationalityCode': nationalityCode,
'Occupation': occupation,
'PatientStatus': patientStatus,
'PlaceofBirth': placeOfBirth,
'MessageStatus': messageStatus,
'ErrorEndUserMessage': errorEndUserMessage,
'ErrorMessage': errorMessage,
};
}
String get fullNameAr {
List<String> parts = [
firstNameAr ?? '',
secondNameAr ?? '',
(thirdNameAr != null && thirdNameAr != '-') ? thirdNameAr! : '',
lastNameAr ?? '',
].where((s) => s.trim().isNotEmpty && s != '-').toList();
return parts.join(' ').trim();
}
String get fullNameEn {
List<String> parts = [
firstNameEn ?? '',
secondNameEn ?? '',
(thirdNameEn != null && thirdNameEn != '-') ? thirdNameEn! : '',
lastNameEn ?? '',
].where((s) => s.trim().isNotEmpty && s != '-').toList();
return parts.join(' ').trim();
}
}

@ -17,12 +17,12 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin {
MediaStream? _remoteStream; MediaStream? _remoteStream;
void initCallListeners() { void initCallListeners() {
chatHubConnection?.on("OnCallAcceptedAsync", onCallAcceptedAsync); chatHubConnection.on("OnCallAcceptedAsync", onCallAcceptedAsync);
chatHubConnection?.on("OnIceCandidateAsync", onIceCandidateAsync); chatHubConnection.on("OnIceCandidateAsync", onIceCandidateAsync);
chatHubConnection?.on("OnOfferAsync", onOfferAsync); chatHubConnection.on("OnOfferAsync", onOfferAsync);
chatHubConnection?.on("OnAnswerOffer", onAnswerOffer); chatHubConnection.on("OnAnswerOffer", onAnswerOffer);
chatHubConnection?.on("OnHangUpAsync", onHangUpAsync); chatHubConnection.on("OnHangUpAsync", onHangUpAsync);
chatHubConnection?.on("OnCallDeclinedAsync", onCallDeclinedAsync); chatHubConnection.on("OnCallDeclinedAsync", onCallDeclinedAsync);
} }
//Video Constraints //Video Constraints
@ -175,13 +175,13 @@ class ChatCallProvider with ChangeNotifier, DiagnosticableTreeMixin {
args = [targetUserID, data]; args = [targetUserID, data];
//json In Data //json In Data
} }
await chatHubConnection?.invoke(invokeMethod, args: args); await chatHubConnection.invoke(invokeMethod, args: args);
} }
void stopListeners() async { void stopListeners() async {
chatHubConnection?.off('OnCallDeclinedAsync'); chatHubConnection.off('OnCallDeclinedAsync');
chatHubConnection?.off('OnCallAcceptedAsync'); chatHubConnection.off('OnCallAcceptedAsync');
chatHubConnection?.off('OnIceCandidateAsync'); chatHubConnection.off('OnIceCandidateAsync');
chatHubConnection?.off('OnAnswerOffer'); chatHubConnection.off('OnAnswerOffer');
} }
} }

@ -113,16 +113,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
Future<void> buildHubConnection() async { Future<void> buildHubConnection() async {
chatHubConnection = await getHubConnection(); chatHubConnection = await getHubConnection();
await chatHubConnection?.start(); await chatHubConnection.start();
if (kDebugMode) { if (kDebugMode) {
logger.i("Hub Conn: Startedddddddd"); logger.i("Hub Conn: Startedddddddd");
} }
chatHubConnection?.on("OnDeliveredChatUserAsync", onMsgReceived); chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection?.on("OnGetChatConversationCount", onNewChatConversion); chatHubConnection.on("OnGetChatConversationCount", onNewChatConversion);
//group On message //group On message
chatHubConnection?.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived); chatHubConnection.on("OnDeliveredGroupChatHistoryAsync", onGroupMsgReceived);
} }
Future<HubConnection> getHubConnection() async { Future<HubConnection> getHubConnection() async {
@ -135,16 +135,16 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
} }
void registerEvents() { void registerEvents() {
chatHubConnection?.on("OnUpdateUserStatusAsync", changeStatus); chatHubConnection.on("OnUpdateUserStatusAsync", changeStatus);
// chatHubConnection?.on("OnDeliveredChatUserAsync", onMsgReceived); // chatHubConnection.on("OnDeliveredChatUserAsync", onMsgReceived);
chatHubConnection?.on("OnSubmitChatAsync", OnSubmitChatAsync); chatHubConnection.on("OnSubmitChatAsync", OnSubmitChatAsync);
chatHubConnection?.on("OnUserTypingAsync", onUserTyping); chatHubConnection.on("OnUserTypingAsync", onUserTyping);
chatHubConnection?.on("OnUserCountAsync", userCountAsync); chatHubConnection.on("OnUserCountAsync", userCountAsync);
// chatHubConnection?.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow); // chatHubConnection.on("OnUpdateUserChatHistoryWindowsAsync", updateChatHistoryWindow);
chatHubConnection?.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered); chatHubConnection.on("OnGetUserChatHistoryNotDeliveredAsync", chatNotDelivered);
chatHubConnection?.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus); chatHubConnection.on("OnUpdateUserChatHistoryStatusAsync", updateUserChatStatus);
chatHubConnection?.on("OnGetGroupUserStatusAsync", getGroupUserStatus); chatHubConnection.on("OnGetGroupUserStatusAsync", getGroupUserStatus);
// //
// {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]} // {"type":1,"target":"","arguments":[[{"id":217869,"userName":"Sultan.Khan","email":"Sultan.Khan@cloudsolutions.com.sa","phone":null,"title":"Sultan.Khan","userStatus":1,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":false,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null},{"id":15153,"userName":"Tamer.Fanasheh","email":"Tamer.F@cloudsolutions.com.sa","phone":null,"title":"Tamer Fanasheh","userStatus":2,"image":null,"unreadMessageCount":0,"userAction":3,"isPin":false,"isFav":false,"isAdmin":true,"rKey":null,"totalCount":0,"isHuaweiDevice":false,"deviceToken":null}]]}
@ -183,7 +183,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
} }
Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async { Future invokeUserChatHistoryNotDeliveredAsync({required int userId}) async {
await chatHubConnection?.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]); await chatHubConnection.invoke("GetUserChatHistoryNotDeliveredAsync", args: [userId]);
return ""; return "";
} }
@ -252,7 +252,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void updateUserChatHistoryStatusAsync(List data) { void updateUserChatHistoryStatusAsync(List data) {
try { try {
chatHubConnection?.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); chatHubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
} catch (e) { } catch (e) {
throw e; throw e;
} }
@ -260,7 +260,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
void updateUserChatHistoryOnMsg(List data) { void updateUserChatHistoryOnMsg(List data) {
try { try {
chatHubConnection?.invoke("UpdateUserChatHistoryStatusAsync", args: [data]); chatHubConnection.invoke("UpdateUserChatHistoryStatusAsync", args: [data]);
} catch (e) { } catch (e) {
throw e; throw e;
} }
@ -753,7 +753,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String chatData = String chatData =
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}'; '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId": $fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"userChatHistoryLineRequestList":[{"isSeen":false,"isDelivered":false,"targetUserId":$targetUserId,"targetUserStatus":1}],"chatReplyId":$chatReplyId,"conversationId":"$chatCID"}';
await chatHubConnection?.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]); await chatHubConnection.invoke("AddChatUserAsync", args: <Object>[json.decode(chatData)]);
} }
//groupChatMessage //groupChatMessage
@ -829,7 +829,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
String chatData = String chatData =
'{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId":$fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"groupId":$targetGroupId,"groupChatHistoryLineRequestList":${json.encode(targetUsers)},"chatReplyId": $chatReplyId,"conversationId":"${uuid.v4()}"}'; '{"contant":"$msg","contantNo":"$contentNo","chatEventId":$chatEventId,"fileTypeId":$fileTypeId,"currentUserId":${AppState().chatDetails!.response!.id},"chatSource":1,"groupId":$targetGroupId,"groupChatHistoryLineRequestList":${json.encode(targetUsers)},"chatReplyId": $chatReplyId,"conversationId":"${uuid.v4()}"}';
await chatHubConnection?.invoke("AddGroupChatHistoryAsync", args: <Object>[json.decode(chatData)]); await chatHubConnection.invoke("AddGroupChatHistoryAsync", args: <Object>[json.decode(chatData)]);
} }
void sendGroupChatMessage( void sendGroupChatMessage(
@ -1450,14 +1450,7 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
pChatHistory?.clear(); pChatHistory?.clear();
uGroups?.clear(); uGroups?.clear();
searchGroup?.clear(); searchGroup?.clear();
try { chatHubConnection.stop();
chatHubConnection?.stop();
} catch (e) {
if (kDebugMode) {
print("Error stopping chatHubConnection: $e");
}
}
chatHubConnection = null;
AppState().chatDetails = null; AppState().chatDetails = null;
} }
} }
@ -1589,17 +1582,17 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
} }
Future invokeChatCounter({required int userId}) async { Future invokeChatCounter({required int userId}) async {
await chatHubConnection?.invoke("GetChatCounversationCount", args: [userId]); await chatHubConnection.invoke("GetChatCounversationCount", args: [userId]);
return ""; return "";
} }
void userTypingInvoke({required int currentUser, required int reciptUser}) async { void userTypingInvoke({required int currentUser, required int reciptUser}) async {
await chatHubConnection?.invoke("UserTypingAsync", args: [reciptUser, currentUser]); await chatHubConnection.invoke("UserTypingAsync", args: [reciptUser, currentUser]);
} }
void groupTypingInvoke({required GroupResponse groupDetails, required int groupId}) async { void groupTypingInvoke({required GroupResponse groupDetails, required int groupId}) async {
var data = json.decode(json.encode(groupDetails.groupUserList)); var data = json.decode(json.encode(groupDetails.groupUserList));
await chatHubConnection?.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]); await chatHubConnection.invoke("GroupTypingAsync", args: ["${groupDetails.adminUser!.userName}", data, groupId]);
} }
//////// Audio Recoding Work //////////////////// //////// Audio Recoding Work ////////////////////
@ -1616,13 +1609,13 @@ class ChatProviderModel with ChangeNotifier, DiagnosticableTreeMixin {
await File('$dirPath/.nomedia').create(); await File('$dirPath/.nomedia').create();
} }
path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac"; path = "$dirPath/${AppState().chatDetails!.response!.id}-$receiverID-${DateTime.now().microsecondsSinceEpoch}.aac";
recorderController = RecorderController() recorderController = RecorderController();
..androidEncoder = AndroidEncoder.aac // ..androidEncoder = AndroidEncoder.aac
..androidOutputFormat = AndroidOutputFormat.mpeg4 // ..androidOutputFormat = AndroidOutputFormat.mpeg4
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC // ..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
..sampleRate = 6000 // ..sampleRate = 6000
..updateFrequency = const Duration(milliseconds: 100) // ..updateFrequency = const Duration(milliseconds: 100)
..bitRate = 18000; // ..bitRate = 18000;
playerController = PlayerController(); playerController = PlayerController();
} }

@ -216,7 +216,7 @@ class _AddVacationRuleScreenState extends State<AddVacationRuleScreen> {
CreateVacationRuleList? createVacationRuleList = await VacationRuleApiClient().createVacationRule(DateUtil.convertDateToStringLocation(startTime), DateUtil.convertDateToStringLocation(endTime!), CreateVacationRuleList? createVacationRuleList = await VacationRuleApiClient().createVacationRule(DateUtil.convertDateToStringLocation(startTime), DateUtil.convertDateToStringLocation(endTime!),
selectedItemType!.iTEMTYPE!, selectedItemTypeNotification!.nOTIFICATIONNAME!, message, getPAction(), selectedReplacementEmployee!.userName!, respondAttributeList); selectedItemType!.iTEMTYPE!, selectedItemTypeNotification!.nOTIFICATIONNAME!, message, getPAction(), selectedReplacementEmployee!.userName!, respondAttributeList);
Utils.hideLoading(context); Utils.hideLoading(context);
Utils.showToast(LocaleKeys.vacationRuleAdded.tr()); Utils.showToast("Vacation rule added");
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)); Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
} catch (ex) { } catch (ex) {
Utils.hideLoading(context); Utils.hideLoading(context);

@ -33,7 +33,7 @@ class _ChatHomeState extends State<ChatHome> {
void initState() { void initState() {
super.initState(); super.initState();
if (chatHubConnection?.state == HubConnectionState.Connected) { if (chatHubConnection.state == HubConnectionState.Connected) {
data = Provider.of<ChatProviderModel>(context, listen: false); data = Provider.of<ChatProviderModel>(context, listen: false);
data.registerEvents(); data.registerEvents();
} }
@ -46,7 +46,7 @@ class _ChatHomeState extends State<ChatHome> {
} }
void fetchAgain() { void fetchAgain() {
if (chatHubConnection?.state != HubConnectionState.Connected) { if (chatHubConnection.state != HubConnectionState.Connected) {
data.getUserAutoLoginToken().whenComplete(() async { data.getUserAutoLoginToken().whenComplete(() async {
await data.buildHubConnection(); await data.buildHubConnection();
data.getUserRecentChats(); data.getUserRecentChats();

@ -0,0 +1,499 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/svg.dart';
import 'package:mohem_flutter_app/api/dashboard_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_to_text.dart';
class MessagesHolder{
static List<ChatMessage> messages = [];
}
enum ChatMode { home, chatActive }
class ChatMessage {
final String text;
final bool isUser;
final DateTime timestamp;
ChatMessage({required this.text, required this.isUser, required this.timestamp});
}
class ChatBotScreen extends StatefulWidget {
const ChatBotScreen({Key? key}) : super(key: key);
@override
State<ChatBotScreen> createState() => _ChatBotScreenState();
}
class _ChatBotScreenState extends State<ChatBotScreen> {
ChatMode _currentMode = ChatMode.home;
final List<ChatMessage> _messages = <ChatMessage>[];
final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController();
AtlasInitiateChatResponse? atlasChatTokenResponse;
bool _isThinking = false;
// Speech to text
final SpeechToText _speechToText = SpeechToText();
bool _speechEnabled = false;
@override
void initState() {
// TODO: implement initState
super.initState();
getChatSession();
_initSpeech();
}
/// Initialize speech to text
void _initSpeech() async {
_speechEnabled = await _speechToText.initialize();
if(MessagesHolder.messages.isNotEmpty){
setState(() {
_messages.addAll(MessagesHolder.messages);
_currentMode = ChatMode.chatActive;
});
}
setState(() {});
}
/// Start listening for speech
void _startListening() async {
await _speechToText.listen(
onResult: _onSpeechResult,
localeId: AppState().isArabic(context) ? 'ar_SA' : 'en_US',
);
setState(() {});
}
/// Stop listening for speech
void _stopListening() async {
await _speechToText.stop();
setState(() {});
}
/// Callback when speech is recognized
void _onSpeechResult(SpeechRecognitionResult result) {
setState(() {
_messageController.text = result.recognizedWords;
// Move cursor to end of text
_messageController.selection = TextSelection.fromPosition(TextPosition(offset: _messageController.text.length));
});
}
void getChatSession() async {
try {
GenericResponseModel? res = await DashboardApiClient().getChatBotSession();
if (res != null) {
setState(() {
atlasChatTokenResponse = res.atlasInitiateChatResponse;
});
}
} catch (ex) {
Utils.handleException(ex, context, null);
}
}
@override
void dispose() {
atlasChatTokenResponse = null;
_messageController.dispose();
_scrollController.dispose();
super.dispose();
}
void _sendMessage({String? suggestionText}) async {
String text = suggestionText ?? _messageController.text.trim();
if (text.isEmpty || atlasChatTokenResponse == null) return;
try {
setState(() {
_messages.add(ChatMessage(text: text, isUser: true, timestamp: DateTime.now()));
_currentMode = ChatMode.chatActive;
_messageController.clear();
_isThinking = true;
});
_scrollToBottom();
GenericResponseModel? res = await DashboardApiClient().sendChatBotMessage(atlasChatTokenResponse: atlasChatTokenResponse!, atlasChatText: text);
if (res != null) {
res.atlasContinueChatResponseDetails;
if (mounted) {
setState(() {
_isThinking = false;
_messages.add(
ChatMessage(
text: res.atlasContinueChatResponseDetails!.aiResponse!.structuredData!.answer ?? "",
isUser: false,
timestamp: res.atlasContinueChatResponseDetails!.aiResponse!.createdAt ?? DateTime.now(),
),
);
});
}
_scrollToBottom();
}
} catch (ex) {
setState(() {
_isThinking = false;
});
Utils.handleException(ex, context, null);
}
_scrollToBottom();
}
void _scrollToBottom() {
Future<void>.delayed(const Duration(milliseconds: 100), () {
if (_scrollController.hasClients) {
_scrollController.animateTo(_scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
}
});
}
void _sendSuggestion(String text) {
if (atlasChatTokenResponse == null) {
Utils.showToast(LocaleKeys.initializingChatPleaseWait.tr());
return;
}
_sendMessage(suggestionText: text);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyColors.backgroundColor,
appBar: AppBar(
automaticallyImplyLeading: false,
title: Text(LocaleKeys.chatBot.tr(), style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2B353E))),
backgroundColor: MyColors.backgroundColor,
centerTitle: false,
actions: [
IconButton(
icon: const Icon(Icons.minimize, color: Color(0xFF2B353E)),
onPressed: () {
MessagesHolder.messages = _messages;
Navigator.of(context).pop();
},
),
IconButton(
icon: const Icon(Icons.close, color: Color(0xFF2B353E)),
onPressed: () {
MessagesHolder.messages.clear();
Navigator.of(context).pop();
},
),
],
),
body: Column(children: <Widget>[Expanded(child: _currentMode == ChatMode.home ? _buildHomeMode() : _buildChatMode()), _buildBottomInputBar()]),
);
}
Widget _buildHomeMode() {
return Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(LocaleKeys.helloAskMe.tr(), textAlign: TextAlign.center, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2E303A))),
Text(LocaleKeys.anything.tr(), textAlign: TextAlign.center, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700, color: Color(0xFF2E303A))),
const SizedBox(height: 50),
const Wrap(
alignment: WrapAlignment.center,
spacing: 8,
runSpacing: 12,
children: <Widget>[
// _buildSuggestionChip("Nostalgia Perfume", () => _sendSuggestion("Nostalgia Perfume")),
// _buildSuggestionChip("Al Nafoura", () => _sendSuggestion("Al Nafoura")),
// _buildSuggestionChip("Al Nafoura", () => _sendSuggestion("Al Nafoura")),
// _buildSuggestionChip("Al Jadi", () => _sendSuggestion("Al Jadi")),
],
),
],
),
),
);
}
Widget _buildChatMode() {
return ListView.separated(
controller: _scrollController,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
itemCount: _messages.length + (_isThinking ? 1 : 0),
itemBuilder: (BuildContext context, int index) {
if (index == _messages.length && _isThinking) {
return _buildThinkingIndicator();
}
ChatMessage message = _messages[index];
return _buildMessageBubble(message);
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(height: 15);
},
);
}
Widget _buildThinkingIndicator() {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
gradient: const LinearGradient(colors: <Color>[Color(0xFF32D892), Color(0xFF259CB8)], begin: Alignment.centerLeft, end: Alignment.centerRight),
borderRadius: BorderRadius.circular(15),
boxShadow: <BoxShadow>[BoxShadow(color: const Color(0xFF000000).withValues(alpha: 0.0784), offset: const Offset(0, 0), blurRadius: 13, spreadRadius: 0)],
),
child: const ThinkingDotsAnimation(),
),
const Spacer(flex: 2),
],
);
}
Widget _buildMessageBubble(ChatMessage message) {
bool isUser = message.isUser;
Widget bubbleContent = Row(
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (isUser) const Spacer(flex: 2),
Flexible(
flex: 7,
child: Column(
crossAxisAlignment: isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: isUser ? Colors.white : null,
gradient: isUser ? null : const LinearGradient(colors: <Color>[Color(0xFF32D892), Color(0xFF259CB8)], begin: Alignment.centerLeft, end: Alignment.centerRight),
borderRadius: BorderRadius.circular(15),
boxShadow: isUser ? null : <BoxShadow>[BoxShadow(color: const Color(0xFF000000).withValues(alpha: 0.0784), offset: const Offset(0, 0), blurRadius: 13, spreadRadius: 0)],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(message.text, softWrap: true, style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: isUser ? const Color(0xFF2B353E) : MyColors.white)),
Align(
alignment: Alignment.centerRight,
child: Text(
_formatTimestamp(message.timestamp),
style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, letterSpacing: -0.4, color: isUser ? const Color(0xFF2B353E) : MyColors.whiteColor),
),
),
],
),
),
],
),
),
if (!isUser) const Spacer(flex: 2),
],
);
if (!isUser) {
return AnimatedMessageBubble(child: bubbleContent);
}
return bubbleContent;
}
String _formatTimestamp(DateTime timestamp) {
int hourInt = timestamp.hour;
int minute = timestamp.minute;
String period = hourInt >= 12 ? LocaleKeys.pm.tr() : LocaleKeys.am.tr();
int formattedHour = hourInt % 12 == 0 ? 12 : hourInt % 12;
String hour = formattedHour.toString().padLeft(2, '0');
String min = minute.toString().padLeft(2, '0');
String month = timestamp.month.toString().padLeft(2, '0');
String day = timestamp.day.toString().padLeft(2, '0');
int year = timestamp.year;
return "$month/$day/$year $hour:$min $period";
}
Widget _buildSuggestionChip(String label, VoidCallback onTap) {
return Material(
color: Colors.white,
elevation: 3,
shadowColor: const Color(0xFF000015).withValues(alpha: 0.0784),
borderRadius: BorderRadius.circular(10),
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: MyColors.darkTextColor, letterSpacing: 0.2)),
),
),
);
}
Widget _buildBottomInputBar() {
return Container(
decoration: const BoxDecoration(color: MyColors.backgroundColor),
child: SafeArea(
top: false,
child: Row(
children: <Widget>[
// Plus button
// IconButton(padding: EdgeInsets.zero, icon: const Icon(Icons.add, color: MyColors.darkTextColor, size: 34), onPressed: () {}),
// const SizedBox(width: 12),
// Text field
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
height: 48,
decoration: BoxDecoration(color: Colors.transparent, borderRadius: BorderRadius.circular(10), border: BoxBorder.all(width: 1, color: const Color(0xFFE5E5E5))),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _messageController,
onSubmitted: _isThinking ? null : (_) => _sendMessage(),
enabled: !_isThinking,
maxLines: 1,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: _isThinking ? LocaleKeys.waitingForResponse.tr() : LocaleKeys.typeAMessage.tr(),
hintStyle: const TextStyle(color: MyColors.hintTextColor, fontSize: 14, fontWeight: FontWeight.normal),
border: InputBorder.none,
isDense: true,
contentPadding: const EdgeInsets.symmetric(vertical: 10),
),
),
),
GestureDetector(
onTap: _isThinking ? null : (_speechEnabled ? (_speechToText.isNotListening ? _startListening : _stopListening) : null),
child: SvgPicture.asset(
"assets/icons/microphone.svg",
colorFilter: ColorFilter.mode(_isThinking ? MyColors.hintTextColor : (_speechToText.isListening ? Colors.red : MyColors.darkTextColor), BlendMode.srcIn),
),
),
],
),
),
),
const SizedBox(width: 6),
// Send button
GestureDetector(
onTap: _isThinking ? null : _sendMessage,
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(color: _isThinking ? MyColors.hintTextColor.withValues(alpha: 0.3) : MyColors.white, shape: BoxShape.circle),
child: Center(
child: SvgPicture.asset(
"assets/icons/send.svg",
matchTextDirection: true,
colorFilter: _isThinking ? const ColorFilter.mode(MyColors.hintTextColor, BlendMode.srcIn) : null,
),
),
),
),
],
),
),
).paddingOnly(left: 16, right: 16, bottom: 16);
}
}
class ThinkingDotsAnimation extends StatefulWidget {
const ThinkingDotsAnimation({Key? key}) : super(key: key);
@override
State<ThinkingDotsAnimation> createState() => _ThinkingDotsAnimationState();
}
class _ThinkingDotsAnimationState extends State<ThinkingDotsAnimation> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(LocaleKeys.thinking.tr(), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white)),
const SizedBox(width: 2),
AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget? child) {
int dotCount = (_controller.value * 4).floor() % 4;
String dots = '.' * dotCount;
return SizedBox(width: 18, child: Text(dots, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white)));
},
),
],
);
}
}
class AnimatedMessageBubble extends StatefulWidget {
final Widget child;
const AnimatedMessageBubble({Key? key, required this.child}) : super(key: key);
@override
State<AnimatedMessageBubble> createState() => _AnimatedMessageBubbleState();
}
class _AnimatedMessageBubbleState extends State<AnimatedMessageBubble> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 400));
_fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_slideAnimation = Tween<Offset>(begin: const Offset(-0.3, 0.0), end: Offset.zero).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic));
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutBack));
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: _slideAnimation,
child: FadeTransition(opacity: _fadeAnimation, child: ScaleTransition(scale: _scaleAnimation, alignment: Alignment.centerLeft, child: widget.child)),
);
}
}

@ -1,19 +1,15 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
class BusinessCardDialog extends StatelessWidget { class BusinessCardDialog extends StatelessWidget {
final String? qrBase64; const BusinessCardDialog({Key? key}) : super(key: key);
final EmpBusinessCardInfoModel? employeeInfo;
const BusinessCardDialog({Key? key, this.qrBase64, this.employeeInfo})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -21,7 +17,6 @@ class BusinessCardDialog extends StatelessWidget {
quarterTurns: 1, quarterTurns: 1,
child: AspectRatio( child: AspectRatio(
aspectRatio: 3.5 / 2, aspectRatio: 3.5 / 2,
child: SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -45,7 +40,7 @@ class BusinessCardDialog extends StatelessWidget {
), ),
), ),
Image.memory( Image.memory(
Utils.getPostBytes(qrBase64 ?? ""), Utils.getPostBytes(AppState().memberInformationList!.businessCardQR ?? ""),
width: 129, width: 129,
height: 129, height: 129,
), ),
@ -55,45 +50,33 @@ class BusinessCardDialog extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
(employeeInfo?.fullName ?? "") (AppState().memberInformationList!.eMPLOYEENAME ?? "").toText23(isBold: true).paddingOnly(top: 15),
.toText23(isBold: true) (AppState().memberInformationList!.getPositionName() ?? "").toText17(
.paddingOnly(top: 15),
(employeeInfo?.pPositionTitle ?? "").toText17(
color: MyColors.grey57Color, color: MyColors.grey57Color,
), ),
30.height, 30.height,
// (AppState().memberInformationList!.eMPLOYEENUMBER ?? "").toText13(),
Row( Row(
children: [ children: [
const Icon( const Icon(Icons.email, size: 17.5, color: MyColors.grey3AColor).paddingOnly(right: 11.5),
Icons.email, ("${LocaleKeys.email.tr()}: ").toText18(color: MyColors.grey57Color),
size: 17.5, (AppState().memberInformationList!.eMPLOYEEEMAILADDRESS ?? "").toText18(),
color: MyColors.grey3AColor,
).paddingOnly(right: 11.5),
("${LocaleKeys.email.tr()}: ").toText18(
color: MyColors.grey57Color,
),
(employeeInfo?.pEmailAddress ?? "").toText18(),
], ],
), ),
Row( Row(
children: [ children: [
const Icon( const Icon(Icons.call, size: 17.5, color: MyColors.grey3AColor).paddingOnly(right: 11.5),
Icons.call, ("${LocaleKeys.mobile.tr()}: ").toText18(color: MyColors.grey57Color),
size: 17.5, (AppState().memberInformationList!.mobileNumberWithZipCode ?? "").toText18(),
color: MyColors.grey3AColor,
).paddingOnly(right: 11.5),
("${LocaleKeys.mobile.tr()}: ").toText18(
color: MyColors.grey57Color,
),
(employeeInfo?.pMobilePhoneNumber ?? "").toText18(),
], ],
), ),
// 1.height,
// (AppState().memberInformationList!.jOBNAME ?? "").toText13(),
], ],
), ),
], ],
).paddingOnly(right: 13, left: 32, bottom: 28), ).paddingOnly(right: 13, left: 32, bottom: 28),
), ),
),
); );
} }
} }

@ -29,13 +29,13 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/marathon_banner.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheet.dart'; import 'package:mohem_flutter_app/widgets/bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart'; import 'package:mohem_flutter_app/widgets/mark_attendance_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart'; import 'package:mohem_flutter_app/widgets/shimmer/dashboard_shimmer_widget.dart';
import 'package:mohem_flutter_app/widgets/shimmer/offers_shimmer_widget.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:signalr_netcore/signalr_client.dart'; import 'package:signalr_netcore/signalr_client.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
HubConnection? chatHubConnection; late HubConnection chatHubConnection;
class DashboardScreen extends StatefulWidget { class DashboardScreen extends StatefulWidget {
DashboardScreen({Key? key}) : super(key: key); DashboardScreen({Key? key}) : super(key: key);
@ -56,7 +56,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
int currentIndex = 0; int currentIndex = 0;
bool isDisplayMazaya = true; bool isDisplayMazaya = false;
@override @override
void initState() { void initState() {
@ -93,7 +93,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
if (!cProvider.disbaleChatForThisUser) { if (!cProvider.disbaleChatForThisUser) {
chatHubConnection?.stop(); chatHubConnection.stop();
} }
} }
@ -120,16 +120,16 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
Future<void> checkHubCon() async { Future<void> checkHubCon() async {
// chatHubConnection = await context.read<ChatProviderModel>().getHubConnection(); // chatHubConnection = await context.read<ChatProviderModel>().getHubConnection();
if (chatHubConnection?.state == HubConnectionState.Connected) { if (chatHubConnection.state == HubConnectionState.Connected) {
await chatHubConnection?.stop(); await chatHubConnection.stop();
await chatHubConnection?.start(); await chatHubConnection.start();
} else if (chatHubConnection != null && chatHubConnection!.state != HubConnectionState.Connected) { } else if (chatHubConnection.state != HubConnectionState.Connected) {
await chatHubConnection?.start(); await chatHubConnection.start();
} }
} }
void gotoChat(BuildContext context) async { void gotoChat(BuildContext context) async {
if (chatHubConnection?.state == HubConnectionState.Connected) { if (chatHubConnection.state == HubConnectionState.Connected) {
Utils.hideLoading(context); Utils.hideLoading(context);
Navigator.pushNamed(context, AppRoutes.chat); Navigator.pushNamed(context, AppRoutes.chat);
String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat"); String isAppOpendByChat = await Utils.getStringFromPrefs("isAppOpendByChat");
@ -290,7 +290,9 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
}); });
}, },
), ),
Image.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 28).expanded, Image
.asset("assets/images/logos/main_mohemm_logo.png", width: 134, height: 28)
.expanded,
SvgPicture.asset("assets/images/announcements.svg", matchTextDirection: true).onPress(() async { SvgPicture.asset("assets/images/announcements.svg", matchTextDirection: true).onPress(() async {
await Navigator.pushNamed(context, AppRoutes.announcements); await Navigator.pushNamed(context, AppRoutes.announcements);
}), }),
@ -451,30 +453,141 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
return const SizedBox(); return const SizedBox();
} }
}, },
).paddingOnly(bottom: 18, left: 24, right: 24), ).paddingOnly(bottom: 24, left: 24, right: 24),
// eventActivityWidget(context), // eventActivityWidget(context),
if (isDisplayMazaya) ...<Widget>[ if (isDisplayMazaya) ...[
InkWell( Column(
borderRadius: BorderRadius.circular(20), mainAxisSize: MainAxisSize.min,
onTap: () { crossAxisAlignment: CrossAxisAlignment.start,
Navigator.pushNamed(context, AppRoutes.dashboardScreenForDependet); children: [
}, Directionality(
child: ClipRRect( textDirection: AppState().isArabic(context) ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
child: AspectRatio( gradient: const LinearGradient(colors: [Color(0xFF91C481), Color(0xFF7CCED7)], begin: Alignment.centerLeft, end: Alignment.centerRight),
aspectRatio: 386 / 121.37, ),
child: SvgPicture.asset( child: Padding(
AppState().isArabic(context) ? "assets/images/mazaya_banner_ar.svg" : "assets/images/mazaya_banner_en.svg", padding: const EdgeInsets.all(3.0), // This creates the border width
width: double.infinity, child: Container(
fit: BoxFit.fill, decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(17), // Slightly less than outer radius
), ),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 4,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
children: [
Expanded(
flex: 2,
child: RichText(
text:
AppState().isArabic(context)
? TextSpan(
children: [
TextSpan(
text: 'اطلع على مميزات',
style: TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: const Color(0xFF5D5E5E),
),
),
TextSpan(
text: ' مزايا',
style: TextStyle(
fontSize: 16,
fontFamily: AppState().isArabic(context) ? 'Cairo' : 'Poppins',
fontWeight: FontWeight.w700,
letterSpacing: -0.2,
height: 24 / 16,
color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
),
),
],
)
: TextSpan(
children: [
TextSpan(
text: LocaleKeys.explore.tr() + ' ',
style: const TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: Color(0xFF5D5E5E),
),
),
TextSpan(
text: LocaleKeys.mazaya.tr(),
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
fontFamily: 'Poppins',
letterSpacing: -0.2,
height: 24 / 16,
color: MyColors.mazayaRedColor, // Use your MAZAYA red color here if defined, e.g. MyColors.mazayaRed
),
),
TextSpan(
text: ' ' + LocaleKeys.benefits.tr(),
style: const TextStyle(
fontSize: 16,
letterSpacing: -0.2,
fontFamily: 'Poppins',
fontWeight: FontWeight.w700,
height: 24 / 16,
color: Color(0xFF5D5E5E),
),
),
],
),
),
),
const Expanded(flex: 1, child: SizedBox()),
],
),
const SizedBox(height: 8),
LocaleKeys.mazayaDesc.tr().toText11(color: const Color(0xFF5D5E5E)),
],
),
),
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
SvgPicture.asset("assets/icons/mazaya_brand.svg", width: 90, height: 47),
const SizedBox(height: 28),
LocaleKeys.viewallofferMazaya.tr().toText12(isUnderLine: true, color: const Color(0xFF3B3D4A)).onPress(() {
Navigator.pushNamed(context, AppRoutes.offersAndDiscounts);
}),
],
),
),
],
).paddingOnly(left: 21, right: 21, top: 14, bottom: 14),
), ),
), ),
).paddingOnly(left: 21, right: 21, top: 0, bottom: 21), ).paddingOnly(left: 21, right: 21, top: 0, bottom: 21),
),
],
),
], ],
// Offers & Discounts commented out as requested
/*
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -565,7 +678,7 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
), ),
], ],
), ),
*/
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.only(top: 31), padding: const EdgeInsets.only(top: 31),
@ -578,7 +691,9 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ServicesWidget(), ServicesWidget(),
context.watch<MarathonProvider>().isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8), context
.watch<MarathonProvider>()
.isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8),
// context.watch<MarathonProvider>().isTutorialLoading // context.watch<MarathonProvider>().isTutorialLoading
// ? const MarathonBannerShimmer().paddingAll(20) // ? const MarathonBannerShimmer().paddingAll(20)
// : Container( // : Container(
@ -734,14 +849,34 @@ class _DashboardScreenState extends State<DashboardScreen> with WidgetsBindingOb
}, },
), ),
), ),
// floatingActionButton: FloatingActionButton.large(
// onPressed: () {
// Navigator.pushNamed(context, AppRoutes.chatBotHome);
// },
// backgroundColor: Colors.transparent,
// elevation: 0,
// focusElevation: 0,
// hoverElevation: 0,
// highlightElevation: 0,
//
// shape: const CircleBorder(),
// child: SvgPicture.asset("assets/icons/chatbot.svg", width: 100, height: 100),
// ),
), ),
); );
} }
Widget eventActivityWidget(BuildContext context) { Widget eventActivityWidget(BuildContext context) {
return (context.watch<DashboardProviderModel>().isEventLoadingLoading) return (context
.watch<DashboardProviderModel>()
.isEventLoadingLoading)
? const MarathonBannerShimmer().paddingOnly(left: 21, right: 21, bottom: 21, top: 0) ? const MarathonBannerShimmer().paddingOnly(left: 21, right: 21, bottom: 21, top: 0)
: (context.watch<DashboardProviderModel>().eventActivity != null && context.watch<DashboardProviderModel>().eventActivity!.isActive == true) : (context
.watch<DashboardProviderModel>()
.eventActivity != null && context
.watch<DashboardProviderModel>()
.eventActivity!
.isActive == true)
? const EventActivityBanner().paddingOnly(left: 21, right: 21, bottom: 21, top: 0) ? const EventActivityBanner().paddingOnly(left: 21, right: 21, bottom: 21, top: 0)
: const SizedBox(); : const SizedBox();
} }

@ -1,967 +0,0 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/api/offers_and_discounts_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/main.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_category_model.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/wala_plus_retailer_model.dart';
import 'package:mohem_flutter_app/widgets/info_chip_widget.dart';
import 'package:shimmer/shimmer.dart';
class DashboardScreenForDependet extends StatefulWidget {
const DashboardScreenForDependet({Key? key}) : super(key: key);
@override
State<DashboardScreenForDependet> createState() => _DashboardScreenForDependetState();
}
class _DashboardScreenForDependetState extends State<DashboardScreenForDependet> {
List<WalaPlusRetailerModel> retailersList = [];
List<WalaPlusRetailerModel> _filteredRetailersList = [];
List<WalaPlusCategoryModel> categoriesList = [];
String currentCategoryId = "";
String selectedTypeFilter = "all";
bool isNearestOffers = true;
bool isLoading = true;
bool isLoadingMore = false;
bool isCategoriesLoading = true;
bool hasMore = true;
bool _isFetchingPage = false;
int currentPage = 1;
int totalPages = 1;
final int pageSize = 10;
String searchQuery = "";
final TextEditingController _searchController = TextEditingController();
final ScrollController _scrollController = ScrollController();
bool _hasRegisteredMobileLoginInfo = false;
List<String> get _availableTypeFilters {
Set<String> types = <String>{};
for (WalaPlusRetailerModel r in retailersList) {
String? t = r.normalizedType;
if (t != null && t.isNotEmpty) {
types.add(t);
}
}
List<String> list = <String>['all', 'instore', 'online'];
for (String t in types) {
if (!list.contains(t)) {
list.add(t);
}
}
return list;
}
String _getTypeFilterTitle(String type) {
if (type == 'all') {
return LocaleKeys.allCategories.tr();
} else if (type == 'instore') {
return LocaleKeys.inStore.tr();
} else if (type == 'online') {
return LocaleKeys.online.tr();
}
return type.isNotEmpty ? (type[0].toUpperCase() + type.substring(1)) : type;
}
IconData _getTypeFilterIcon(String type) {
if (type == 'instore') {
return Icons.storefront_rounded;
} else if (type == 'online') {
return Icons.language_rounded;
}
return Icons.tune_rounded;
}
@override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
_loadDependentName();
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (mounted) {
await _registerMobileLoginInfo();
await fetchCategories();
await fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId);
}
});
}
Future<void> _registerMobileLoginInfo() async {
if (_hasRegisteredMobileLoginInfo) return;
if (!ApiConsts.isDependetLogin) return;
_hasRegisteredMobileLoginInfo = true;
try {
int loginType = AppState().dependentLoginType ?? 1;
String nationalId = AppState().getUserName ?? "";
String dob = AppState().dependentDob ?? "";
String name = AppState().dependentName ?? "";
String mobile = AppState().postParamsObject?.mobileNumber ?? "";
String tokenID =
(AppState().postParamsObject?.tokenID != null && AppState().postParamsObject!.tokenID!.isNotEmpty) ? AppState().postParamsObject!.tokenID! : (AppState().authenticationTokenID ?? "");
if (tokenID.isEmpty) {
tokenID = await Utils.getStringFromPrefs("P_DEP_TOKEN_ID");
}
int sessionId = AppState().postParamsObject?.pSessionId ?? 0;
String email = AppState().postParamsObject?.pEmailAddress ?? "";
String deviceToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (AppState().getDeviceToken ?? "");
await LoginApiClient().insertMobileLoginInfoDependent(
loginType: loginType,
nationalId: nationalId,
dob: dob,
dependentName: name,
mobileNumber: mobile,
tokenID: tokenID,
sessionId: sessionId,
email: email,
deviceToken: deviceToken,
context: context,
);
} catch (e) {
if (kDebugMode) {
print("Error calling insertMobileLoginInfoDependent: $e");
}
}
}
void _loadDependentName() {
if (AppState().dependentName != null && AppState().dependentName!.isNotEmpty && mounted) {
setState(() {});
}
}
@override
void dispose() {
_scrollController.removeListener(_scrollListener);
_scrollController.dispose();
_searchController.dispose();
super.dispose();
}
void _scrollListener() {
if (!_scrollController.hasClients) return;
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
// Trigger only when user scrolls to within 35px of the very bottom
if (currentScroll >= maxScroll - 35) {
if (!isLoading && !isLoadingMore && !_isFetchingPage && hasMore) {
fetchNextPage();
}
}
}
Future<void> fetchCategories() async {
setState(() {
isCategoriesLoading = true;
});
try {
List<WalaPlusCategoryModel> list = await OffersAndDiscountsApiClient().getWalaPlusCategories(context: context);
if (list.isEmpty) {
await Future.delayed(const Duration(milliseconds: 600));
list = await OffersAndDiscountsApiClient().getWalaPlusCategories(context: context);
}
if (mounted) {
List<WalaPlusCategoryModel> fullList = [];
bool hasAll = list.any((c) => (c.id == "all" || c.name?.toLowerCase() == "all" || c.name == "الجميع" || c.name == "الكل"));
if (!hasAll) {
fullList.add(WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr()));
}
fullList.addAll(list);
setState(() {
categoriesList = fullList;
if (currentCategoryId.isEmpty) {
currentCategoryId = "all";
}
});
}
} catch (e) {
debugPrint("Error fetching WalaPlus categories (attempt 1): $e");
try {
await Future.delayed(const Duration(milliseconds: 600));
List<WalaPlusCategoryModel> retryList = await OffersAndDiscountsApiClient().getWalaPlusCategories(context: context);
if (mounted && retryList.isNotEmpty) {
List<WalaPlusCategoryModel> fullList = [];
bool hasAll = retryList.any((c) => (c.id == "all" || c.name?.toLowerCase() == "all" || c.name == "الجميع" || c.name == "الكل"));
if (!hasAll) {
fullList.add(WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr()));
}
fullList.addAll(retryList);
setState(() {
categoriesList = fullList;
if (currentCategoryId.isEmpty) {
currentCategoryId = "all";
}
});
return;
}
} catch (retryEx) {
debugPrint("Error fetching WalaPlus categories (attempt 2): $retryEx");
}
if (mounted && categoriesList.isEmpty) {
setState(() {
categoriesList = [WalaPlusCategoryModel(id: "all", name: LocaleKeys.allCategories.tr())];
if (currentCategoryId.isEmpty) {
currentCategoryId = "all";
}
});
}
} finally {
if (mounted) {
setState(() {
isCategoriesLoading = false;
});
}
}
}
Future<void> fetchOffers({String? categoryId}) async {
String targetCategoryId = categoryId ?? (currentCategoryId.isEmpty ? "all" : currentCategoryId);
setState(() {
isLoading = true;
currentPage = 1;
hasMore = true;
});
try {
String catParam = (targetCategoryId == "all" || targetCategoryId == "0") ? "" : targetCategoryId;
String? offerTypeParam = OffersAndDiscountsApiClient.mapOfferTypeFilter(selectedTypeFilter);
print("Fetching WalaPlus retailers: category='$catParam', offerType='$offerTypeParam', page=1, per_page=$pageSize");
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient().getWalaPlusRetailers(
categoryId: catParam,
page: 1,
perPage: pageSize,
offerType: offerTypeParam,
context: isNearestOffers ? context : null,
);
retailersList = response?.retailers ?? [];
currentPage = 1;
totalPages = response?.pagesCount ?? 1;
hasMore = currentPage < totalPages && (response?.retailers?.isNotEmpty ?? false);
_runFilter(_searchController.text);
} catch (e) {
print("Error fetching WalaPlus retailers: $e");
retailersList = [];
_runFilter(_searchController.text);
} finally {
if (mounted) {
setState(() {
isLoading = false;
});
}
}
}
Future<void> fetchNextPage() async {
if (isLoading || isLoadingMore || _isFetchingPage || !hasMore) return;
_isFetchingPage = true;
setState(() {
isLoadingMore = true;
});
try {
int nextPage = currentPage + 1;
String catParam = (currentCategoryId == "all" || currentCategoryId == "0") ? "" : currentCategoryId;
String? offerTypeParam = OffersAndDiscountsApiClient.mapOfferTypeFilter(selectedTypeFilter);
print("Fetching WalaPlus retailers next page: $nextPage of $totalPages, category: '$catParam', offerType: '$offerTypeParam'");
WalaPlusRetailersResponse? response = await OffersAndDiscountsApiClient().getWalaPlusRetailers(
categoryId: catParam,
page: nextPage,
perPage: pageSize,
offerType: offerTypeParam,
context: isNearestOffers ? context : null,
);
List<WalaPlusRetailerModel> newRetailers = response?.retailers ?? [];
if (newRetailers.isNotEmpty) {
currentPage = nextPage;
totalPages = response?.pagesCount ?? totalPages;
hasMore = currentPage < totalPages;
retailersList.addAll(newRetailers);
_runFilter(_searchController.text);
} else {
hasMore = false;
}
} catch (e) {
print("Error fetching next page of WalaPlus retailers: $e");
} finally {
_isFetchingPage = false;
if (mounted) {
setState(() {
isLoadingMore = false;
});
}
}
}
Future<void> _onRefresh() async {
await Future.wait([fetchCategories(), fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId)]);
}
void _runFilter([String? query]) {
if (query != null) {
searchQuery = query;
}
// Type filtering (online/in store) is done server-side via WP_Offer_Type (see fetchOffers),
// so retailersList already reflects the selected type - only search is applied locally here.
List<WalaPlusRetailerModel> temp = retailersList;
if (searchQuery.trim().isNotEmpty) {
String q = searchQuery.trim().toLowerCase();
temp =
temp.where((WalaPlusRetailerModel item) {
String name = (item.name ?? "").toLowerCase();
String title = (item.offerTitle ?? "").toLowerCase();
String desc = (item.description ?? "").toLowerCase();
String city = item.displayCity.toLowerCase();
return name.contains(q) || title.contains(q) || desc.contains(q) || city.contains(q);
}).toList();
}
_filteredRetailersList = temp;
setState(() {});
}
void _openRetailer(WalaPlusRetailerModel item) {
Navigator.pushNamed(context, AppRoutes.walaPlusRetailerDetails, arguments: item);
}
void _performLogout() {
Utils.confirmDialog(
context,
LocaleKeys.areYouSureWantToLogOut.tr(),
onTap: () {
ApiConsts.selectedMemberType = MemberType.employee;
ApiConsts.ISDependetLogin = false;
AppState().setPostParamsInitConfig();
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.login, (Route<dynamic> route) => false);
},
);
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
surfaceTintColor: Colors.transparent,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
} else {
_performLogout();
}
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Icon(isAr ? Icons.arrow_forward_ios_rounded : Icons.arrow_back_ios_new_rounded, size: 20, color: const Color(0xFF1E293B)),
),
),
const SizedBox(width: 8),
SvgPicture.asset("assets/icons/mazaya_brand.svg", height: 48, fit: BoxFit.contain),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!ApiConsts.isDependetLogin) ...[
IconButton(
icon: Image.asset(
'assets/icons/ic_add_dependents.png',
width: 24,
height: 24,
color: const Color(0xFF1E293B),
errorBuilder:
(context, error, stackTrace) =>
SvgPicture.asset('assets/icons/ic_dependents_top.svg', width: 24, height: 24, colorFilter: const ColorFilter.mode(Color(0xFF1E293B), BlendMode.srcIn)),
),
tooltip: LocaleKeys.addDependents.tr(),
onPressed: () {
Navigator.pushNamed(context, AppRoutes.familyMembers, arguments: {'fromMazaya': true});
},
),
],
IconButton(
icon: const Icon(Icons.home_rounded, size: 26, color: Color(0xFF1E293B)),
onPressed: () {
if (ApiConsts.isDependetLogin) {
_onRefresh();
} else {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
}
},
),
],
),
],
),
),
),
body: SafeArea(
child: RefreshIndicator(
onRefresh: _onRefresh,
color: MyColors.textMixColor,
child: SingleChildScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Search Input Card
_buildSearchBar(isAr),
// "Browse Categories" Header
_buildBrowseCategoriesHeader(isAr),
// Categories Section
if (categoriesList.isNotEmpty) _buildCategoriesList(isAr) else _buildCategoriesShimmer(),
// Type Filter Chips (All, In Store, Online)
_buildTypeFilterChips(isAr),
const SizedBox(height: 16),
// Offers Grid
if (isLoading)
_buildRetailersShimmer()
else if (_filteredRetailersList.isNotEmpty) ...[
GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 4 : 2, childAspectRatio: 0.7, crossAxisSpacing: 12, mainAxisSpacing: 16),
itemCount: _filteredRetailersList.length,
itemBuilder: (context, index) {
return _buildOfferCard(_filteredRetailersList[index], isAr);
},
),
if (isLoadingMore) ...[
16.height,
Center(child: Padding(padding: const EdgeInsets.symmetric(vertical: 12), child: Image.asset("assets/images/logos/loading_mohemm_logo.gif", height: 48.0, width: 48.0))),
],
] else
Padding(padding: const EdgeInsets.only(top: 40), child: Center(child: LocaleKeys.noOffersAvailable.tr().toText14(color: const Color(0xFF64748B)))),
],
),
),
),
),
);
}
Widget _buildSearchBar(bool isAr) {
return Container(
width: double.infinity,
height: 70,
padding: const EdgeInsets.only(top: 14, right: 16, bottom: 14, left: 16),
decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE8E8E8), width: 1.0),
boxShadow: [BoxShadow(color: const Color(0xFF000000).withOpacity(0.0314), offset: const Offset(0, 2), blurRadius: 5, spreadRadius: 0)],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
LocaleKeys.search.tr().toText12(isBold: true, color: const Color(0xFF2A333C)),
const SizedBox(height: 2),
TextField(
controller: _searchController,
onChanged: _runFilter,
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.zero,
hintText: LocaleKeys.searchItems.tr(),
hintStyle: const TextStyle(color: Color(0xFFACAFB1), fontSize: 14, fontWeight: FontWeight.w400, height: 1.2),
border: InputBorder.none,
),
style: const TextStyle(color: Color(0xFF2A333C), fontSize: 14, fontWeight: FontWeight.w400),
),
],
),
);
}
Widget _buildBrowseCategoriesHeader(bool isAr) {
return Padding(padding: const EdgeInsets.only(top: 22, bottom: 14), child: LocaleKeys.browseCategories.tr().toText18(isBold: true, color: const Color(0xFF1E293B)));
}
Widget _buildCategoriesList(bool isAr) {
return SizedBox(
height: 136.0,
width: double.infinity,
child: ListView.separated(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: categoriesList.length,
separatorBuilder: (_, __) => const SizedBox(width: 14),
itemBuilder: (BuildContext context, int index) {
WalaPlusCategoryModel item = categoriesList[index];
bool isSelected = (item.id == "all" && (currentCategoryId == "all" || currentCategoryId.isEmpty)) || (currentCategoryId.isNotEmpty && currentCategoryId == item.id);
String title = (item.id == "all") ? LocaleKeys.allCategories.tr() : (item.name ?? "");
return InkWell(
borderRadius: BorderRadius.circular(18),
onTap: () {
if (currentCategoryId != item.id) {
setState(() {
currentCategoryId = item.id ?? "";
});
fetchOffers(categoryId: currentCategoryId);
}
},
child: SizedBox(
width: 84,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_buildCategoryCard(item, isSelected),
const SizedBox(height: 8),
title.toText13(
// isBold: true,
color: const Color(0xFF1E293B),
maxlines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
},
),
);
}
Widget _buildTypeFilterChips(bool isAr) {
List<String> types = _availableTypeFilters;
return Padding(
padding: const EdgeInsets.only(top: 14, bottom: 2),
child: SizedBox(
height: 38,
child: ListView.separated(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
itemCount: types.length,
separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 8),
itemBuilder: (BuildContext context, int index) {
String typeKey = types[index];
bool isSelected = selectedTypeFilter == typeKey;
String title = _getTypeFilterTitle(typeKey);
IconData icon = _getTypeFilterIcon(typeKey);
return InkWell(
borderRadius: BorderRadius.circular(6),
onTap: () {
if (selectedTypeFilter != typeKey) {
setState(() {
selectedTypeFilter = typeKey;
});
fetchOffers(categoryId: currentCategoryId.isEmpty ? "all" : currentCategoryId);
}
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
alignment: Alignment.center,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(6), color: isSelected ? MyColors.darkIconColor : MyColors.lightGreyEAColor),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icon, size: 14, color: isSelected ? MyColors.white : MyColors.black),
const SizedBox(width: 5),
title.toText12(color: isSelected ? MyColors.white : MyColors.black),
],
),
),
);
},
),
),
);
}
Widget _buildCategoryCard(WalaPlusCategoryModel item, bool isSelected) {
const double cardSize = 84.0;
const double borderRadiusVal = 18.0;
const double borderWidthVal = 2.8;
const Color activeBorderColor = Color(0xFF00A86B);
bool isAll = (item.id == "all" || item.id == "0" || (item.name ?? "").toLowerCase() == "all" || item.name == "الجميع" || item.name == "الكل");
Widget imageContent;
if (isAll) {
imageContent = Image.asset('assets/images/cat_all.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _getFallbackCategoryAsset(item));
} else {
String categoryImageUrl = item.categoryImageUrl.isNotEmpty ? item.categoryImageUrl : (item.image?.trim() ?? "");
if (categoryImageUrl.isNotEmpty) {
if (categoryImageUrl.toLowerCase().endsWith('.svg') || item.isSvg) {
imageContent = SvgPicture.network(
categoryImageUrl,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
placeholderBuilder: (_) => const Center(child: SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Color(0xFF00A86B)))),
);
} else {
imageContent = Image.network(categoryImageUrl, width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _getFallbackCategoryAsset(item));
}
} else {
imageContent = _getFallbackCategoryAsset(item);
}
}
return Container(
width: cardSize,
height: cardSize,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(borderRadiusVal),
border: isSelected ? Border.all(color: activeBorderColor, width: borderWidthVal) : null,
),
child: ClipRRect(borderRadius: BorderRadius.circular(isSelected ? (borderRadiusVal - borderWidthVal) : borderRadiusVal), child: imageContent),
);
}
Widget _getFallbackCategoryAsset(WalaPlusCategoryModel item) {
String name = (item.name ?? "").toLowerCase();
if (item.id == "all" || item.id == "0" || name == "all" || name == "الكل" || name == "الجميع") {
return Image.asset('assets/images/cat_all.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item));
} else if (name.contains("shop") || name.contains("retail") || name.contains("تسوق") || name.contains("تجزئة")) {
return Image.asset('assets/images/cat_retail.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item));
} else if (name.contains("food") || name.contains("restaurant") || name.contains("مطاعم") || name.contains("أغذية") || name.contains("cafe") || name.contains("مقهى")) {
return Image.asset('assets/images/cat_food.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item));
} else if (name.contains("fitness") || name.contains("wellness") || name.contains("sport") || name.contains("gym") || name.contains("رياضة") || name.contains("لياقة") || name.contains("صحة")) {
return Image.asset('assets/images/cat_fitness.png', width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildFallbackCategoryIconWidget(item));
}
return _buildFallbackCategoryIconWidget(item);
}
Widget _buildFallbackCategoryIconWidget(WalaPlusCategoryModel item) {
return Container(color: Colors.transparent, alignment: Alignment.center, child: _getFallbackCategoryIcon(item, const Color(0xFF168A89)));
}
Widget _getFallbackCategoryIcon(WalaPlusCategoryModel item, Color iconColor) {
String name = (item.name ?? "").toLowerCase();
IconData iconData = Icons.grid_view_rounded;
Color color = iconColor;
if (item.id == "all" || name == "all" || name == "الكل" || name == "الجميع") {
iconData = Icons.confirmation_number_outlined;
color = const Color(0xFFE52332);
} else if (name.contains("finance") || name.contains("بنوك") || name.contains("مالي")) {
iconData = Icons.account_balance_rounded;
} else if (name.contains("entertain") || name.contains("activities") || name.contains("أنشطة") || name.contains("ترفيه")) {
iconData = Icons.theater_comedy_rounded;
} else if (name.contains("educat") || name.contains("تعليم")) {
iconData = Icons.menu_book_rounded;
} else if (name.contains("shop") || name.contains("retail") || name.contains("تسوق") || name.contains("تجزئة")) {
iconData = Icons.shopping_bag_outlined;
} else if (name.contains("health") || name.contains("medical") || name.contains("صحة") || name.contains("طبي")) {
iconData = Icons.health_and_safety_outlined;
} else if (name.contains("beauty") || name.contains("جمال") || name.contains("عناية")) {
iconData = Icons.face_retouching_natural_rounded;
} else if (name.contains("fitness") || name.contains("sport") || name.contains("gym") || name.contains("رياضة") || name.contains("لياقة")) {
iconData = Icons.fitness_center_rounded;
} else if (name.contains("hotel") || name.contains("travel") || name.contains("فندق") || name.contains("فنادق") || name.contains("سفر")) {
iconData = Icons.hotel_rounded;
} else if (name.contains("service") || name.contains("خدمات")) {
iconData = Icons.build_circle_outlined;
} else if (name.contains("food") || name.contains("restaurant") || name.contains("مطاعم") || name.contains("أغذية")) {
iconData = Icons.restaurant_rounded;
} else if (name.contains("home") || name.contains("منزل")) {
iconData = Icons.home_rounded;
}
return Icon(iconData, color: color, size: 26);
}
String _getDiscountBadge(WalaPlusRetailerModel item) {
if (item.offerTitle != null) {
RegExp percentRegex = RegExp(r'(\d+%)');
Match? match = percentRegex.firstMatch(item.offerTitle!);
if (match != null) {
return match.group(1)!;
}
}
if (item.name != null) {
RegExp percentRegex = RegExp(r'(\d+%)');
Match? match = percentRegex.firstMatch(item.name!);
if (match != null) {
return match.group(1)!;
}
}
return "";
}
Widget _buildMerchantMIcon() {
return Container(color: const Color(0xFFED1C24), alignment: Alignment.center, child: "M".toText10(isBold: true, color: Colors.white));
}
Widget _buildOfferCard(WalaPlusRetailerModel item, bool isAr) {
String? bannerUrl = (item.imageThumb != null && item.imageThumb!.isNotEmpty) ? item.imageThumb : item.image;
String? logoUrl = (item.logoThumb != null && item.logoThumb!.isNotEmpty) ? item.logoThumb : item.logo;
String discountText = _getDiscountBadge(item);
String offerSubtitle = "";
if (discountText.isNotEmpty) {
if (item.offerTitle != null && item.offerTitle!.toLowerCase().contains("off")) {
offerSubtitle = item.offerTitle!;
} else {
offerSubtitle = LocaleKeys.offDiscount.tr(args: [discountText]);
}
} else if (item.offerTitle != null && item.offerTitle!.trim().isNotEmpty) {
offerSubtitle = item.offerTitle!.trim();
}
String? typeText = item.getDisplayType();
bool isOnline = item.isOnlineStore;
String validityText = item.getDisplayValidity();
String usageText = "";
if (item.displayUsage.isNotEmpty) {
usageText = item.displayUsage.replaceAll('usage:', LocaleKeys.usageLabel.tr()).replaceAll('Usage :', LocaleKeys.usageLabel.tr());
}
String cityText = item.getDisplayCity(isAr: isAr);
if (cityText.trim().isEmpty) {
cityText = item.displayCity;
}
return InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => _openRetailer(item),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [BoxShadow(color: const Color(0xFF0D1720).withOpacity(0.0392), offset: const Offset(0, 5), blurRadius: 18, spreadRadius: 0)],
),
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Inset image with rounded corners
ClipRRect(
borderRadius: BorderRadius.circular(14),
child: SizedBox(
height: 120,
width: double.infinity,
child: Stack(
fit: StackFit.expand,
children: [
// Banner Image
(bannerUrl != null && bannerUrl.isNotEmpty)
? Image.network(
bannerUrl,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container(color: const Color(0xFFF1F5F9), child: const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 32)),
)
: Container(color: const Color(0xFFF1F5F9), child: const Icon(Icons.image_outlined, color: Color(0xFF94A3B8), size: 32)),
// Gradient overlay at bottom of image
Positioned(
bottom: 0,
left: 0,
right: 0,
height: 50,
child: Container(
decoration: BoxDecoration(gradient: LinearGradient(begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Colors.black.withOpacity(0.75), Colors.transparent])),
),
),
// Discount badge at top right (flush with edge, pill on start side)
if (discountText.isNotEmpty)
PositionedDirectional(
top: 20,
end: 0,
child: Container(
padding: const EdgeInsetsDirectional.only(start: 14, end: 10, top: 4, bottom: 4),
decoration: const BoxDecoration(color: Color(0xFFED1C24), borderRadius: BorderRadiusDirectional.only(topStart: Radius.circular(20), bottomStart: Radius.circular(20))),
child: discountText.toText14(isBold: true, color: Colors.white),
),
),
// Brand logo & name at bottom left
PositionedDirectional(
bottom: 8,
start: 8,
end: 8,
child: Row(
children: [
Container(
width: 18,
height: 18,
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4)),
clipBehavior: Clip.antiAlias,
child: (logoUrl != null && logoUrl.isNotEmpty) ? Image.network(logoUrl, fit: BoxFit.contain, errorBuilder: (_, __, ___) => _buildMerchantMIcon()) : _buildMerchantMIcon(),
),
const SizedBox(width: 5),
Expanded(child: (item.name ?? "").toText11(isBold: true, maxLine: 1, color: Colors.white)),
],
),
),
],
),
),
),
const SizedBox(height: 8),
// Retailer Name / Bundle Title
(item.name ?? "").toText14(isBold: true, maxlines: 1, overflow: TextOverflow.ellipsis, color: const Color(0xFF1E293B)),
if (offerSubtitle.isNotEmpty) ...[const SizedBox(height: 2), offerSubtitle.toText14(maxlines: 1, overflow: TextOverflow.ellipsis, isBold: true, color: const Color(0xFF1E293B))],
// Badges Row: Type, Validity, Location & Usage
if ((typeText != null && typeText.trim().isNotEmpty) || validityText.trim().isNotEmpty || cityText.trim().isNotEmpty || usageText.trim().isNotEmpty) ...[
const SizedBox(height: 6),
Wrap(
spacing: 5,
runSpacing: 4,
children: <Widget>[
// Type Chip (Online or In Store)
if (typeText != null && typeText.trim().isNotEmpty)
InfoChipWidget(
text: typeText,
icon: isOnline ? Icons.language_rounded : Icons.storefront_rounded,
backgroundColor: isOnline ? const Color(0xFFEFF6FF) : const Color(0xFFF5F3FF),
textColor: isOnline ? const Color(0xFF2563EB) : const Color(0xFF7C3AED),
),
// Validity badge
if (validityText.trim().isNotEmpty) InfoChipWidget(text: validityText, backgroundColor: const Color(0xFFE6F7F0), textColor: const Color(0xFF00A651)),
// Location Badge
if (cityText.trim().isNotEmpty)
InfoChipWidget(
text: cityText,
svgIcon: "assets/icons/location.svg",
isBold: false,
backgroundColor: const Color(0xFFF3F4F6),
textColor: const Color(0xFF4B5563),
iconColor: const Color(0xFF64748B),
iconTextGap: 2.5,
),
// Usage badge
if (usageText.trim().isNotEmpty) InfoChipWidget(text: usageText, backgroundColor: const Color(0xFFFEF3C7), textColor: const Color(0xFFB45309)),
],
),
],
],
),
),
);
}
Widget _buildRetailersShimmer() {
return Shimmer.fromColors(
baseColor: const Color(0xFFE2E8F0),
highlightColor: const Color(0xFFF8FAFC),
child: GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: isTablet ? 4 : 2, childAspectRatio: 0.7, crossAxisSpacing: 12, mainAxisSpacing: 16),
itemCount: 4,
itemBuilder: (_, __) {
return Container(
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(height: 105, width: double.infinity, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14))),
const SizedBox(height: 8),
Container(height: 14, width: 110, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
const SizedBox(height: 4),
Container(height: 12, width: 80, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
const SizedBox(height: 6),
Wrap(
spacing: 5,
runSpacing: 4,
children: <Widget>[
Container(height: 16, width: 50, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10))),
Container(height: 16, width: 45, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10))),
Container(height: 16, width: 45, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(10))),
],
),
],
),
);
},
),
);
}
Widget _buildCategoriesShimmer() {
return SizedBox(
height: 136.0,
child: Shimmer.fromColors(
baseColor: const Color(0xFFE2E8F0),
highlightColor: const Color(0xFFF8FAFC),
child: ListView.separated(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: 5,
separatorBuilder: (BuildContext _, int __) => const SizedBox(width: 14),
itemBuilder: (BuildContext _, int index) {
return SizedBox(
width: 84,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(width: 84, height: 84, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(18))),
const SizedBox(height: 8),
Container(width: 65, height: 11, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
const SizedBox(height: 4),
Container(width: 42, height: 11, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))),
],
),
);
},
),
),
);
}
}

File diff suppressed because it is too large Load Diff

@ -2,7 +2,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/classes/utils.dart';
@ -12,7 +11,6 @@ import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/dashboard/drawer_menu_item_model.dart'; import 'package:mohem_flutter_app/models/dashboard/drawer_menu_item_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/provider/chat_provider_model.dart'; import 'package:mohem_flutter_app/provider/chat_provider_model.dart';
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/dialogs/id/business_card_dialog.dart'; import 'package:mohem_flutter_app/ui/dialogs/id/business_card_dialog.dart';
@ -39,22 +37,6 @@ class _AppDrawerState extends State<AppDrawer> {
chatData = Provider.of<ChatProviderModel>(context, listen: false); chatData = Provider.of<ChatProviderModel>(context, listen: false);
} }
Future<void> _showBusinessCard(BuildContext context) async {
Utils.showLoading(context);
try {
GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo();
Utils.hideLoading(context);
showMDialog(
context,
child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel),
isBusniessCard: true,
);
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (drawerMenuItemList.isEmpty) { if (drawerMenuItemList.isEmpty) {
@ -137,7 +119,7 @@ class _AppDrawerState extends State<AppDrawer> {
}), }),
menuItem("assets/images/drawer/employee_id.svg", LocaleKeys.employeeDigitalID.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: EmployeeDigitialIdDialog())), menuItem("assets/images/drawer/employee_id.svg", LocaleKeys.employeeDigitalID.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: EmployeeDigitialIdDialog())),
if (AppState().businessCardPrivilege) if (AppState().businessCardPrivilege)
menuItem("assets/images/drawer/view_business_card.svg", LocaleKeys.viewBusinessCard.tr(), "", closeDrawer: false, onPress: () => _showBusinessCard(context)), menuItem("assets/images/drawer/view_business_card.svg", LocaleKeys.viewBusinessCard.tr(), "", closeDrawer: false, onPress: () => showMDialog(context, child: BusinessCardDialog(), isBusniessCard: true)),
menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: (){Utils.performLogout(context, chatData);}), menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: (){Utils.performLogout(context, chatData);}),
// menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: () {Navigator.pushNamed(context, AppRoutes.survey,); // menuItem("assets/images/drawer/logout.svg", LocaleKeys.logout.tr(), "", color: MyColors.redA3Color, closeDrawer: false, onPress: () {Navigator.pushNamed(context, AppRoutes.survey,);
], ],

@ -1,7 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'dart:ui' as ui;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization/src/public_ext.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart'; import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
@ -22,12 +22,10 @@ import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
import 'package:mohem_flutter_app/models/basic_member_information_model.dart'; import 'package:mohem_flutter_app/models/basic_member_information_model.dart';
import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart'; import 'package:mohem_flutter_app/models/check_mobile_app_version_model.dart';
import 'package:mohem_flutter_app/models/generic_response_model.dart'; import 'package:mohem_flutter_app/models/generic_response_model.dart';
import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart'; import 'package:mohem_flutter_app/models/get_mobile_login_info_list_model.dart';
import 'package:mohem_flutter_app/models/dependent/get_mobile_login_info_dependent_model.dart';
import 'package:mohem_flutter_app/models/member_information_list_model.dart'; import 'package:mohem_flutter_app/models/member_information_list_model.dart';
import 'package:mohem_flutter_app/models/member_login_list_model.dart'; import 'package:mohem_flutter_app/models/member_login_list_model.dart';
import 'package:mohem_flutter_app/models/privilege_list_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart';
@ -50,11 +48,7 @@ class LoginScreen extends StatefulWidget {
class _LoginScreenState extends State<LoginScreen> { class _LoginScreenState extends State<LoginScreen> {
TextEditingController username = TextEditingController(); TextEditingController username = TextEditingController();
TextEditingController password = TextEditingController(); TextEditingController password = TextEditingController();
TextEditingController nationalId = TextEditingController();
TextEditingController dobController = TextEditingController();
DateTime? selectedDob;
// ignore: unused_field
CheckMobileAppVersionModel? _checkMobileAppVersion; CheckMobileAppVersionModel? _checkMobileAppVersion;
MemberLoginListModel? _memberLoginList; MemberLoginListModel? _memberLoginList;
@ -76,11 +70,6 @@ class _LoginScreenState extends State<LoginScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
ApiConsts.selectedMemberType = MemberType.employee;
ApiConsts.ISDependetLogin = false;
nationalId.addListener(_onCredentialsChanged);
dobController.addListener(_onCredentialsChanged);
checkDependentPrefs();
// hmsApiAvailability = HmsApiAvailability(); // hmsApiAvailability = HmsApiAvailability();
// checkFirebaseToken(); // checkFirebaseToken();
if (kReleaseMode) { if (kReleaseMode) {
@ -88,12 +77,6 @@ class _LoginScreenState extends State<LoginScreen> {
} }
} }
void _onCredentialsChanged() {
if (mounted && ApiConsts.ISDependetLogin) {
setState(() {});
}
}
void checkDeviceSafety() { void checkDeviceSafety() {
try { try {
SafeDevice.isJailBroken.then((bool value) { SafeDevice.isJailBroken.then((bool value) {
@ -116,15 +99,11 @@ class _LoginScreenState extends State<LoginScreen> {
@override @override
void dispose() { void dispose() {
nationalId.removeListener(_onCredentialsChanged);
dobController.removeListener(_onCredentialsChanged);
dobController.dispose();
super.dispose(); super.dispose();
} }
String? firebaseToken; String? firebaseToken;
GetMobileLoginInfoListModel? loginInfo; GetMobileLoginInfoListModel? loginInfo;
GetMobileLoginInfoDependentModel? dependentLoginInfo;
Future<void> checkFirebaseToken() async { Future<void> checkFirebaseToken() async {
try { try {
@ -143,10 +122,6 @@ class _LoginScreenState extends State<LoginScreen> {
_firebaseMessaging = FirebaseMessaging.instance; _firebaseMessaging = FirebaseMessaging.instance;
try { try {
firebaseToken = await _firebaseMessaging.getToken(); firebaseToken = await _firebaseMessaging.getToken();
if (firebaseToken != null && firebaseToken!.isNotEmpty) {
AppState().setDeviceToken = firebaseToken;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", firebaseToken!);
}
} catch (ex) { } catch (ex) {
firebaseToken = ""; firebaseToken = "";
} }
@ -167,10 +142,6 @@ class _LoginScreenState extends State<LoginScreen> {
await Firebase.initializeApp(); await Firebase.initializeApp();
_firebaseMessaging = FirebaseMessaging.instance; _firebaseMessaging = FirebaseMessaging.instance;
firebaseToken = await _firebaseMessaging.getToken(); firebaseToken = await _firebaseMessaging.getToken();
if (firebaseToken != null && firebaseToken!.isNotEmpty) {
AppState().setDeviceToken = firebaseToken;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", firebaseToken!);
}
String? firebaseAPNSToken = await _firebaseMessaging.getAPNSToken(); String? firebaseAPNSToken = await _firebaseMessaging.getAPNSToken();
print("Firebase Token: $firebaseToken"); print("Firebase Token: $firebaseToken");
print("Firebase APNS Token: $firebaseAPNSToken"); print("Firebase APNS Token: $firebaseAPNSToken");
@ -187,44 +158,7 @@ class _LoginScreenState extends State<LoginScreen> {
void checkLoginInfo() async { void checkLoginInfo() async {
try { try {
String devToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (firebaseToken ?? ""); loginInfo = await LoginApiClient().getMobileLoginInfoNEW(AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken ?? "", Platform.isAndroid ? "android" : "ios");
if (devToken.isEmpty && !AppState().getIsHuawei) {
if (AppState().getDeviceToken != null && AppState().getDeviceToken!.isNotEmpty) {
devToken = AppState().getDeviceToken!;
firebaseToken = devToken;
} else {
String saved = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (saved.isNotEmpty) {
devToken = saved;
firebaseToken = devToken;
} else {
try {
await Firebase.initializeApp();
String? tok = await FirebaseMessaging.instance.getToken();
if (tok != null && tok.isNotEmpty) {
devToken = tok;
firebaseToken = tok;
AppState().setDeviceToken = tok;
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", tok);
}
} catch (e) {
if (kDebugMode) print("Error fetching FCM token in checkLoginInfo: $e");
}
}
}
}
String devType = Platform.isAndroid ? "android" : "ios";
if (ApiConsts.selectedMemberType.isDependent) {
dependentLoginInfo = await LoginApiClient().getMobileLoginInfoNEWDependent(devToken, devType, memberType: MemberType.dependent.value);
Utils.hideLoading(context);
if (dependentLoginInfo != null) {
_handleDependentLoginInfo(dependentLoginInfo!);
}
return;
}
loginInfo = await LoginApiClient().getMobileLoginInfoNEW(devToken, devType, memberType: ApiConsts.selectedMemberType.value);
if (loginInfo == null) { if (loginInfo == null) {
await checkPrefs(); await checkPrefs();
_autoLogin = false; _autoLogin = false;
@ -241,97 +175,6 @@ class _LoginScreenState extends State<LoginScreen> {
} }
} }
void _handleDependentLoginInfo(GetMobileLoginInfoDependentModel info) {
if (info.nationalId != null && info.nationalId!.isNotEmpty) {
nationalId.text = info.nationalId!;
} else if (info.dependentID != null && info.dependentID!.isNotEmpty) {
nationalId.text = info.dependentID!;
}
String? rawDob = (info.birthDate != null && info.birthDate!.isNotEmpty) ? info.birthDate : ((info.dob != null && info.dob!.isNotEmpty) ? info.dob : null);
if (rawDob != null && rawDob.isNotEmpty) {
DateTime? parsed = _parseBirthDate(rawDob);
if (parsed != null) {
selectedDob = parsed;
dobController.text = DateFormat('dd-MM-yyyy').format(parsed);
} else {
dobController.text = rawDob;
}
}
if (info.dependentName != null && info.dependentName!.isNotEmpty) {
AppState().setDependentName = info.dependentName;
Utils.saveStringFromPrefs("P_DEPENDENT_NAME", info.dependentName!);
}
}
DateTime? _parseBirthDate(String? dateStr) {
if (dateStr == null || dateStr.trim().isEmpty) return null;
String s = dateStr.trim();
// 1. Handle Microsoft WCF JSON Date format: /Date(889401600000)/ or /Date(889401600000+0300)/
RegExp dateRegex = RegExp(r'/?Date\((-?\d+)(?:[+-]\d+)?\)/?');
Match? match = dateRegex.firstMatch(s);
if (match != null) {
try {
int millis = int.parse(match.group(1)!);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
// 2. Handle pure epoch milliseconds string e.g. "889401600000"
if (RegExp(r'^-?\d{10,14}$').hasMatch(s)) {
try {
int millis = int.parse(s);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
// 3. Try standard ISO-8601 (yyyy-MM-dd, yyyy-MM-ddTHH:mm:ss, etc.)
DateTime? dt = DateTime.tryParse(s);
if (dt != null) return DateTime(dt.year, dt.month, dt.day);
// 4. Try standard custom date patterns
List<String> formats = ['dd-MM-yyyy', 'yyyy-MM-dd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'yyyy/MM/dd'];
for (String fmt in formats) {
try {
DateTime parsed = DateFormat(fmt).parseStrict(s);
return DateTime(parsed.year, parsed.month, parsed.day);
} catch (_) {}
}
return null;
}
Future<void> checkDependentPrefs() async {
if (nationalId.text.isEmpty) {
String savedNid = await Utils.getStringFromPrefs("P_DEP_NATIONAL_ID");
if (savedNid.isNotEmpty) nationalId.text = savedNid;
}
if (dobController.text.isEmpty) {
String savedDob = await Utils.getStringFromPrefs("P_DEP_BIRTH_DATE");
if (savedDob.isNotEmpty) {
DateTime? parsed = _parseBirthDate(savedDob);
if (parsed != null) {
selectedDob = parsed;
dobController.text = DateFormat('dd-MM-yyyy').format(parsed);
} else {
dobController.text = savedDob;
}
}
} else {
// If dobController already has raw /Date(...) text, parse and format it cleanly
DateTime? parsed = _parseBirthDate(dobController.text);
if (parsed != null) {
selectedDob = parsed;
dobController.text = DateFormat('dd-MM-yyyy').format(parsed);
}
}
}
Future<void> checkPrefs() async { Future<void> checkPrefs() async {
String username = await Utils.getStringFromPrefs(SharedPrefsConsts.username); String username = await Utils.getStringFromPrefs(SharedPrefsConsts.username);
if (username.isNotEmpty) { if (username.isNotEmpty) {
@ -422,7 +265,9 @@ class _LoginScreenState extends State<LoginScreen> {
Utils.handleException(ex, context, null); Utils.handleException(ex, context, null);
} }
}, },
() => {Navigator.pop(context)}, () => {
Navigator.pop(context),
},
onResendCode: () { onResendCode: () {
performForgotPassword(); performForgotPassword();
}, },
@ -434,345 +279,6 @@ class _LoginScreenState extends State<LoginScreen> {
} }
} }
void _switchToMode(MemberType type) async {
if (ApiConsts.selectedMemberType != type) {
setState(() {
ApiConsts.selectedMemberType = type;
ApiConsts.ISDependetLogin = (type == MemberType.dependent);
});
if (type == MemberType.dependent) {
await checkDependentPrefs();
}
if (firebaseToken == null || firebaseToken!.isEmpty) {
String saved = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (saved.isNotEmpty) {
firebaseToken = saved;
}
}
checkLoginInfo();
}
}
Widget _buildCtaCard({required BuildContext context, required IconData icon, required String title, required String subtitle, required VoidCallback onTap}) {
bool isRtl = Directionality.of(context) == ui.TextDirection.rtl || AppState().isArabic(context);
return InkWell(
borderRadius: BorderRadius.circular(14),
onTap: onTap,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(color: const Color(0xFFEAF8F4), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFB8E8DB), width: 1.0)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 40,
height: 40,
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
alignment: Alignment.center,
child: Icon(icon, color: const Color(0xFF168A89), size: 22),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
title.toText14(isBold: true, color: const Color(0xFF1E293B), textAlign: isRtl ? TextAlign.right : TextAlign.left),
const SizedBox(height: 4),
subtitle.toText12(color: const Color(0xFF64748B)),
],
),
),
const SizedBox(width: 8),
Icon(isRtl ? Icons.chevron_left_rounded : Icons.chevron_right_rounded, color: const Color(0xFF64748B), size: 20),
],
),
),
);
}
Widget _buildDependentCtaCard(BuildContext context) {
return _buildCtaCard(
context: context,
icon: Icons.person_outline_rounded,
title: LocaleKeys.joinMazaya.tr(),
subtitle: LocaleKeys.joinMazayaSubtitle.tr(),
onTap: () => _switchToMode(MemberType.dependent),
);
}
Widget _buildEmployeeCtaCard(BuildContext context) {
return _buildCtaCard(
context: context,
icon: Icons.badge_outlined,
title: LocaleKeys.continueAsEmployee.tr(),
subtitle: LocaleKeys.continueAsEmployeeSubtitle.tr(),
onTap: () => _switchToMode(MemberType.employee),
);
}
Future<void> _selectDateOfBirth(BuildContext context) async {
DateTime initialDate = selectedDob ?? DateTime(2000, 1, 1);
DateTime firstDate = DateTime(1920, 1, 1);
DateTime lastDate = DateTime.now();
DateTime? picked = await showDatePicker(
context: context,
initialDate: initialDate.isAfter(lastDate) ? lastDate : initialDate,
firstDate: firstDate,
lastDate: lastDate,
builder: (context, child) {
return Theme(data: Theme.of(context).copyWith(colorScheme: const ColorScheme.light(primary: MyColors.textMixColor, onPrimary: Colors.white, onSurface: MyColors.darkTextColor)), child: child!);
},
);
if (picked != null) {
setState(() {
selectedDob = picked;
dobController.text = DateFormat('dd-MM-yyyy', 'en_US').format(picked);
});
}
}
String getFormattedBirthDate() {
DateTime? date = selectedDob ?? _parseBirthDate(dobController.text.trim());
if (date != null) {
int utcMillis = DateTime.utc(date.year, date.month, date.day).millisecondsSinceEpoch;
return "/Date($utcMillis)/";
}
return "";
}
void _navigateToMazayaAccessRevoked([String? depName]) async {
String name = depName ?? AppState().dependentName ?? "";
if (name.isEmpty) {
name = await Utils.getStringFromPrefs("P_DEPENDENT_NAME");
}
if (!mounted) return;
Navigator.push(context, MaterialPageRoute(builder: (_) => MazayaAccessResultScreen(isGranted: false, dependentName: name)));
}
void performDependentLogin() async {
if (nationalId.text.trim().isEmpty) {
Utils.showToast(LocaleKeys.pleaseEnterNationalId.tr());
return;
}
String birthDateStr = getFormattedBirthDate();
if (birthDateStr.isEmpty) {
Utils.showToast(LocaleKeys.pleaseSelectDob.tr());
return;
}
Utils.showLoading(context);
try {
String devToken = AppState().getIsHuawei ? AppState().getHuaweiPushToken : (firebaseToken ?? "");
if (devToken.isEmpty && !AppState().getIsHuawei) {
if (AppState().getDeviceToken != null && AppState().getDeviceToken!.isNotEmpty) {
devToken = AppState().getDeviceToken!;
} else {
String saved = await Utils.getStringFromPrefs("P_DEP_DEVICE_TOKEN");
if (saved.isNotEmpty) {
devToken = saved;
}
}
}
String devType = Platform.isAndroid ? "android" : "ios";
GenericResponseModel? depLoginResponse = await LoginApiClient().memberLoginDependent(
nationalId: nationalId.text.trim(),
birthDate: birthDateStr,
deviceToken: devToken,
deviceType: devType,
context: context,
);
if (depLoginResponse?.errorMessage != null && depLoginResponse!.errorMessage!.isNotEmpty) {
Utils.hideLoading(context);
if (Utils.isMazayaAccessRevoked(depLoginResponse.errorMessage)) {
_navigateToMazayaAccessRevoked();
return;
}
Utils.showToast(depLoginResponse.errorMessage!);
return;
}
if (depLoginResponse?.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = depLoginResponse!.getWalaplusDepInfoModel;
String? depName = depLoginResponse.getWalaplusDepInfoModel?.pDependentName;
if (depName != null && depName.trim().isNotEmpty) {
AppState().setDependentName = depName.trim();
Utils.saveStringFromPrefs("P_DEPENDENT_NAME", depName.trim());
}
}
String? authToken = (depLoginResponse?.authenticationTokenID != null && depLoginResponse!.authenticationTokenID!.trim().isNotEmpty) ? depLoginResponse.authenticationTokenID!.trim() : null;
String? logInTokenID = depLoginResponse?.logInTokenID;
String? mobileNumber = depLoginResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber;
// If AuthenticationTokenID is present in MemberLogin_Dependent response:
// No need to show OTP screen, direct move to dashboard and use AuthenticationTokenID as TokenID for all other requests
if (authToken != null && authToken.isNotEmpty) {
Utils.hideLoading(context);
_completeDependentLogin(depLoginResponse, logInTokenID ?? authToken, mobileNumber ?? "", loginType: 1, navigateToDashboard: true);
return;
}
// Step 2: Send OTP if no previous login found on this device
if (logInTokenID == null || logInTokenID.isEmpty || mobileNumber == null || mobileNumber.isEmpty) {
Utils.hideLoading(context);
Utils.showToast(LocaleKeys.unableToRetrieveDependentInfo.tr());
return;
}
GenericResponseModel? sendOtpResponse = await LoginApiClient().mohemmSendOTPDependent(
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
logInTokenID: logInTokenID,
context: context,
);
Utils.hideLoading(context);
if (sendOtpResponse?.messageStatus == 1) {
// Step 3: Open OTP Dialog
_showDependentOtpDialog(logInTokenID, mobileNumber);
} else {
Utils.showToast(sendOtpResponse?.errorMessage ?? LocaleKeys.failedToSendOtp.tr());
}
} catch (ex) {
if (kDebugMode) print(ex);
Utils.hideLoading(context);
if (Utils.isMazayaAccessRevoked(ex)) {
_navigateToMazayaAccessRevoked();
return;
}
Utils.handleException(ex, context, null);
}
}
void _showDependentOtpDialog(String logInTokenID, String mobileNumber) {
OtpDialog(
context,
1,
int.tryParse(mobileNumber),
(value, TextEditingController _pinPutController) async {
Utils.showLoading(context);
try {
GenericResponseModel? checkOtpResponse = await LoginApiClient().mohemmCheckOTPDependent(
logInTokenID: logInTokenID,
activationCode: value,
nationalId: nationalId.text.trim(),
mobileNumber: mobileNumber,
context: context,
);
Utils.hideLoading(context);
if (checkOtpResponse?.errorMessage != null && checkOtpResponse!.errorMessage!.isNotEmpty) {
if (Utils.isMazayaAccessRevoked(checkOtpResponse.errorMessage)) {
Navigator.pop(context);
_navigateToMazayaAccessRevoked();
return;
}
Utils.showToast(checkOtpResponse.errorMessage!);
_pinPutController.clear();
otpFieldClear.value = "";
return;
}
Navigator.pop(context);
_completeDependentLogin(checkOtpResponse, logInTokenID, mobileNumber, loginType: 1, navigateToDashboard: true);
} catch (ex) {
if (kDebugMode) print(ex);
_pinPutController.clear();
otpFieldClear.value = "";
Utils.hideLoading(context);
if (Utils.isMazayaAccessRevoked(ex)) {
Navigator.pop(context);
_navigateToMazayaAccessRevoked();
return;
}
Utils.handleException(ex, context, null);
}
},
() {
Navigator.pop(context);
},
onResendCode: () async {
Utils.showLoading(context);
try {
await LoginApiClient().mohemmSendOTPDependent(nationalId: nationalId.text.trim(), mobileNumber: mobileNumber, logInTokenID: logInTokenID, context: context);
Utils.hideLoading(context);
Utils.showToast(LocaleKeys.verificationCodeResent.tr());
_showDependentOtpDialog(logInTokenID, mobileNumber);
} catch (ex) {
Utils.hideLoading(context);
if (Utils.isMazayaAccessRevoked(ex)) {
Navigator.pop(context);
_navigateToMazayaAccessRevoked();
return;
}
Utils.handleException(ex, context, null);
}
},
).displayDialog(context);
}
void _completeDependentLogin(GenericResponseModel? checkOtpResponse, String logInTokenID, String mobileNumber, {int loginType = 1, bool navigateToDashboard = true}) {
String effectiveToken =
(checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty)
? checkOtpResponse.authenticationTokenID!.trim()
: ((checkOtpResponse?.tokenID != null && checkOtpResponse!.tokenID!.isNotEmpty)
? checkOtpResponse.tokenID!
: ((checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) ? checkOtpResponse.logInTokenID! : logInTokenID));
AppState().postParamsObject?.setTokenID = effectiveToken;
AppState().setAuthenticationTokenID = effectiveToken;
AppState().postParamsObject?.setLogInTokenID = (checkOtpResponse?.logInTokenID != null && checkOtpResponse!.logInTokenID!.isNotEmpty) ? checkOtpResponse.logInTokenID! : logInTokenID;
AppState().postParamsObject?.setUserName = nationalId.text.trim();
AppState().postParamsObject?.setPUserName = nationalId.text.trim();
String resolvedMobile = mobileNumber.isNotEmpty ? mobileNumber : (checkOtpResponse?.getWalaplusDepInfoModel?.pEmpMobileNumber ?? "");
if (resolvedMobile.isNotEmpty) {
AppState().postParamsObject?.setMobileNumer = resolvedMobile;
Utils.saveStringFromPrefs("P_DEP_MOBILE_NUMBER", resolvedMobile);
}
AppState().postParamsObject?.channel = 31;
AppState().postParamsObject?.versionID = 20;
AppState().setUserName = nationalId.text.trim();
AppState().isAuthenticated = true;
AppState().setLogged = true;
if (checkOtpResponse?.getWalaplusDepInfoModel != null) {
AppState().setWalaplusDepInfoModel = checkOtpResponse!.getWalaplusDepInfoModel;
String? depName = checkOtpResponse.getWalaplusDepInfoModel?.pDependentName;
if (depName != null && depName.trim().isNotEmpty) {
AppState().setDependentName = depName.trim();
Utils.saveStringFromPrefs("P_DEPENDENT_NAME", depName.trim());
}
}
AppState().setDependentLoginType = loginType;
AppState().setDependentDob = getFormattedBirthDate();
Utils.saveStringFromPrefs("P_DEP_NATIONAL_ID", nationalId.text.trim());
Utils.saveStringFromPrefs("P_DEP_BIRTH_DATE", getFormattedBirthDate());
Utils.saveStringFromPrefs("P_DEP_LOGIN_TOKEN_ID", AppState().postParamsObject?.logInTokenID ?? logInTokenID);
Utils.saveStringFromPrefs("P_DEP_TOKEN_ID", effectiveToken);
if (checkOtpResponse?.authenticationTokenID != null && checkOtpResponse!.authenticationTokenID!.trim().isNotEmpty) {
Utils.saveStringFromPrefs("P_DEP_AUTHENTICATION_TOKEN_ID", checkOtpResponse.authenticationTokenID!.trim());
}
Utils.saveStringFromPrefs("P_DEP_DEVICE_TOKEN", AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken ?? "");
Utils.saveStringFromPrefs("P_DEP_LOGIN_TYPE", loginType.toString());
if (navigateToDashboard && ApiConsts.ISDependetLogin) {
Navigator.pushNamedAndRemoveUntil(context, AppRoutes.dashboardScreenForDependet, (route) => false);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (isAppOpenBySystem == null) { if (isAppOpenBySystem == null) {
@ -805,12 +311,20 @@ class _LoginScreenState extends State<LoginScreen> {
children: [ children: [
Row( Row(
children: [ children: [
// Expanded(
// child:SizedBox(child: HmgConnectivityButton(),),
// ),
Row( Row(
children: [ children: [
LocaleKeys.english.tr().toText14(color: AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() { LocaleKeys.english.tr().toText14(color: AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() {
context.setLocale(const Locale("en", "US")); context.setLocale(const Locale("en", "US"));
}), }),
Container(width: 1, color: MyColors.darkWhiteColor, height: 16, margin: const EdgeInsets.only(left: 10, right: 10)), Container(
width: 1,
color: MyColors.darkWhiteColor,
height: 16,
margin: const EdgeInsets.only(left: 10, right: 10),
),
LocaleKeys.arabic.tr().toText14(color: !AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() { LocaleKeys.arabic.tr().toText14(color: !AppState().isArabic(context) ? null : MyColors.textMixColor).onPress(() {
context.setLocale(const Locale("ar", "SA")); context.setLocale(const Locale("ar", "SA"));
}), }),
@ -819,74 +333,46 @@ class _LoginScreenState extends State<LoginScreen> {
], ],
), ),
Expanded( Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: IntrinsicHeight(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
LocaleKeys.login.tr().toText24(isBold: true), LocaleKeys.login.tr().toText24(isBold: true),
LocaleKeys.pleaseEnterLoginDetails.tr().toText16(), LocaleKeys.pleaseEnterLoginDetails.tr().toText16(),
16.height, 16.height,
if (ApiConsts.selectedMemberType == MemberType.employee) ...[
InputWidget(LocaleKeys.username.tr(), "123456", username), InputWidget(LocaleKeys.username.tr(), "123456", username),
12.height, 12.height,
InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true), InputWidget(LocaleKeys.password.tr(), "xxxxxx", password, isTextIsPassword: true),
9.height, 9.height,
Align( Align(
alignment: AlignmentDirectional.centerEnd, alignment: Alignment.centerRight,
child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(() { child: LocaleKeys.forgotPassword.tr().toText12(isUnderLine: true, color: MyColors.textMixColor).onPress(
() {
// Navigator.pushNamed(context, AppRoutes.forgotPassword);
performForgotPassword(); performForgotPassword();
}), },
),
20.height,
_buildDependentCtaCard(context),
] else ...[
InputWidget(LocaleKeys.nationalId.tr(), "1234567890", nationalId, isInputTypeNum: true),
12.height,
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _selectDateOfBirth(context),
child: Stack(
alignment: AlignmentDirectional.centerEnd,
children: [
IgnorePointer(child: InputWidget(LocaleKeys.dateOfBirth.tr(), "DD-MM-YYYY", dobController)),
Positioned.directional(
textDirection: Directionality.of(context),
end: 16,
child: const Icon(Icons.calendar_today_outlined, size: 20, color: MyColors.grey70Color),
),
],
), ),
), ),
20.height, 20.height,
_buildEmployeeCtaCard(context), // DefaultButton(
], // "Connect HMG Network",
// () async {
// SystemChannels.textInput.invokeMethod('TextInput.hide');
// connectWithHmgNetwork();
// },
// ),
], ],
), ),
), )
),
);
},
),
),
], ],
), ),
), ),
), ),
DefaultButton(LocaleKeys.login.tr(), () async { DefaultButton(LocaleKeys.login.tr(), () async {
SystemChannels.textInput.invokeMethod('TextInput.hide'); SystemChannels.textInput.invokeMethod('TextInput.hide');
if (ApiConsts.ISDependetLogin) {
performDependentLogin();
} else {
performLogin(); performLogin();
} }).insideContainer
}).insideContainer,
], ],
), ),
); );

@ -8,10 +8,8 @@ import 'package:local_auth/local_auth.dart';
import 'package:local_auth_darwin/local_auth_darwin.dart'; import 'package:local_auth_darwin/local_auth_darwin.dart';
import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_android/local_auth_android.dart';
import 'package:mohem_flutter_app/api/login_api_client.dart'; import 'package:mohem_flutter_app/api/login_api_client.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/classes/date_uitl.dart'; import 'package:mohem_flutter_app/classes/date_uitl.dart';
import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/config/routes.dart';
@ -71,22 +69,14 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
title: title: (mobileLoginInfoListModel?.businessCardPrivilege ?? false)
(mobileLoginInfoListModel?.businessCardPrivilege ?? false) ? LocaleKeys.viewBusinessCard.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() {
? LocaleKeys.viewBusinessCard.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() async { showMDialog(context, child: const BusinessCardDialog());
Utils.showLoading(context);
try {
GenericResponseModel? response = await ProfileApiClient().getEmployeeBusinessCardInfo();
Utils.hideLoading(context);
showMDialog(context, child: BusinessCardDialog(qrBase64: response?.businessCardQROutside, employeeInfo: response?.empBusinessCardInfoModel));
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}) })
: Container(), : Container(),
actions: [ actions: [
Center( Center(
child: LocaleKeys.employeeDigitalID.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() { child: LocaleKeys.employeeDigitalID.tr().toText12(color: MyColors.textMixColor, isUnderLine: true).onPress(() {
showMDialog(context, child: EmployeeDigitialIdDialog()); showMDialog(context, child: EmployeeDigitialIdDialog());
}), }),
@ -122,10 +112,7 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [LocaleKeys.lastLoginDetails.tr().toText16(), DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn ?? null), false).toText12()],
LocaleKeys.lastLoginDetails.tr().toText16(),
DateUtil.formatDateToDate(DateUtil.convertStringToDate(mobileLoginInfoListModel!.editedOn ?? DateTime.now().toString()), false).toText12(),
],
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -134,7 +121,7 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
LocaleKeys.verificationType.tr().toText10(color: MyColors.grey57Color), LocaleKeys.verificationType.tr().toText10(color: MyColors.grey57Color),
getVerificationType(mobileLoginInfoListModel!.loginType!).toText12(), getVerificationType(mobileLoginInfoListModel!.loginType!).toText12(),
const Expanded(child: SizedBox()), const Expanded(child: SizedBox()),
DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel!.editedOn!)).toText12(), DateUtil.formatDateToTime(DateUtil.convertStringToDate(mobileLoginInfoListModel?.editedOn)).toText12(),
], ],
), ),
], ],
@ -220,12 +207,8 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
} }
Future<bool> loginWithFaceIDAndBiometrics() async { Future<bool> loginWithFaceIDAndBiometrics() async {
IOSAuthMessages iosStrings = const IOSAuthMessages(
cancelButton: 'cancel', IOSAuthMessages iosStrings = const IOSAuthMessages(cancelButton: 'cancel');
goToSettingsButton: 'settings',
goToSettingsDescription: 'Please set up your Touch ID.',
lockOut: 'Please reenable your Touch ID',
);
// IOSAuthMessages iosStrings = const IOSAuthMessages( // IOSAuthMessages iosStrings = const IOSAuthMessages(
// cancelButton: 'cancel', // cancelButton: 'cancel',
@ -236,10 +219,11 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
bool authenticated = false; bool authenticated = false;
try { try {
authenticated = await auth.authenticate( authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate',
localizedReason: 'Scan your fingerprint to authenticate', authMessages: [
options: const AuthenticationOptions(useErrorDialogs: true, stickyAuth: true, biometricOnly: true), iosStrings,
authMessages: [iosStrings, const AndroidAuthMessages()], const AndroidAuthMessages(),
],
); );
} on PlatformException catch (e) { } on PlatformException catch (e) {
print(e); print(e);
@ -250,22 +234,24 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
} }
Widget _loginOptionButton(String _title, String _icon, int _flag, int? _loginIndex) { Widget _loginOptionButton(String _title, String _icon, int _flag, int? _loginIndex) {
// <<<<<<< HEAD // <<<<<<< HEAD
bool isDisable = false; bool isDisable = false;
if (_flag >= 3 && _flag <= 4) { if(_flag >= 3 && _flag <= 4) {
bool isFaceEnabled = (_flag == 3 && (checkBiometricIsAvailable(BiometricType.face) || checkBiometricIsAvailable(BiometricType.weak))); bool isFaceEnabled = (_flag == 3 && (checkBiometricIsAvailable(BiometricType.face) ||
checkBiometricIsAvailable(BiometricType.weak)));
bool isThumbEnabled = (_flag == 4 && (checkBiometricIsAvailable(BiometricType.fingerprint) || checkBiometricIsAvailable(BiometricType.strong))); bool isThumbEnabled =
(_flag == 4 && (checkBiometricIsAvailable(BiometricType.fingerprint) ||
checkBiometricIsAvailable(BiometricType.strong)));
isDisable = !(isFaceEnabled || isThumbEnabled); isDisable = !(isFaceEnabled || isThumbEnabled);
} }
// ======= // =======
// bool isDisable = (_flag == 3 && !checkBiometricIsAvailable(BiometricType.face) || // bool isDisable = (_flag == 3 && !checkBiometricIsAvailable(BiometricType.face) ||
// _flag == 4 && !checkBiometricIsAvailable(BiometricType.fingerprint) && _flag == 4 && !checkBiometricIsAvailable(BiometricType.face)); // _flag == 4 && !checkBiometricIsAvailable(BiometricType.fingerprint) && _flag == 4 && !checkBiometricIsAvailable(BiometricType.face));
// // >>>>>>> master // // >>>>>>> master
return InkWell( return InkWell(
onTap: onTap: isDisable
isDisable
? null ? null
: () async { : () async {
if (_flag == 0) { if (_flag == 0) {
@ -398,7 +384,6 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
AppState().getUserName!, AppState().getUserName!,
AppState().getIsHuawei ? AppState().getHuaweiPushToken : mobileLoginInfoListModel!.deviceToken!, AppState().getIsHuawei ? AppState().getHuaweiPushToken : mobileLoginInfoListModel!.deviceToken!,
Platform.isAndroid ? "android" : "ios", Platform.isAndroid ? "android" : "ios",
memberType: MemberType.employee.value,
); );
AppState().setMemberInformationListModel = genericResponseModel!.memberInformationList?.first; AppState().setMemberInformationListModel = genericResponseModel!.memberInformationList?.first;
AppState().setPrivilegeListModel = genericResponseModel!.privilegeList ?? []; AppState().setPrivilegeListModel = genericResponseModel!.privilegeList ?? [];
@ -426,15 +411,15 @@ class _VerifyLastLoginScreenState extends State<VerifyLastLoginScreen> {
} }
} }
// //
// formatDate(date) { // formatDate(date) {
// return date; // return date;
// return DateFormat('MMM dd, yyy, kk:mm').format(date); // return DateFormat('MMM dd, yyy, kk:mm').format(date);
// } // }
// //
// showLoader(bool isTrue) { // showLoader(bool isTrue) {
// setState(() { // setState(() {
// // isLoading = isTrue; // // isLoading = isTrue;
// }); // });
// } // }
} }

@ -515,11 +515,11 @@ class _VerifyLoginScreenState extends State<VerifyLoginScreen> {
Future<bool> loginWithFaceIDAndBiometrics() async { Future<bool> loginWithFaceIDAndBiometrics() async {
IOSAuthMessages iosStrings = IOSAuthMessages iosStrings =
const IOSAuthMessages(cancelButton: 'cancel', goToSettingsButton: 'settings', goToSettingsDescription: 'Please set up your Touch ID.', lockOut: 'Please reenable your Touch ID'); const IOSAuthMessages(cancelButton: 'cancel',);
bool authenticated = false; bool authenticated = false;
try { try {
authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate', authenticated = await auth.authenticate(localizedReason: 'Scan your fingerprint to authenticate',
options: const AuthenticationOptions(useErrorDialogs: true, stickyAuth: true, biometricOnly: true,), authMessages: [ authMessages: [
iosStrings, iosStrings,
const AndroidAuthMessages(), const AndroidAuthMessages(),
],); ],);
@ -650,8 +650,7 @@ class _VerifyLoginScreenState extends State<VerifyLoginScreen> {
AppState().memberLoginList?.pMOBILENUMBER ?? "", AppState().memberLoginList?.pMOBILENUMBER ?? "",
AppState().getUserName!, AppState().getUserName!,
AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken!, AppState().getIsHuawei ? AppState().getHuaweiPushToken : firebaseToken!,
Platform.isAndroid ? "android" : "ios", Platform.isAndroid ? "android" : "ios");
memberType: MemberType.employee.value);
if (genericResponseModel?.errorMessage != null) { if (genericResponseModel?.errorMessage != null) {
Utils.showToast(genericResponseModel?.errorMessage ?? ""); Utils.showToast(genericResponseModel?.errorMessage ?? "");
} else { } else {

@ -30,18 +30,21 @@ class QuestionCard extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: AppinioSwiper( child: AppinioSwiper(
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 400),
padding: EdgeInsets.zero, swipeOptions: const SwipeOptions.all(),
isDisabled: true, isDisabled: true,
controller: provider.swiperController, controller: provider.swiperController,
unswipe: (int index, AppinioSwiperDirection direction) {}, cardCount: provider.cardContentList.length,
onSwipe: (int index, AppinioSwiperDirection direction) {}, cardBuilder: (BuildContext context, int index) {
cards: provider.cardContentList, return provider.cardContentList[index];
},
), ),
), ),
); );
} }
} }
class CardContent extends StatelessWidget { class CardContent extends StatelessWidget {
const CardContent({Key? key}) : super(key: key); const CardContent({Key? key}) : super(key: key);

@ -52,7 +52,7 @@ class _DynamicInputScreenState extends State<DynamicInputScreen> {
genericResponseModel = await MyAttendanceApiClient().getEitDffStructure(dynamicParams!.dynamicId, dynamicParams!.selectedEmp); genericResponseModel = await MyAttendanceApiClient().getEitDffStructure(dynamicParams!.dynamicId, dynamicParams!.selectedEmp);
getEitDffStructureList = genericResponseModel?.getEITDFFStructureList ?? []; getEitDffStructureList = genericResponseModel?.getEITDFFStructureList ?? [];
} else { } else {
genericResponseModel = await MyAttendanceApiClient().getCEIDffStructure(dynamicParams!.dynamicId, null, dynamicParams?.pContactRelationshipId); //added pContactRelationshipId this based on the ERP requested. genericResponseModel = await MyAttendanceApiClient().getCEIDffStructure(dynamicParams!.dynamicId, null);
getEitDffStructureList = genericResponseModel?.getCEIDFFStructureList ?? []; getEitDffStructureList = genericResponseModel?.getCEIDFFStructureList ?? [];
} }

@ -97,8 +97,8 @@ class _SubordinateLeaveState extends State<SubordinateLeave> {
separatorBuilder: (BuildContext cxt, int index) => 12.height, separatorBuilder: (BuildContext cxt, int index) => 12.height,
itemCount: getSubordinatesLeavesTotalList.length, itemCount: getSubordinatesLeavesTotalList.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
var diffDays = DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND!) var diffDays = DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND)
.difference(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART!)) .difference(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART))
.inDays + 1; .inDays + 1;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -109,8 +109,8 @@ class _SubordinateLeaveState extends State<SubordinateLeave> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
getSubordinatesLeavesTotalList[index].eMPLOYEENAME!.toText16(), getSubordinatesLeavesTotalList[index].eMPLOYEENAME!.toText16(),
ItemDetailView(LocaleKeys.from.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART!), "MMM dd yyyy")), ItemDetailView(LocaleKeys.from.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATESTART), "MMM dd yyyy")),
ItemDetailView(LocaleKeys.to.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND!), "MMM dd yyyy")), ItemDetailView(LocaleKeys.to.tr(), DateUtil.getFormattedDate(DateUtil.convertStringToDate(getSubordinatesLeavesTotalList[index].dATEEND), "MMM dd yyyy")),
ItemDetailView(LocaleKeys.numberDays.tr(), diffDays.toString()), ItemDetailView(LocaleKeys.numberDays.tr(), diffDays.toString()),
], ],
).expanded ).expanded

@ -49,11 +49,11 @@ class _RequestDetailsState extends State<ParkingQrScreen> {
children: [ children: [
// QR Code Image // QR Code Image
if (qrData!.getEmployeeParking!.qrCodeImageBase64 != null && qrData!.getEmployeeParking!.qrCodeImageBase64!.isNotEmpty) if (qrData!.getEmployeeParking!.qrCodeImageBase64 != null && qrData!.getEmployeeParking!.qrCodeImageBase64!.isNotEmpty)
Image.memory(Utils.dataFromBase64String(qrData!.getEmployeeParking!.qrCodeImageBase64!), width: 150, height: 150, fit: BoxFit.contain) Image.memory(Utils.dataFromBase64String(qrData!.getEmployeeParking!.qrCodeImageBase64!), width: 300, height: 300, fit: BoxFit.contain)
else else
Container( Container(
width: 200, width: 300,
height: 200, height: 300,
color: MyColors.lightGreyEFColor, color: MyColors.lightGreyEFColor,
child: Center(child: Text(LocaleKeys.noDataAvailable.tr(), style: const TextStyle(color: MyColors.grey67Color))), child: Center(child: Text(LocaleKeys.noDataAvailable.tr(), style: const TextStyle(color: MyColors.grey67Color))),
), ),

File diff suppressed because it is too large Load Diff

@ -1,551 +0,0 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart';
import 'package:mohem_flutter_app/ui/profile/mazaya_access_result_screen.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
class _ShieldIconPainter extends CustomPainter {
final Color color;
final bool isGranted;
_ShieldIconPainter({required this.color, required this.isGranted});
@override
void paint(Canvas canvas, Size size) {
Paint paint =
Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2.0
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
double w = size.width;
double h = size.height;
Path path = Path();
path.moveTo(w * 0.5, h * 0.1);
path.cubicTo(w * 0.72, h * 0.1, w * 0.88, h * 0.18, w * 0.88, h * 0.38);
path.cubicTo(w * 0.88, h * 0.65, w * 0.68, h * 0.84, w * 0.5, h * 0.92);
path.cubicTo(w * 0.32, h * 0.84, w * 0.12, h * 0.65, w * 0.12, h * 0.38);
path.cubicTo(w * 0.12, h * 0.18, w * 0.28, h * 0.1, w * 0.5, h * 0.1);
path.close();
canvas.drawPath(path, paint);
if (isGranted) {
Path checkPath = Path();
checkPath.moveTo(w * 0.34, h * 0.49);
checkPath.lineTo(w * 0.46, h * 0.61);
checkPath.lineTo(w * 0.66, h * 0.39);
canvas.drawPath(checkPath, paint);
} else {
canvas.drawLine(Offset(w * 0.38, h * 0.38), Offset(w * 0.62, h * 0.62), paint);
canvas.drawLine(Offset(w * 0.62, h * 0.38), Offset(w * 0.38, h * 0.62), paint);
}
}
@override
bool shouldRepaint(covariant _ShieldIconPainter oldDelegate) => color != oldDelegate.color || isGranted != oldDelegate.isGranted;
}
class DependentDetailsScreen extends StatefulWidget {
final GetEmployeeContactsList? contact;
// Null = just viewing (arrived by tapping the card, no top badge). True/false = this screen
// is being opened right after a successful grant/revoke performed elsewhere (e.g. the
// dependents list's inline "Allow/Revoke Mazaya Access" link), so the matching badge shows
// on first build. Toggling from within this screen itself (_performToggleAccess) sets the
// equivalent local state directly instead of needing this.
final bool? recentAction;
const DependentDetailsScreen({Key? key, this.contact, this.recentAction}) : super(key: key);
@override
_DependentDetailsScreenState createState() => _DependentDetailsScreenState();
}
class _DependentDetailsScreenState extends State<DependentDetailsScreen> {
GetEmployeeContactsList? _contact;
bool _argsLoaded = false;
// null = just viewing (arrived by tapping the card, no badge) - true = just granted here,
// false = just revoked here. Set once per toggle in _performToggleAccess; a tap-only visit
// never sets it, so the top badge only ever reflects an action just taken on this screen.
bool? _recentAction;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (!_argsLoaded) {
if (widget.contact != null) {
_contact = widget.contact;
} else {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is GetEmployeeContactsList) {
_contact = args;
} else if (args is Map && args['contact'] is GetEmployeeContactsList) {
_contact = args['contact'] as GetEmployeeContactsList;
}
}
_recentAction = widget.recentAction;
_argsLoaded = true;
}
}
bool get _isAccessAllowed {
if (_contact == null) return false;
String? flag = _contact!.mAZAYAACCESSFLG ?? _contact!.mazayaAccess?.toString();
return flag?.trim().toUpperCase() == 'Y';
}
bool get _isNhicVerified {
if (_contact == null) return false;
return _contact!.pNHICVERIFIEDFLAG?.trim().toUpperCase() == 'Y';
}
String _getInitials(String? name) {
if (name == null || name.trim().isEmpty) return "D";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
if (parts.isEmpty) return "D";
if (parts.length == 1) {
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
}
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
}
String _getFirstName(String? name) {
if (name == null || name.trim().isEmpty) return "";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
if (parts.isEmpty) return "";
return parts.first;
}
DateTime? _parseBirthDate(String? dateStr) {
if (dateStr == null || dateStr.trim().isEmpty) return null;
String s = dateStr.trim();
RegExp dateRegex = RegExp(r'/?Date\((-?\d+)(?:[+-]\d+)?\)/?');
Match? match = dateRegex.firstMatch(s);
if (match != null) {
try {
int millis = int.parse(match.group(1)!);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
if (RegExp(r'^-?\d{10,14}$').hasMatch(s)) {
try {
int millis = int.parse(s);
DateTime dtUtc = DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
return DateTime(dtUtc.year, dtUtc.month, dtUtc.day);
} catch (_) {}
}
DateTime? dt = DateTime.tryParse(s);
if (dt != null) return DateTime(dt.year, dt.month, dt.day);
List<String> formats = <String>['yyyy-MM-dd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'dd-MM-yyyy', 'yyyy/MM/dd', 'dd-MMM-yyyy', 'dd MMM yyyy'];
for (String fmt in formats) {
try {
DateTime parsed = DateFormat(fmt, 'en_US').parseStrict(s);
return DateTime(parsed.year, parsed.month, parsed.day);
} catch (_) {}
}
return null;
}
String _formatDob(DateTime? dob, String? raw) {
if (dob != null) {
return DateFormat('dd MMM yyyy', 'en_US').format(dob);
}
if (raw != null && raw.trim().isNotEmpty) {
return raw.split(' ').first;
}
return "-";
}
String _formatNationalId(String? nationalId) {
if (nationalId == null || nationalId.trim().isEmpty) return "-";
String clean = nationalId.replaceAll('-', '').trim();
String last4 = clean.length >= 4 ? clean.substring(clean.length - 4) : clean;
return LocaleKeys.idEnding.tr(args: <String>[last4]);
}
void _onToggleAccess() {
if (_contact == null) return;
bool isAllowed = _isAccessAllowed;
String dependentName = _contact!.cONTACTNAME ?? "";
ConfirmBottomSheet.show(
context,
title: isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(),
message: isAllowed ? LocaleKeys.confirmRevokeMazayaAccess.tr(args: <String>[dependentName]) : LocaleKeys.confirmGrantMazayaAccess.tr(args: <String>[dependentName]),
icon: isAllowed ? Icons.remove_circle_outline : Icons.check_circle_outline,
iconColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: _performToggleAccess,
);
}
Future<void> _performToggleAccess() async {
if (_contact == null) return;
bool isAllowed = _isAccessAllowed;
String action = isAllowed ? "Revoke" : "Grant";
try {
Utils.showLoading(context);
await ProfileApiClient().processContactMazayaAccess(
accessAction: action,
contactRelationshipId: _contact!.cONTACTRELATIONSHIPID,
contactPersonId: _contact!.cONTACTPERSONID,
personId: _contact!.pERSONID,
patientIdentificationId: _contact!.nATIONALIDENTIFIER,
// DATE_OF_BIRTH, not the separate P_BIRTH_DATE response field - that one stays null
// for every contact until NHIC verification happens, so it's never a usable source here.
patientBirthDate: _contact!.dATEOFBIRTH,
nhicVerifiedFlag: _contact!.pNHICVERIFIEDFLAG,
);
setState(() {
_contact!.mAZAYAACCESSFLG = action == "Grant" ? "Y" : "N";
_contact!.mazayaAccess = _contact!.mAZAYAACCESSFLG;
// Top badge reflects whichever action was just taken on this screen, not just "was
// granted at some point" - a later revoke flips it so a stale grant badge never lingers.
_recentAction = action == "Grant";
});
Utils.hideLoading(context);
// Stay on this same screen either way - it already reflects the new access state via
// setState above (status badge, top banner, action button, and the share-link section
// all update in place), so tapping a dependent card and successfully toggling access
// both land on the exact same screen instead of a separate "success" screen. The top
// badge itself is the confirmation now, so no toast on top of it.
} catch (ex) {
Utils.hideLoading(context);
Utils.handleException(ex, context, null);
}
}
// Full (unmasked) DOB for the share message - distinct from _formatDob/_formatNationalId
// above, which are for the masked on-screen Key Details display.
String _formatDobForShare() {
DateTime? dob = _parseBirthDate(_contact?.dATEOFBIRTH);
if (dob != null) return DateFormat('dd-MM-yyyy').format(dob);
String raw = _contact?.dATEOFBIRTH?.trim() ?? "";
if (raw.isEmpty) return "";
if (raw.contains('T')) return raw.split('T').first;
if (raw.contains(' ')) return raw.split(' ').first;
return raw;
}
// Same message built for both "Share Download Link" and "Copy Link" - they must always
// carry identical content, so this is the single source of truth for it.
String _buildMazayaInviteMessage(String linkToShare) {
String? nationalId = _contact?.nATIONALIDENTIFIER?.trim();
String nationalIdLine = (nationalId == null || nationalId.isEmpty) ? LocaleKeys.nationalIdOrIqamaFallback.tr() : "${LocaleKeys.nationalId.tr()}: $nationalId";
return "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n$nationalIdLine\n${LocaleKeys.dateOfBirth.tr()}: ${_formatDobForShare()}";
}
// Shares the invite text together with the language-appropriate steps PDF from assets.
// Falls back to sharing just the text if the PDF can't be loaded/copied for any reason.
Future<void> _shareMazayaInvite(bool isAr, String linkToShare) async {
String message = _buildMazayaInviteMessage(linkToShare);
try {
ByteData bytes = await rootBundle.load(MazayaAccessResultScreen.getShareGuidePdfAssetPath(isAr));
Directory tempDir = await getTemporaryDirectory();
String localFileName = isAr ? "mazaya_access_guide_ar.pdf" : "mazaya_access_guide_en.pdf";
File pdfFile = File('${tempDir.path}/$localFileName');
await pdfFile.writeAsBytes(bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes));
await SharePlus.instance.share(ShareParams(text: message, files: [XFile(pdfFile.path)]));
} catch (_) {
try {
await SharePlus.instance.share(ShareParams(text: message));
} catch (_) {}
}
}
@override
Widget build(BuildContext context) {
DateTime? dob = _parseBirthDate(_contact?.dATEOFBIRTH);
String initials = _getInitials(_contact?.cONTACTNAME);
String fullName = _contact?.cONTACTNAME ?? "";
String firstName = _getFirstName(_contact?.cONTACTNAME);
String relation = (_contact?.rELATIONSHIP != null && _contact!.rELATIONSHIP!.trim().isNotEmpty) ? _contact!.rELATIONSHIP!.trim() : (_contact?.cONTACTTYPE?.trim() ?? "");
String formattedId = _formatNationalId(_contact?.nATIONALIDENTIFIER);
String formattedDob = _formatDob(dob, _contact?.dATEOFBIRTH);
bool isAllowed = _isAccessAllowed;
bool isAr = AppState().isArabic(context);
String linkToShare = MazayaAccessResultScreen.getDownloadLink();
return Scaffold(
backgroundColor: const Color(0xFFF8FAFC),
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: SafeArea(
child: Container(
height: 56,
color: const Color(0xFFF8FAFC),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: <Widget>[
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B))),
),
const SizedBox(width: 8),
LocaleKeys.dependentDetails.tr().toText20(color: const Color(0xFF1E293B)),
const Spacer(),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)),
child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.home_outlined, size: 24, color: Color(0xFF1E293B))),
),
],
),
),
),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 30),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Shown only right after a grant/revoke action taken on this screen - not
// when just viewing a dependent by tapping into this screen (_recentAction
// is null in that case).
if (_recentAction != null) ...<Widget>[
Center(
child: Container(
width: 72,
height: 72,
decoration: BoxDecoration(color: _recentAction! ? const Color(0xFFE6F7F2) : const Color(0xFFFEECEB), shape: BoxShape.circle),
child: Center(
child: CustomPaint(
size: const Size(36, 36),
painter: _ShieldIconPainter(color: _recentAction! ? const Color(0xFF168A89) : const Color(0xFFED1C24), isGranted: _recentAction!),
),
),
),
),
const SizedBox(height: 10),
Center(
child: (_recentAction! ? LocaleKeys.mazayaAccessGranted.tr() : LocaleKeys.mazayaAccessRevoked.tr()).toText16(
weight: FontWeight.w500,
color: _recentAction! ? const Color(0xFF168A89) : const Color(0xFFED1C24),
),
),
const SizedBox(height: 16),
],
// Profile Card (Mint Green)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(color: const Color(0xFFE8F7F2), borderRadius: BorderRadius.circular(16), border: Border.all(color: const Color(0xFFBCE6D7), width: 1.2)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// Avatar circle with initials
Container(
width: 52,
height: 52,
decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle),
child: Center(child: initials.toText17(color: const Color(0xFF168A89))),
),
const SizedBox(width: 14),
// Dependent Name & Relation
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
fullName.toText16(maxlines: 2, weight: FontWeight.w500, color: const Color(0xFF1E293B)),
if (relation.isNotEmpty) ...<Widget>[const SizedBox(height: 3), relation.toText12(maxLine: 1, weight: FontWeight.w500, color: const Color(0xFF168A89))],
],
),
),
// Verified badge on right - only when NHIC has actually verified this contact
if (_isNhicVerified) ...<Widget>[const SizedBox(width: 8), const Icon(Icons.verified_outlined, color: Color(0xFF059669), size: 24)],
],
),
),
const SizedBox(height: 16),
// Key details Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(0, 2))],
),
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.keyDetails.tr().toText16(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
if (_isNhicVerified) LocaleKeys.verified.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF059669)),
],
),
const SizedBox(height: 14),
const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)),
const SizedBox(height: 14),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.nationalId.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
formattedId.toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.dateOfBirth.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
formattedDob.toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
],
),
],
),
),
const SizedBox(height: 24),
// Services & actions section header
LocaleKeys.servicesAndActions.tr().toText17(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
const SizedBox(height: 4),
LocaleKeys.startDependentFlowsDesc.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
const SizedBox(height: 12),
// Mazaya access Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[BoxShadow(color: Color(0x08000000), blurRadius: 8, offset: Offset(0, 2))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Header Row: Mazaya access + Badge
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.mazayaAccess.tr().toText16(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 3),
decoration: BoxDecoration(color: isAllowed ? const Color(0xFFECFDF5) : const Color(0xFFFEE2E2), borderRadius: BorderRadius.circular(12)),
child: (isAllowed ? LocaleKeys.active.tr() : LocaleKeys.accessOff.tr()).toText12(
weight: FontWeight.w500,
color: isAllowed ? const Color(0xFF059669) : const Color(0xFFEF4444),
),
),
],
),
const SizedBox(height: 6),
LocaleKeys.allowDependentMazayaOffersDesc.tr(args: <String>[firstName.isNotEmpty ? firstName : fullName]).toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
const SizedBox(height: 16),
const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)),
const SizedBox(height: 14),
// Login method row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.loginMethod.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
LocaleKeys.nationalIdAndDob.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
],
),
const SizedBox(height: 12),
// Verification row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
LocaleKeys.verification.tr().toText13(weight: FontWeight.w400, color: const Color(0xFF64748B)),
LocaleKeys.sponsorOtp.tr().toText13(weight: FontWeight.w500, color: const Color(0xFF1E293B)),
],
),
const SizedBox(height: 20),
// Action Button: Allow Mazaya Access (Teal) or Revoke Mazaya Access (Red Outline)
InkWell(
borderRadius: BorderRadius.circular(12),
onTap: _onToggleAccess,
child: Container(
height: 48,
width: double.infinity,
decoration: BoxDecoration(
color: isAllowed ? const Color(0xFFFEF2F2) : const Color(0xFF22B0A6),
borderRadius: BorderRadius.circular(12),
border: isAllowed ? Border.all(color: const Color(0xFFFECACA), width: 1.2) : null,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(width: 18, height: 18, child: CustomPaint(painter: _ShieldIconPainter(color: isAllowed ? const Color(0xFFDC2626) : Colors.white, isGranted: !isAllowed))),
const SizedBox(width: 8),
(isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr()).toText16(
weight: FontWeight.w500,
color: isAllowed ? const Color(0xFFDC2626) : Colors.white,
),
],
),
),
),
if (isAllowed) ...<Widget>[
const SizedBox(height: 12),
Center(child: LocaleKeys.revokingPreventsAccessDesc.tr().toText11(weight: FontWeight.w400, color: const Color(0xFF64748B), isCenter: true)),
],
],
),
),
],
),
),
),
// Share download link - same bottom-sticky button convention used elsewhere in
// the app (e.g. "Join Marathon"), shown once access is granted.
if (isAllowed) DefaultButton(LocaleKeys.shareDownloadLink.tr(), () => _shareMazayaInvite(isAr, linkToShare)).insideContainer,
],
),
),
);
}
}

@ -1,24 +1,21 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/api/profile_api_client.dart'; import 'package:mohem_flutter_app/api/profile_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/classes/utils.dart'; import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart'; import 'package:mohem_flutter_app/models/dashboard/menu_entries.dart';
import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart'; import 'package:mohem_flutter_app/models/get_employee_contacts.model.dart';
import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart'; import 'package:mohem_flutter_app/provider/dashboard_provider_model.dart';
import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart'; import 'package:mohem_flutter_app/ui/profile/add_update_family_member.dart';
import 'package:mohem_flutter_app/ui/profile/dependent_details_screen.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:mohem_flutter_app/widgets/bottom_sheets/confirm_bottom_sheet.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart'; import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class FamilyMembers extends StatefulWidget { class FamilyMembers extends StatefulWidget {
const FamilyMembers({Key? key}) : super(key: key); const FamilyMembers({Key? key}) : super(key: key);
@ -28,13 +25,9 @@ class FamilyMembers extends StatefulWidget {
} }
class _FamilyMembersState extends State<FamilyMembers> { class _FamilyMembersState extends State<FamilyMembers> {
List<GetEmployeeContactsList> getEmployeeContactsList = <GetEmployeeContactsList>[]; List<GetEmployeeContactsList> getEmployeeContactsList = [];
bool fromMazaya = false;
bool _argsLoaded = false;
bool _isLoading = false;
final TextEditingController _searchController = TextEditingController(); int? relationId;
String _searchQuery = "";
@override @override
void initState() { void initState() {
@ -42,137 +35,18 @@ class _FamilyMembersState extends State<FamilyMembers> {
getEmployeeContacts(); getEmployeeContacts();
} }
@override void getEmployeeContacts() async {
void didChangeDependencies() {
super.didChangeDependencies();
if (!_argsLoaded) {
Object? args = ModalRoute.of(context)?.settings.arguments;
if (args is Map && args['fromMazaya'] == true) {
fromMazaya = true;
}
_argsLoaded = true;
}
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
bool _isAccessAllowed(GetEmployeeContactsList contact) {
String? flag = contact.mAZAYAACCESSFLG ?? contact.mazayaAccess?.toString();
return flag?.trim().toUpperCase() == 'Y';
}
bool _isNhicVerified(GetEmployeeContactsList contact) {
return contact.pNHICVERIFIEDFLAG?.trim().toUpperCase() == 'Y';
}
void _confirmToggleMazayaAccess(GetEmployeeContactsList contact) {
bool isAllowed = _isAccessAllowed(contact);
String dependentName = contact.cONTACTNAME ?? "";
ConfirmBottomSheet.show(
context,
title: isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(),
message: isAllowed ? LocaleKeys.confirmRevokeMazayaAccess.tr(args: [dependentName]) : LocaleKeys.confirmGrantMazayaAccess.tr(args: [dependentName]),
icon: isAllowed ? Icons.remove_circle_outline : Icons.check_circle_outline,
iconColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
// Same gradient as the "Add another user" button on this screen (DefaultButton's default).
confirmButtonColors: const [MyColors.gradiantEndColor, MyColors.gradiantStartColor],
confirmText: LocaleKeys.yes.tr(),
cancelText: LocaleKeys.no.tr(),
onConfirm: () => _toggleMazayaAccess(contact),
);
}
Future<void> _toggleMazayaAccess(GetEmployeeContactsList contact) async {
bool current = _isAccessAllowed(contact);
String action = current ? "Revoke" : "Grant";
try { try {
Utils.showLoading(context); Utils.showLoading(context);
await ProfileApiClient().processContactMazayaAccess( getEmployeeContactsList = await ProfileApiClient().getEmployeeContacts();
accessAction: action,
contactRelationshipId: contact.cONTACTRELATIONSHIPID,
contactPersonId: contact.cONTACTPERSONID,
personId: contact.pERSONID,
patientIdentificationId: contact.nATIONALIDENTIFIER,
// DATE_OF_BIRTH, not the separate P_BIRTH_DATE response field - that one stays null
// for every contact until NHIC verification happens, so it's never a usable source here.
patientBirthDate: contact.dATEOFBIRTH,
nhicVerifiedFlag: contact.pNHICVERIFIEDFLAG,
);
// Keep this exact `contact` instance in sync too - it's what gets handed to
// DependentDetailsScreen below, and that's a different object than whatever the fresh
// list refetch returns, so without this it would show the old (pre-toggle) access state
// even though the screen's own "just granted/revoked" badge is correct.
contact.mAZAYAACCESSFLG = action == "Grant" ? "Y" : "N";
contact.mazayaAccess = contact.mAZAYAACCESSFLG;
List<GetEmployeeContactsList> rawList = await ProfileApiClient().getEmployeeContacts();
getEmployeeContactsList = rawList;
Utils.hideLoading(context); Utils.hideLoading(context);
if (mounted) {
setState(() {}); setState(() {});
// Same destination as tapping the card itself, so toggling from either entry point
// lands on the exact same screen (grant/revoke badge, button, and share-link section
// all reflect the new state there) - no toast on top of it.
await Navigator.push(context, MaterialPageRoute<dynamic>(builder: (BuildContext cxt) => DependentDetailsScreen(contact: contact, recentAction: action == "Grant")));
getEmployeeContacts();
}
} catch (ex) { } catch (ex) {
log("erros: ${ex.toString()}");
Utils.hideLoading(context); Utils.hideLoading(context);
Utils.handleException(ex, context, null); Utils.handleException(ex, context, null);
} }
} }
String _getInitials(String? name) {
if (name == null || name.trim().isEmpty) return "D";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
if (parts.isEmpty) return "D";
if (parts.length == 1) {
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
}
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
}
List<GetEmployeeContactsList> get _filteredList {
if (_searchQuery.trim().isEmpty) return getEmployeeContactsList;
String q = _searchQuery.trim().toLowerCase();
return getEmployeeContactsList.where((GetEmployeeContactsList item) {
String name = (item.cONTACTNAME ?? "").toLowerCase();
String rel = (item.rELATIONSHIP ?? "").toLowerCase();
String personId = (item.pERSONID?.toString() ?? "").toLowerCase();
String contactPersonId = (item.cONTACTPERSONID?.toString() ?? "").toLowerCase();
String contactType = (item.cONTACTTYPE ?? "").toLowerCase();
return name.contains(q) || rel.contains(q) || personId.contains(q) || contactPersonId.contains(q) || contactType.contains(q);
}).toList();
}
void getEmployeeContacts() async {
setState(() {
_isLoading = true;
});
try {
List<GetEmployeeContactsList> rawList = await ProfileApiClient().getEmployeeContacts();
getEmployeeContactsList = rawList;
} catch (ex) {
Utils.handleException(ex, context, null);
} finally {
if (mounted) {
setState(() {
_isLoading = false;
});
}
}
}
GetMenuEntriesList _getContactMenuEntries(BuildContext context) { GetMenuEntriesList _getContactMenuEntries(BuildContext context) {
List<GetMenuEntriesList> menuData = context.watch<DashboardProviderModel>().getMenuEntriesList ?? <GetMenuEntriesList>[]; List<GetMenuEntriesList> menuData = context.watch<DashboardProviderModel>().getMenuEntriesList ?? <GetMenuEntriesList>[];
List<GetMenuEntriesList> arr = menuData.where((GetMenuEntriesList e) => e.requestType == 'CONTACT').toList(); List<GetMenuEntriesList> arr = menuData.where((GetMenuEntriesList e) => e.requestType == 'CONTACT').toList();
@ -181,380 +55,181 @@ class _FamilyMembersState extends State<FamilyMembers> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
GetMenuEntriesList menuEntries = _getContactMenuEntries(context); GetMenuEntriesList menuEntries = _getContactMenuEntries(context);
bool canAddFamilyMember = menuEntries.addButton == 'Y' || menuEntries.updateButton == 'Y'; bool canAddFamilyMember = menuEntries.addButton == 'Y';
bool canUpdateFamilyMember = menuEntries.updateButton == 'Y'; bool canUpdateFamilyMember = menuEntries.updateButton == 'Y';
bool canDeleteFamilyMember = menuEntries.deleteButton == 'Y'; bool canDeleteFamilyMember = menuEntries.deleteButton == 'Y';
List<GetEmployeeContactsList> displayList = _filteredList;
return Scaffold( return Scaffold(
backgroundColor: const Color(0xFFF8FAFC), appBar: AppBarWidget(
appBar: PreferredSize( context,
preferredSize: const Size.fromHeight(56), title: LocaleKeys.profile_familyDetails.tr(),
child: SafeArea(
child: Container(
height: 56,
color: const Color(0xFFF8FAFC),
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: <Widget>[
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B))),
),
const SizedBox(width: 8),
(fromMazaya ? LocaleKeys.myDependents.tr() : LocaleKeys.profile_familyDetails.tr()).toText20(isBold: true, color: const Color(0xFF1E293B)),
const Spacer(),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard)),
child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.home_outlined, size: 24, color: Color(0xFF1E293B))),
),
],
),
),
),
),
body: SafeArea(
child: Column(
children: <Widget>[
// Search Bar
Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 12),
child: Container(
height: 48,
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFE2E8F0))),
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Row(
children: <Widget>[
const Icon(Icons.search, color: Color(0xFF64748B), size: 20),
const SizedBox(width: 10),
Expanded(
child: TextField(
controller: _searchController,
onChanged: (String val) {
setState(() {
_searchQuery = val;
});
},
decoration: InputDecoration(
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
hintText: fromMazaya ? LocaleKeys.searchDependents.tr() : LocaleKeys.searchFamilyMembers.tr(),
hintStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 14),
),
),
),
if (_searchQuery.isNotEmpty)
InkWell(
onTap: () {
_searchController.clear();
setState(() {
_searchQuery = "";
});
},
child: const Padding(padding: EdgeInsets.symmetric(horizontal: 4), child: Icon(Icons.close, color: Color(0xFF94A3B8), size: 18)),
),
],
),
),
), ),
backgroundColor: MyColors.backgroundColor,
// Dependents list body: Column(
children: [
Expanded( Expanded(
child: child: getEmployeeContactsList.isEmpty
_isLoading
? _buildDependentsShimmer()
: displayList.isEmpty
? Utils.getNoDataWidget(context) ? Utils.getNoDataWidget(context)
: ListView.separated( : ListView.separated(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 20), padding: const EdgeInsets.all(21),
separatorBuilder: (BuildContext cxt, int index) => const SizedBox(height: 12), separatorBuilder: (cxt, index) => 12.height,
itemCount: displayList.length, itemCount: getEmployeeContactsList.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (context, index) {
return _buildDependentCard(displayList[index], isAr, canUpdateFamilyMember, canDeleteFamilyMember); return Column(
},
),
),
// Bottom action button: + Add another user
DefaultButton(
fromMazaya ? LocaleKeys.addAnotherUser.tr() : LocaleKeys.addNewFamilyMember.tr(),
(canAddFamilyMember || fromMazaya)
? () async {
await Navigator.pushNamed(context, AppRoutes.addDependentVerify, arguments: <String, dynamic>{'fromMazaya': fromMazaya});
getEmployeeContacts();
}
: null,
iconData: Icons.add,
isTextExpanded: false,
height: 48,
borderRadius: 12,
).insideContainer,
],
),
),
);
}
// Same Shimmer.fromColors skeleton-card convention used elsewhere in the app (e.g.
// dashboard_screen_for_dependet.dart's retailer/category shimmers), shaped like the
// real _buildDependentCard layout so the loading state doesn't jump around once it loads.
Widget _buildDependentsShimmer() {
return Shimmer.fromColors(
baseColor: const Color(0xFFE2E8F0),
highlightColor: const Color(0xFFF8FAFC),
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(20, 4, 20, 20),
separatorBuilder: (BuildContext _, int __) => const SizedBox(height: 12),
itemCount: 6,
itemBuilder: (BuildContext _, int __) {
return Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: [
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
"${getEmployeeContactsList[index].cONTACTNAME}".toText16(color: MyColors.grey3AColor),
"${getEmployeeContactsList[index].rELATIONSHIP}".toText11(color: MyColors.textMixColor),
]).paddingOnly(top: 15, left: 13, right: 13, bottom: 12),
1.divider,
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(width: 48, height: 48, decoration: const BoxDecoration(color: Colors.white, shape: BoxShape.circle)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container(height: 18, width: 140, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), // todo @faitma clean below logic and write clean code.
const SizedBox(height: 6), RichText(
Container(height: 14, width: 90, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4))), text: TextSpan(
], children: [
), WidgetSpan(
), child: Icon(
], Icons.edit,
), size: 14,
const SizedBox(height: 14), color: canUpdateFamilyMember ? MyColors.grey67Color : MyColors.lightGreyColor,
Container(height: 1, color: Colors.white), )),
const SizedBox(height: 12), TextSpan(
Center(child: Container(height: 14, width: 150, decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(4)))), text: LocaleKeys.update.tr(),
], style: TextStyle(
), color: canUpdateFamilyMember ? MyColors.grey67Color : MyColors.lightGreyColor, fontSize: 12, letterSpacing: -0.36, fontWeight: FontWeight.w600),
); recognizer: TapGestureRecognizer()
}, ..onTap = () async {
), relationId = getEmployeeContactsList[index].cONTACTRELATIONSHIPID;
); if (canUpdateFamilyMember && relationId != null) {
showUpdateAlertDialog(context, relationId, 2, "UPDATE");
} }
})
Widget _buildDependentCard(GetEmployeeContactsList item, bool isAr, bool canUpdate, bool canDelete) {
bool isAllowed = _isAccessAllowed(item);
return InkWell(
borderRadius: BorderRadius.circular(16),
onTap: () async {
await Navigator.push(context, MaterialPageRoute<dynamic>(builder: (BuildContext cxt) => DependentDetailsScreen(contact: item)));
getEmployeeContacts();
},
child: Container(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[BoxShadow(color: Color(0x0A000000), blurRadius: 8, offset: Offset(0, 3))],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Top row: Avatar, Info, and Status Badge
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// Initials Circle
Container(
width: 48,
height: 48,
decoration: const BoxDecoration(color: Color(0xFFE6F5F3), shape: BoxShape.circle),
child: Center(child: Text(_getInitials(item.cONTACTNAME), style: const TextStyle(color: Color(0xFF168A89), fontWeight: FontWeight.w700, fontSize: 15))),
),
const SizedBox(width: 12),
// Name & Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Flexible(
child: Text(
item.cONTACTNAME ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1E293B)),
),
),
if (_isNhicVerified(item)) ...<Widget>[const SizedBox(width: 8), const Icon(Icons.verified_outlined, color: Color(0xFF059669), size: 20)],
],
),
const SizedBox(height: 3),
Row(
children: <Widget>[
Flexible(child: (item.rELATIONSHIP ?? "").toText12(maxLine: 1, color: const Color(0xFF168A89))),
if (_isNhicVerified(item)) ...<Widget>[
" • ".toText12(color: const Color(0xFF94A3B8)),
LocaleKeys.verified.tr().toText12(weight: FontWeight.w500, color: const Color(0xFF059669)),
],
], ],
), ),
],
), ),
Container(height: 35, width: 1, color: const Color(0xffEFEFEF)),
RichText(
text: TextSpan(
children: [
WidgetSpan(
child: Icon(
Icons.delete,
size: 15,
color: canDeleteFamilyMember ? MyColors.redColor : MyColors.lightGreyColor,
), ),
// Trailing arrow (shown in Mazaya flow): the whole card opens
// DependentDetailsScreen on tap, so this just signals that.
// Same "arrow_next.svg" + RotatedBox-for-RTL convention used in menus_widget.dart
// and services_widget.dart on the dashboard.
if (fromMazaya) ...<Widget>[
const SizedBox(width: 8),
RotatedBox(quarterTurns: isAr ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg", color: const Color(0xFF94A3B8), width: 14)),
],
],
), ),
TextSpan(
const SizedBox(height: 14), text: LocaleKeys.remove.tr(),
const Divider(height: 1, thickness: 1, color: Color(0xFFF1F5F9)),
const SizedBox(height: 12),
// Bottom Action Row
if (fromMazaya) ...<Widget>[
// Mazaya Access Buttons: Revoke or Allow
Center(
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => _confirmToggleMazayaAccess(item),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(isAllowed ? Icons.close : Icons.check, size: isAllowed ? 16 : 18, color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89)),
const SizedBox(width: 6),
Text(
isAllowed ? LocaleKeys.revokeMazayaAccess.tr() : LocaleKeys.allowMazayaAccess.tr(),
style: TextStyle( style: TextStyle(
fontSize: 14, color: canDeleteFamilyMember ? MyColors.redColor : MyColors.lightGreyColor, fontSize: 12, letterSpacing: -0.36, fontWeight: FontWeight.w600),
fontWeight: FontWeight.w600,
color: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
decoration: TextDecoration.underline,
decorationColor: isAllowed ? const Color(0xFFDC2626) : const Color(0xFF168A89),
),
), ),
], ],
), ),
), ).onPress(() {
), relationId = getEmployeeContactsList[index].cONTACTRELATIONSHIPID;
), if (canDeleteFamilyMember && relationId != null) {
] else ...<Widget>[ showRemoveAlertDialog(context, relationId);
// Profile Flow: Update and Remove action buttons
Row(
children: <Widget>[
Expanded(
child: InkWell(
borderRadius: BorderRadius.circular(10),
onTap:
canUpdate
? () {
int? relationId = item.cONTACTRELATIONSHIPID;
if (relationId != null) {
showUpdateAlertDialog(context, relationId, 2, "UPDATE");
}
} }
: null, }),
child: Container(
height: 38,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: canUpdate ? const Color(0xFFCBD5E1) : const Color(0xFFE2E8F0)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.edit_outlined, size: 15, color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8)),
const SizedBox(width: 6),
LocaleKeys.update.tr().toText12(color: canUpdate ? const Color(0xFF475569) : const Color(0xFF94A3B8)),
], ],
), ),
],
).objectContainerView(disablePadding: true);
}),
), ),
), DefaultButton(
), LocaleKeys.addNewFamilyMember.tr(),
const SizedBox(width: 10), canAddFamilyMember
Expanded( ? () async {
child: InkWell( Navigator.pushNamed(
borderRadius: BorderRadius.circular(10), context,
onTap: AppRoutes.addUpdateFamilyMember,
canDelete arguments: FamilyMemberScreenParams(null, flag: 1, actionType: "ADD"),
? () { );
int? relationId = item.cONTACTRELATIONSHIPID;
if (relationId != null) {
showRemoveAlertDialog(context, relationId);
}
} }
: null, : null)
child: Container( .insideContainer,
height: 38,
decoration: BoxDecoration(
color: canDelete ? const Color(0xFFFFF5F5) : Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: canDelete ? const Color(0xFFFFCDD2) : const Color(0xFFE2E8F0)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.delete_outline_rounded, size: 15, color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8)),
const SizedBox(width: 6),
LocaleKeys.remove.tr().toText12(color: canDelete ? const Color(0xFFED1C24) : const Color(0xFF94A3B8)),
], ],
));
}
void showUpdateAlertDialog(BuildContext context, relationId, int flag, String actionType) {
Widget cancelButton = TextButton(
child: Text(
LocaleKeys.cancel.tr(),
), ),
onPressed: () {
Navigator.pop(context);
},
);
Widget continueButton = TextButton(
child: Text(
LocaleKeys.ok.tr(),
), ),
onPressed: () {
Navigator.pop(context);
Navigator.pushNamed(
context,
AppRoutes.addUpdateFamilyMember,
arguments:FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE"),
);
},
);
AlertDialog alert = AlertDialog(
title: Text(
LocaleKeys.confirm.tr(),
), ),
), content: Text(LocaleKeys.updateMember.tr()),
], actions: [
), cancelButton,
], continueButton,
], ],
), );
), showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
); );
} }
void showUpdateAlertDialog(BuildContext context, int relationId, int flag, String actionType) { void showRemoveAlertDialog(BuildContext context, relationId) {
Widget cancelButton = TextButton(child: LocaleKeys.cancel.tr().toText14(), onPressed: () => Navigator.pop(context)); Widget cancelButton = TextButton(
Widget continueButton = TextButton( child: Text(
child: LocaleKeys.ok.tr().toText14(), LocaleKeys.cancel.tr(),
),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pop(context);
Navigator.pushNamed(context, AppRoutes.addUpdateFamilyMember, arguments: FamilyMemberScreenParams(relationId, flag: 2, actionType: "UPDATE")).then((_) => getEmployeeContacts());
}, },
); );
AlertDialog alert = AlertDialog(title: LocaleKeys.confirm.tr().toText16(isBold: true), content: LocaleKeys.updateMember.tr().toText14(), actions: <Widget>[cancelButton, continueButton]);
showDialog(context: context, builder: (BuildContext context) => alert);
}
void showRemoveAlertDialog(BuildContext context, int relationId) {
Widget cancelButton = TextButton(child: LocaleKeys.cancel.tr().toText14(), onPressed: () => Navigator.pop(context));
Widget continueButton = TextButton( Widget continueButton = TextButton(
child: LocaleKeys.ok.tr().toText14(), child: Text(
LocaleKeys.ok.tr(),
),
onPressed: () { onPressed: () {
Navigator.pop(context); Navigator.pushNamed(context, AppRoutes.deleteFamilyMember, arguments: relationId);
Navigator.pushNamed(context, AppRoutes.deleteFamilyMember, arguments: relationId).then((_) => getEmployeeContacts()); // continueDynamicForms();
},
);
AlertDialog alert = AlertDialog(
title: Text(
LocaleKeys.confirm.tr(),
),
content: Text(LocaleKeys.removeThisMember.tr()),
actions: [
cancelButton,
continueButton,
],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
}, },
); );
AlertDialog alert = AlertDialog(title: LocaleKeys.confirm.tr().toText16(isBold: true), content: LocaleKeys.removeThisMember.tr().toText14(), actions: <Widget>[cancelButton, continueButton]);
showDialog(context: context, builder: (BuildContext context) => alert);
} }
} }

@ -1,547 +0,0 @@
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/utils.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
class MazayaAccessResultParams {
final bool isGranted;
final String dependentName;
final String? dependentInitials;
final String? downloadUrl;
final String? nationalId;
final String? dateOfBirth;
const MazayaAccessResultParams({required this.isGranted, required this.dependentName, this.dependentInitials, this.downloadUrl, this.nationalId, this.dateOfBirth});
}
class _ShieldPainter extends CustomPainter {
final Color color;
final bool isGranted;
_ShieldPainter({required this.color, required this.isGranted});
@override
void paint(Canvas canvas, Size size) {
Paint paint =
Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 2.4
..strokeCap = StrokeCap.round
..strokeJoin = StrokeJoin.round;
double w = size.width;
double h = size.height;
// Shield path
Path path = Path();
path.moveTo(w * 0.5, h * 0.1);
path.cubicTo(w * 0.72, h * 0.1, w * 0.88, h * 0.18, w * 0.88, h * 0.38);
path.cubicTo(w * 0.88, h * 0.65, w * 0.68, h * 0.84, w * 0.5, h * 0.92);
path.cubicTo(w * 0.32, h * 0.84, w * 0.12, h * 0.65, w * 0.12, h * 0.38);
path.cubicTo(w * 0.12, h * 0.18, w * 0.28, h * 0.1, w * 0.5, h * 0.1);
path.close();
canvas.drawPath(path, paint);
if (isGranted) {
// Checkmark path
Path checkPath = Path();
checkPath.moveTo(w * 0.34, h * 0.49);
checkPath.lineTo(w * 0.46, h * 0.61);
checkPath.lineTo(w * 0.66, h * 0.39);
canvas.drawPath(checkPath, paint);
} else {
// Cross path
canvas.drawLine(Offset(w * 0.38, h * 0.38), Offset(w * 0.62, h * 0.62), paint);
canvas.drawLine(Offset(w * 0.62, h * 0.38), Offset(w * 0.38, h * 0.62), paint);
}
}
@override
bool shouldRepaint(covariant _ShieldPainter oldDelegate) => color != oldDelegate.color || isGranted != oldDelegate.isGranted;
}
class MazayaAccessResultScreen extends StatelessWidget {
final bool isGranted;
final String dependentName;
final String? dependentInitials;
final String? downloadUrl;
final String? nationalId;
final String? dateOfBirth;
const MazayaAccessResultScreen({Key? key, this.isGranted = true, this.dependentName = "", this.dependentInitials, this.downloadUrl, this.nationalId, this.dateOfBirth}) : super(key: key);
static String getDownloadLink() {
int channel = AppState().postParamsObject?.channel ?? 31;
return channel == 33 ? "https://mohemm.hmg.com/mhmg.html" : "https://mohemm.hmg.com/mohem.html";
}
// Channel 33 (HMG) gets the HMG-branded guide, everything else gets the CS-branded one -
// same channel check as getDownloadLink() - crossed with the current app language.
static String getShareGuidePdfAssetPath(bool isAr) {
int channel = AppState().postParamsObject?.channel ?? 31;
String brand = channel == 33 ? "HMG" : "CS";
String lang = isAr ? "ar" : "en";
return "assets/pdf/Mazaya Access Guide $brand $lang.pdf";
}
String _getInitials(String? name) {
if (name == null || name.trim().isEmpty) return "D";
List<String> parts = name.trim().split(RegExp(r'\s+')).where((String p) => p.isNotEmpty).toList();
if (parts.isEmpty) return "D";
if (parts.length == 1) {
return parts.first.length >= 2 ? parts.first.substring(0, 2).toUpperCase() : parts.first.toUpperCase();
}
return "${parts.first[0]}${parts.last[0]}".toUpperCase();
}
@override
Widget build(BuildContext context) {
bool isAr = AppState().isArabic(context);
String fontFamily = isAr ? 'Cairo' : 'Poppins';
// Resolve arguments if passed via ModalRoute
Object? routeArgs = ModalRoute.of(context)?.settings.arguments;
bool effectiveGranted = isGranted;
String effectiveName = dependentName;
String? effectiveInitials = dependentInitials;
String? effectiveUrl = downloadUrl;
String? effectiveNationalId = nationalId;
String? effectiveDateOfBirth = dateOfBirth;
if (routeArgs is MazayaAccessResultParams) {
effectiveGranted = routeArgs.isGranted;
effectiveName = routeArgs.dependentName;
effectiveInitials = routeArgs.dependentInitials;
effectiveUrl = routeArgs.downloadUrl;
effectiveNationalId = routeArgs.nationalId;
effectiveDateOfBirth = routeArgs.dateOfBirth;
} else if (routeArgs is Map<String, dynamic>) {
effectiveGranted = routeArgs['isGranted'] ?? isGranted;
effectiveName = routeArgs['dependentName'] ?? dependentName;
effectiveInitials = routeArgs['dependentInitials'] ?? dependentInitials;
effectiveUrl = routeArgs['downloadUrl'] ?? downloadUrl;
effectiveNationalId = routeArgs['nationalId'] ?? nationalId;
effectiveDateOfBirth = routeArgs['dateOfBirth'] ?? dateOfBirth;
}
if (effectiveName.trim().isEmpty) {
String fallback = AppState().dependentName ?? "";
if (fallback.isNotEmpty) {
effectiveName = fallback;
} else {
effectiveName = isAr ? "التابع" : "Dependent";
}
}
String linkToShare = effectiveUrl ?? getDownloadLink();
String initials = effectiveInitials ?? _getInitials(effectiveName);
return Scaffold(
backgroundColor: Colors.white,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: <Widget>[
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.arrow_back_ios_new_rounded, size: 20, color: Color(0xFF1E293B))),
),
const SizedBox(width: 4),
LocaleKeys.mazayaTitle.tr().toText20(isBold: true, color: const Color(0xFF1E293B)),
const Spacer(),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () => Navigator.pop(context),
child: const Padding(padding: EdgeInsets.all(8.0), child: Icon(Icons.close_rounded, size: 24, color: Color(0xFF1E293B))),
),
],
),
),
),
),
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 20),
child:
effectiveGranted ? _buildGrantedContent(context, isAr, fontFamily, effectiveName, initials, linkToShare) : _buildRevokedContent(context, isAr, fontFamily, effectiveName, initials),
),
),
_buildBottomButtons(context, isAr, fontFamily, effectiveGranted, linkToShare, effectiveNationalId ?? "", effectiveDateOfBirth ?? ""),
],
),
),
);
}
Widget _buildGrantedContent(BuildContext context, bool isAr, String fontFamily, String name, String initials, String linkToShare) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 8),
// Shield checkmark badge
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(color: Color(0xFFE6F7F2), shape: BoxShape.circle),
child: Center(child: CustomPaint(size: const Size(40, 40), painter: _ShieldPainter(color: const Color(0xFF168A89), isGranted: true))),
),
const SizedBox(height: 18),
// Title
LocaleKeys.mazayaAccessGranted.tr().toText22(isBold: true, isCentered: true, color: const Color(0xFF1E293B)),
const SizedBox(height: 8),
// Description
LocaleKeys.mazayaAccessGrantedDesc.tr(args: <String>[name]).toText14(isCenter: true, color: const Color(0xFF64748B)),
const SizedBox(height: 22),
// Access is now active banner
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(color: const Color(0xFFE8F8F0), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFCEEFE7), width: 1)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 2), child: Icon(Icons.check_circle_outline_rounded, color: Color(0xFF168A89), size: 20)),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.accessIsNowActive.tr().toText14(isBold: true, color: const Color(0xFF168A89)),
const SizedBox(height: 3),
LocaleKeys.accessIsNowActiveDesc.tr(args: <String>[name]).toText12(color: const Color(0xFF64748B)),
],
),
),
],
),
),
const SizedBox(height: 14),
// Dependent Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[BoxShadow(color: Color(0x08000000), blurRadius: 10, offset: Offset(0, 3))],
),
child: Row(
children: <Widget>[
Container(
width: 42,
height: 42,
decoration: const BoxDecoration(color: Color(0xFFE6F7F2), shape: BoxShape.circle),
child: Center(child: initials.toText14(isBold: true, color: const Color(0xFF168A89))),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
name.toText14(isBold: true, maxlines: 1, color: const Color(0xFF1E293B)),
const SizedBox(height: 2),
LocaleKeys.dependentAccessActive.tr().toText12(isBold: true, color: const Color(0xFF168A89)),
],
),
),
const Icon(Icons.lock_open_rounded, color: Color(0xFF168A89), size: 20),
],
),
),
const SizedBox(height: 14),
// Shareable App-Download Link Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFE2E8F0))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 36,
height: 36,
decoration: BoxDecoration(color: const Color(0xFFE8F8F0), borderRadius: BorderRadius.circular(10)),
child: const Center(child: Icon(Icons.smartphone_rounded, color: Color(0xFF168A89), size: 20)),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.shareableAppDownloadLink.tr().toText14(isBold: true, color: const Color(0xFF1E293B)),
const SizedBox(height: 2),
LocaleKeys.shareableAppDownloadLinkDesc.tr(args: <String>[name]).toText12(color: const Color(0xFF64748B)),
],
),
),
],
),
const SizedBox(height: 12),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9),
decoration: BoxDecoration(color: const Color(0xFFF8FAFC), borderRadius: BorderRadius.circular(10), border: Border.all(color: const Color(0xFFE2E8F0))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.downloadLink.tr().toText11(isBold: true, color: const Color(0xFF168A89)),
const SizedBox(height: 3),
linkToShare.toText12(maxLine: 1, color: const Color(0xFF334155)),
],
),
),
],
),
),
],
);
}
Widget _buildRevokedContent(BuildContext context, bool isAr, String fontFamily, String name, String initials) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 8),
// Shield bad badge
Container(
width: 80,
height: 80,
decoration: const BoxDecoration(color: Color(0xFFFEECEB), shape: BoxShape.circle),
child: Center(child: CustomPaint(size: const Size(40, 40), painter: _ShieldPainter(color: const Color(0xFFED1C24), isGranted: false))),
),
const SizedBox(height: 18),
// Title
LocaleKeys.mazayaAccessRevoked.tr().toText22(isBold: true, isCentered: true, color: const Color(0xFF1E293B)),
const SizedBox(height: 8),
// Description
LocaleKeys.mazayaAccessRevokedDesc.tr().toText14(isCenter: true, color: const Color(0xFF64748B)),
const SizedBox(height: 22),
// Contact your sponsor banner
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(color: const Color(0xFFFFF5F5), borderRadius: BorderRadius.circular(14), border: Border.all(color: const Color(0xFFFFCDD2), width: 1)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Padding(padding: EdgeInsets.only(top: 2), child: Icon(Icons.phone_outlined, color: Color(0xFFED1C24), size: 20)),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.contactYourSponsor.tr().toText14(isBold: true, color: const Color(0xFFED1C24)),
const SizedBox(height: 3),
LocaleKeys.contactYourSponsorDesc.tr().toText12(color: const Color(0xFF64748B)),
],
),
),
],
),
),
const SizedBox(height: 14),
// Dependent Card
Container(
width: double.infinity,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: const Color(0xFFF1F5F9)),
boxShadow: const <BoxShadow>[BoxShadow(color: Color(0x08000000), blurRadius: 10, offset: Offset(0, 3))],
),
child: Row(
children: <Widget>[
Container(
width: 42,
height: 42,
decoration: const BoxDecoration(color: Color(0xFFFFF1F0), shape: BoxShape.circle),
child: Center(child: initials.toText14(isBold: true, color: const Color(0xFFED1C24))),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
name.toText14(isBold: true, maxlines: 1, color: const Color(0xFF1E293B)),
const SizedBox(height: 2),
LocaleKeys.dependentAccessBlocked.tr().toText12(isBold: true, color: const Color(0xFFED1C24)),
],
),
),
const Icon(Icons.lock_outline_rounded, color: Color(0xFFED1C24), size: 20),
],
),
),
],
);
}
// Display-only DOB formatting for the share message: "dd-MM-yyyy", no time component.
// The raw value can come in as the API's "/Date(ms+tz)/" wrapper or a plain ISO datetime string
// with a time component - either way we only want the date part.
String _formatDob(String raw) {
String s = raw.trim();
if (s.isEmpty) return "";
try {
RegExp aspNetDateRegex = RegExp(r'/Date\((-?\d+)([+-]\d{4})?\)/');
Match? match = aspNetDateRegex.firstMatch(s);
if (match != null) {
int ms = int.parse(match.group(1)!);
return DateFormat('dd-MM-yyyy').format(DateTime.fromMillisecondsSinceEpoch(ms, isUtc: true));
}
DateTime? parsed = DateTime.tryParse(s);
if (parsed != null) {
return DateFormat('dd-MM-yyyy').format(parsed);
}
} catch (_) {}
// Fallback: strip a time component off a plain "yyyy-MM-dd HH:mm:ss" / "yyyy-MM-ddTHH:mm:ss"
// string we couldn't otherwise parse, rather than showing it as-is with the time attached.
if (s.contains('T')) return s.split('T').first;
if (s.contains(' ')) return s.split(' ').first;
return s;
}
// Same message built for both "Share Download Link" and "Copy Link" - they must always
// carry identical content, so this is the single source of truth for it.
String _buildMazayaInviteMessage(String linkToShare, String nationalId, String dateOfBirth) {
String nationalIdLine = nationalId.trim().isEmpty ? LocaleKeys.nationalIdOrIqamaFallback.tr() : "${LocaleKeys.nationalId.tr()}: $nationalId";
return "$linkToShare\n\n${LocaleKeys.mazayaInviteInstructions.tr()}\n\n$nationalIdLine\n${LocaleKeys.dateOfBirth.tr()}: ${_formatDob(dateOfBirth)}";
}
// Shares the invite text together with the language-appropriate steps PDF from assets.
// Falls back to sharing just the text if the PDF can't be loaded/copied for any reason.
Future<void> _shareMazayaInvite(bool isAr, String linkToShare, String nationalId, String dateOfBirth) async {
String message = _buildMazayaInviteMessage(linkToShare, nationalId, dateOfBirth);
try {
ByteData bytes = await rootBundle.load(getShareGuidePdfAssetPath(isAr));
Directory tempDir = await getTemporaryDirectory();
// Write to a space-free filename locally - the asset path above is what actually matters.
String localFileName = isAr ? "mazaya_access_guide_ar.pdf" : "mazaya_access_guide_en.pdf";
File pdfFile = File('${tempDir.path}/$localFileName');
await pdfFile.writeAsBytes(bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes));
log("final: $message\n\nfile: ${pdfFile.path}");
await SharePlus.instance.share(ShareParams(text: message, files: [XFile(pdfFile.path)]));
} catch (_) {
try {
await SharePlus.instance.share(ShareParams(text: message));
} catch (_) {}
}
}
Widget _buildBottomButtons(BuildContext context, bool isAr, String fontFamily, bool granted, String linkToShare, String nationalId, String dateOfBirth) {
if (granted) {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
DefaultButton(
LocaleKeys.shareDownloadLink.tr(),
() => _shareMazayaInvite(isAr, linkToShare, nationalId, dateOfBirth),
trailingIconData: isAr ? Icons.arrow_back_rounded : Icons.arrow_forward_rounded,
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
const SizedBox(height: 10),
DefaultButton(
LocaleKeys.copyLink.tr(),
() {
Clipboard.setData(ClipboardData(text: _buildMazayaInviteMessage(linkToShare, nationalId, dateOfBirth)));
Utils.showToast(LocaleKeys.linkCopied.tr());
},
colors: const <Color>[Colors.white, Colors.white],
border: Border.all(color: const Color(0xFFCBD5E1)),
textColor: const Color(0xFF168A89),
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
const SizedBox(height: 10),
DefaultButton(
LocaleKeys.done.tr(),
() => Navigator.pop(context),
colors: const <Color>[Colors.white, Colors.white],
border: Border.all(color: const Color(0xFFCBD5E1)),
textColor: const Color(0xFF334155),
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
],
),
);
} else {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(20, 10, 20, 20),
child: DefaultButton(
LocaleKeys.returnToSignIn.tr(),
() => Navigator.pop(context),
colors: const <Color>[Colors.white, Colors.white],
border: Border.all(color: const Color(0xFFCEEFE7), width: 1.2),
textColor: const Color(0xFF168A89),
iconData: isAr ? Icons.arrow_forward_rounded : Icons.arrow_back_rounded,
fontSize: 14,
isTextExpanded: false,
height: 48,
borderRadius: 12,
),
);
}
}
}

@ -2,7 +2,8 @@ import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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:flutter_html/flutter_html.dart';
import 'package:mohem_flutter_app/api/pending_transactions_api_client.dart'; import 'package:mohem_flutter_app/api/pending_transactions_api_client.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
@ -59,8 +60,8 @@ class _AnnouncementDetailsState extends State<AnnouncementDetails> {
), ),
), ),
), ),
Html( HtmlWidget(
data: AppState().isArabic(context) ? getAnnouncementDetailsObj?.bodyAR : getAnnouncementDetailsObj?.bodyEN, AppState().isArabic(context) ? getAnnouncementDetailsObj?.bodyAR??"" : getAnnouncementDetailsObj?.bodyEN ??"",
).paddingOnly(top: 12), ).paddingOnly(top: 12),
], ],
).objectContainerView().paddingAll(21), ).objectContainerView().paddingAll(21),

@ -173,7 +173,7 @@ class _MowadhafhiHRRequestState extends State<MowadhafhiHRRequest> {
} }
bool checkValidation() { bool checkValidation() {
if (selectedServiceType == null || selectedDepartment == null || selectedSection == null || selectedTopic == null || description == "") { if (selectedServiceType == null || selectedDepartment == null || selectedSection == null || selectedTopic == null) {
return false; return false;
} else { } else {
return true; return true;
@ -192,78 +192,10 @@ class _MowadhafhiHRRequestState extends State<MowadhafhiHRRequest> {
title.toText16().expanded, title.toText16().expanded,
6.width, 6.width,
SimpleButton(LocaleKeys.add.tr(), () async { SimpleButton(LocaleKeys.add.tr(), () async {
FilePickerResult? result = await FilePicker.platform.pickFiles( List<PlatformFile>? result = await FilePicker.pickFiles();
allowMultiple: true,
);
if (result != null) { if (result != null) {
// Maximum file size: 2 MB (in bytes) attachmentFiles = attachmentFiles + result.map((path) => File(path.uri.toFilePath())).toList();
const int maxFileSizeInBytes = 2 * 1024 * 1024; // 2 MB = 2097152 bytes
List<File> newFiles = [];
List<String> oversizedFiles = [];
for (PlatformFile platformFile in result.files) {
if (platformFile.path != null) {
File file = File(platformFile.path!);
// Get file size - check multiple sources for iOS compatibility
int fileSize = 0;
// Method 1: Check platformFile.size first
if (platformFile.size > 0) {
fileSize = platformFile.size;
debugPrint('Using platformFile.size: $fileSize bytes');
}
// Method 2: Always verify with file.length() as well
try {
int fileLengthSize = await file.length();
debugPrint('Using file.length(): $fileLengthSize bytes');
// Use the maximum of both to ensure we catch the real size
if (fileLengthSize > fileSize) {
fileSize = fileLengthSize;
debugPrint('Using larger value from file.length()');
}
} catch (e) {
debugPrint('Error getting file.length(): $e');
// If file.length() fails, rely on platformFile.size
if (fileSize == 0) {
debugPrint('Cannot determine file size, rejecting file: ${platformFile.name}');
oversizedFiles.add('${platformFile.name} (unknown size)');
continue;
}
}
double fileSizeMB = fileSize / (1024 * 1024);
debugPrint('Final file size - ${platformFile.name}: $fileSize bytes (${fileSizeMB.toStringAsFixed(2)} MB)');
// STRICT validation: Only accept files that are 2 MB or less
if (fileSize > 0 && fileSize <= maxFileSizeInBytes) {
debugPrint('✓ File accepted: ${platformFile.name}');
newFiles.add(file);
} else if (fileSize > maxFileSizeInBytes) {
debugPrint('✗ File REJECTED (too large): ${platformFile.name} - ${fileSizeMB.toStringAsFixed(2)} MB');
oversizedFiles.add('${platformFile.name} (${fileSizeMB.toStringAsFixed(2)} MB)');
} else {
debugPrint('✗ File REJECTED (invalid size): ${platformFile.name}');
oversizedFiles.add('${platformFile.name} (invalid)');
}
}
}
// Only add valid files
if (newFiles.isNotEmpty) {
attachmentFiles = attachmentFiles + newFiles;
attachmentFiles = attachmentFiles.toSet().toList(); attachmentFiles = attachmentFiles.toSet().toList();
debugPrint('Total files attached: ${attachmentFiles.length}');
}
// Show error message for rejected files
if (oversizedFiles.isNotEmpty) {
String fileList = oversizedFiles.join('\n');
Utils.showToast('Max 2MB File allowed.');
debugPrint('Total rejected files: ${oversizedFiles.length}');
}
setState(() {}); setState(() {});
} }
}, fontSize: 14), }, fontSize: 14),

@ -92,17 +92,22 @@ class _MyDocumentDetailScreenState extends State<MyDocumentDetailScreen> {
), ),
12.height, 12.height,
DottedBorder( DottedBorder(
borderType: BorderType.RRect, options: RoundedRectDottedBorderOptions(
radius: const Radius.circular(10), radius: const Radius.circular(10),
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
dashPattern: const <double>[2, 1], dashPattern: const <double>[2, 1],
color: MyColors.selectedBorderColor, color: MyColors.selectedBorderColor,
),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Transform.rotate( Transform.rotate(
angle: 45, angle: 45,
child: const Icon(Icons.attach_file_rounded, size: 16, color: MyColors.selectedBorderColor), child: const Icon(
Icons.attach_file_rounded,
size: 16,
color: MyColors.selectedBorderColor,
),
), ),
4.width, 4.width,
"Attach Image".toText14(color: MyColors.selectedBorderColor), "Attach Image".toText14(color: MyColors.selectedBorderColor),

@ -67,10 +67,10 @@ class _MyRequestsState extends State<MyRequests> {
isReadOnly: false, isReadOnly: false,
).paddingOnly(bottom: 12), ).paddingOnly(bottom: 12),
itemBuilder: (_) => <PopupMenuItem<int>>[ itemBuilder: (_) => <PopupMenuItem<int>>[
for (int i = 0; i < getConcurrentProgramsList!.length; i++) PopupMenuItem<int>(child: Text(getConcurrentProgramsList![i].uSERCONCURRENTPROGRAMNAME!), value: i), for (int i = 0; i < getConcurrentProgramsList.length; i++) PopupMenuItem<int>(child: Text(getConcurrentProgramsList[i].uSERCONCURRENTPROGRAMNAME!), value: i),
], ],
onSelected: (int popupIndex) { onSelected: (int popupIndex) {
selectedConcurrentProgramList = getConcurrentProgramsList![popupIndex]; selectedConcurrentProgramList = getConcurrentProgramsList[popupIndex];
getCCPTransactions(selectedConcurrentProgramList?.cONCURRENTPROGRAMNAME); getCCPTransactions(selectedConcurrentProgramList?.cONCURRENTPROGRAMNAME);
setState(() {}); setState(() {});
}), }),
@ -105,7 +105,7 @@ class _MyRequestsState extends State<MyRequests> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
("Request ID: " + getCCPTransactionsList[index].rEQUESTID.toString()).toText12(color: MyColors.grey57Color), ("Request ID: " + getCCPTransactionsList[index].rEQUESTID.toString()).toText12(color: MyColors.grey57Color),
DateUtil.formatDateToDate(DateUtil.convertStringToDate(getCCPTransactionsList[index].rEQUESTDATE!), false).toText12(color: MyColors.grey70Color), DateUtil.formatDateToDate(DateUtil.convertStringToDate(getCCPTransactionsList[index].rEQUESTDATE), false).toText12(color: MyColors.grey70Color),
], ],
), ),
Container( Container(

@ -6,8 +6,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
import 'package:mohem_flutter_app/app_state/app_state.dart'; import 'package:mohem_flutter_app/app_state/app_state.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart'; import 'package:mohem_flutter_app/extensions/int_extensions.dart';
@ -16,7 +16,7 @@ import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart'; import 'package:mohem_flutter_app/models/offers_and_discounts/get_offers_list.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart'; // import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class OffersAndDiscountsDetails extends StatefulWidget { class OffersAndDiscountsDetails extends StatefulWidget {
@ -68,10 +68,12 @@ class _OffersAndDiscountsDetailsState extends State<OffersAndDiscountsDetails> {
AppState().isArabic(context) AppState().isArabic(context)
? getOffersList[0].titleAr!.toText22(isBold: true, color: const Color(0xff2B353E)).center ? getOffersList[0].titleAr!.toText22(isBold: true, color: const Color(0xff2B353E)).center
: getOffersList[0].titleEn!.toText22(isBold: true, color: const Color(0xff2B353E)).center, : getOffersList[0].titleEn!.toText22(isBold: true, color: const Color(0xff2B353E)).center,
Html( HtmlWidget(
data: AppState().isArabic(context) ? getOffersList[0].descriptionAr! : getOffersList[0].descriptionEn ?? "", AppState().isArabic(context) ? getOffersList[0].descriptionAr! : getOffersList[0].descriptionEn ?? "",
onLinkTap: (String? url, Map<String, String> attributes, _) { onTapUrl: (String? url) {
launchUrl(Uri.parse(url!)); launchUrl(Uri.parse(url!));
return true;
}, },
), ),
checkDate(getOffersList[0].endDate!).paddingOnly(left: 8), checkDate(getOffersList[0].endDate!).paddingOnly(left: 8),
@ -122,7 +124,7 @@ class _OffersAndDiscountsDetailsState extends State<OffersAndDiscountsDetails> {
Directory tempDir = await getTemporaryDirectory(); Directory tempDir = await getTemporaryDirectory();
File file = await File('${tempDir.path}/${DateTime.now().toString()}.png').create(); File file = await File('${tempDir.path}/${DateTime.now().toString()}.png').create();
await file.writeAsBytes(pngBytes); await file.writeAsBytes(pngBytes);
await SharePlus.instance.share(ShareParams(files: [(XFile(file.path))], text: AppState().isArabic(context) ? getOffersList[0].titleAr : getOffersList[0].titleEn)); // await SharePlus.instance.share(ShareParams(files: [(XFile(file.path))], text: AppState().isArabic(context) ? getOffersList[0].titleAr : getOffersList[0].titleEn));
} catch (ex) { } catch (ex) {
debugPrint(ex.toString()); debugPrint(ex.toString());
} }

@ -211,7 +211,7 @@ class _ItemHistoryScreenState extends State<ItemHistoryScreen> {
interval: 1, interval: 1,
getTitlesWidget: (double value, TitleMeta meta) { getTitlesWidget: (double value, TitleMeta meta) {
return SideTitleWidget( return SideTitleWidget(
axisSide: meta.axisSide, meta: meta,
child: Text(reversedList[int.parse(meta.formattedValue)].cREATIONDATE!, style: TextStyle(fontSize: 10)), child: Text(reversedList[int.parse(meta.formattedValue)].cREATIONDATE!, style: TextStyle(fontSize: 10)),
); );
})), })),
@ -222,7 +222,7 @@ class _ItemHistoryScreenState extends State<ItemHistoryScreen> {
interval: 1, interval: 1,
getTitlesWidget: (double value, TitleMeta meta) { getTitlesWidget: (double value, TitleMeta meta) {
return SideTitleWidget( return SideTitleWidget(
axisSide: meta.axisSide, meta: meta,
child: Text(meta.formattedValue, style: TextStyle(fontSize: 10)), child: Text(meta.formattedValue, style: TextStyle(fontSize: 10)),
); );
})), })),
@ -233,7 +233,7 @@ class _ItemHistoryScreenState extends State<ItemHistoryScreen> {
reservedSize: 15, reservedSize: 15,
getTitlesWidget: (double value, TitleMeta meta) { getTitlesWidget: (double value, TitleMeta meta) {
return SideTitleWidget( return SideTitleWidget(
axisSide: meta.axisSide, meta: meta,
child: Text("", style: TextStyle(fontSize: 10)), child: Text("", style: TextStyle(fontSize: 10)),
); );
})), })),

@ -496,7 +496,7 @@ class _WorkListScreenState extends State<WorkListScreen> {
10.height, 10.height,
Row( Row(
children: [ children: [
DateUtil.formatDateToDate(DateUtil.convertStringToDate(requestDetails.modifiedDate!), false).toText10(color: MyColors.lightTextColor).expanded, DateUtil.formatDateToDate(DateUtil.convertStringToDate(requestDetails.modifiedDate), false).toText10(color: MyColors.lightTextColor).expanded,
RotatedBox(quarterTurns: AppState().isArabic(context) ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.darkIconColor)), RotatedBox(quarterTurns: AppState().isArabic(context) ? 2 : 4, child: SvgPicture.asset("assets/images/arrow_next.svg", color: MyColors.darkIconColor)),
], ],
), ),

@ -751,7 +751,7 @@ class InfoFragment extends StatelessWidget {
ItemDetailGrid( ItemDetailGrid(
ItemDetailViewCol( LocaleKeys.requestDate.tr(), ItemDetailViewCol( LocaleKeys.requestDate.tr(),
DateUtil.formatDateToDate( DateUtil.formatDateToDate(
DateUtil.convertStringToDate(data.paymentDetailsList![0].requestDate!), false) ?? ""), DateUtil.convertStringToDate(data.paymentDetailsList![0].requestDate), false) ?? ""),
ItemDetailViewCol( ItemDetailViewCol(
LocaleKeys.requesterEmpName.tr(), data.paymentDetailsList![0].requesterEmployeeName ?? ""), LocaleKeys.requesterEmpName.tr(), data.paymentDetailsList![0].requesterEmployeeName ?? ""),
), ),

@ -1,177 +0,0 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:mohem_flutter_app/classes/consts.dart';
import 'package:mohem_flutter_app/config/routes.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/app_bar_widget.dart';
import 'package:permission_handler/permission_handler.dart';
class AppInAppWebViewScreen extends StatefulWidget {
final String url;
final String? title;
final void Function(InAppWebViewController controller)? onPageLoaded;
const AppInAppWebViewScreen({
Key? key,
required this.url,
this.title,
this.onPageLoaded,
}) : super(key: key);
@override
State<AppInAppWebViewScreen> createState() => _AppInAppWebViewScreenState();
}
class _AppInAppWebViewScreenState extends State<AppInAppWebViewScreen> {
InAppWebViewController? _webViewController;
double _progress = 0;
String _title = "";
@override
void initState() {
super.initState();
_title = widget.title ?? "";
_requestCameraPermission();
}
Future<void> _requestCameraPermission() async {
try {
PermissionStatus cameraStatus = await Permission.camera.status;
if (!cameraStatus.isGranted) {
await Permission.camera.request();
}
} catch (e) {
debugPrint("Error requesting camera permission: $e");
}
}
void _onHomeTapped() {
if (ApiConsts.isDependetLogin) {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboardScreenForDependet));
} else {
Navigator.popUntil(context, ModalRoute.withName(AppRoutes.dashboard));
}
}
Future<void> _handleBack() async {
if (_webViewController != null && await _webViewController!.canGoBack()) {
await _webViewController!.goBack();
} else {
if (mounted) {
Navigator.pop(context);
}
}
}
@override
Widget build(BuildContext context) {
String displayTitle = _title.isNotEmpty ? _title : LocaleKeys.offerDetails.tr();
return PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) async {
if (didPop) return;
await _handleBack();
},
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBarWidget(
context,
title: displayTitle,
showHomeButton: true,
onHomeTapped: _onHomeTapped,
onBackTapped: () async {
await _handleBack();
},
),
body: SafeArea(
child: Stack(
children: <Widget>[
InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(widget.url)),
initialSettings: InAppWebViewSettings(
javaScriptEnabled: true,
domStorageEnabled: true,
supportZoom: true,
mediaPlaybackRequiresUserGesture: false,
allowsInlineMediaPlayback: true,
javaScriptCanOpenWindowsAutomatically: true,
transparentBackground: false,
useShouldOverrideUrlLoading: false,
cacheEnabled: true,
clearCache: false,
),
onWebViewCreated: (InAppWebViewController controller) {
_webViewController = controller;
},
onPermissionRequest: (InAppWebViewController controller, PermissionRequest permissionRequest) async {
debugPrint("InAppWebView onPermissionRequest: ${permissionRequest.resources}");
bool isGranted = false;
try {
PermissionStatus status = await Permission.camera.status;
isGranted = status.isGranted;
// IMPORTANT: never await Permission.camera.request() here. The native WebView
// is blocked waiting on THIS callback's response to the page's getUserMedia()
// call - if we hold it open for however long the human takes to respond to the
// OS dialog (can be 30s+), Chromium abandons the original getUserMedia() call
// as timed out before we ever answer. So: answer immediately with whatever the
// status already is, and if not granted, kick off the slow dialog in the
// background. Once that resolves, retry the click trigger so the page makes a
// FRESH getUserMedia() call - which resolves instantly next time since the OS
// permission is already durably granted by then (no dialog, no timeout risk).
if (!isGranted) {
unawaited(
Permission.camera.request().then((PermissionStatus result) {
if (result.isGranted) {
widget.onPageLoaded?.call(controller);
}
}),
);
}
} catch (e) {
debugPrint("Error checking camera permission: $e");
}
return PermissionResponse(
resources: permissionRequest.resources,
action: isGranted ? PermissionResponseAction.GRANT : PermissionResponseAction.DENY,
);
},
onProgressChanged: (InAppWebViewController controller, int progress) {
if (mounted) {
setState(() {
_progress = progress / 100;
});
}
},
onTitleChanged: (InAppWebViewController controller, String? title) {
if (widget.title == null || widget.title!.isEmpty) {
if (title != null && title.isNotEmpty && !title.startsWith("http")) {
if (mounted) {
setState(() {
_title = title;
});
}
}
}
},
onLoadStop: (InAppWebViewController controller, WebUri? url) async {
widget.onPageLoaded?.call(controller);
},
),
if (_progress < 1.0)
LinearProgressIndicator(
value: _progress,
color: const Color(0xFF168A89),
backgroundColor: Colors.transparent,
minHeight: 2.5,
),
],
),
),
),
);
}
}

@ -1,10 +1,8 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart'; import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; import 'package:mohem_flutter_app/extensions/widget_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
class AttachmentOptions extends StatelessWidget { class AttachmentOptions extends StatelessWidget {
VoidCallback onCameraTap; VoidCallback onCameraTap;
@ -21,16 +19,16 @@ class AttachmentOptions extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
LocaleKeys.uploadAttachment.tr().toSectionHeading(), "Upload Attachment".toSectionHeading(),
LocaleKeys.selectFromGalleryOrOpenCamera.tr().toText11(weight: FontWeight.w500), "Select from gallery or open camera".toText11(weight: FontWeight.w500),
GridView( GridView(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 105 / 105, crossAxisSpacing: 9, mainAxisSpacing: 9), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 105 / 105, crossAxisSpacing: 9, mainAxisSpacing: 9),
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(top: 21, bottom: 14), padding: const EdgeInsets.only(top: 21, bottom: 14),
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
itemView("open_camera.svg", LocaleKeys.openCamera.tr(), onCameraTap), itemView("open_camera.svg", "Open\nCamera", onCameraTap),
itemView("gallery.svg", LocaleKeys.uploadFromGallery.tr(), onGalleryTap), itemView("gallery.svg", "Upload from\nGallery", onGalleryTap),
if (showFilesOption) itemView("files.svg", "Upload from\nFiles", onFilesTap), if (showFilesOption) itemView("files.svg", "Upload from\nFiles", onFilesTap),
], ],
) )

@ -1,166 +0,0 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:mohem_flutter_app/classes/colors.dart';
import 'package:mohem_flutter_app/extensions/int_extensions.dart';
import 'package:mohem_flutter_app/extensions/string_extensions.dart';
import 'package:mohem_flutter_app/generated/locale_keys.g.dart';
import 'package:mohem_flutter_app/widgets/button/default_button.dart';
/// Generic, fully customizable confirmation bottom sheet matching the app's visual theme
/// (same handle bar / rounded-top-sheet / gradient button look used across other sheets).
/// Everything is driven by the caller - title/message/button text/colors/icon/callbacks -
/// with sensible defaults so a minimal call still looks consistent with the rest of the app.
class ConfirmBottomSheet extends StatelessWidget {
final String? title;
final String message;
final String? confirmText;
final String? cancelText;
final VoidCallback onConfirm;
final VoidCallback? onCancel;
final bool isDestructive;
final bool showCancelButton;
final IconData? icon;
final Color? iconColor;
final Color? iconBackgroundColor;
final List<Color>? confirmButtonColors;
final Color? confirmButtonTextColor;
final Color? cancelButtonColor;
final Color? cancelButtonTextColor;
const ConfirmBottomSheet({
Key? key,
this.title,
required this.message,
this.confirmText,
this.cancelText,
required this.onConfirm,
this.onCancel,
this.isDestructive = false,
this.showCancelButton = true,
this.icon,
this.iconColor,
this.iconBackgroundColor,
this.confirmButtonColors,
this.confirmButtonTextColor,
this.cancelButtonColor,
this.cancelButtonTextColor,
}) : super(key: key);
/// Convenience launcher - mirrors Utils.confirmDialog's call shape but as a bottom sheet.
static Future<void> show(
BuildContext context, {
String? title,
required String message,
String? confirmText,
String? cancelText,
required VoidCallback onConfirm,
VoidCallback? onCancel,
bool isDestructive = false,
bool showCancelButton = true,
IconData? icon,
Color? iconColor,
Color? iconBackgroundColor,
List<Color>? confirmButtonColors,
Color? confirmButtonTextColor,
Color? cancelButtonColor,
Color? cancelButtonTextColor,
}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (BuildContext sheetContext) {
return ConfirmBottomSheet(
title: title,
message: message,
confirmText: confirmText,
cancelText: cancelText,
onConfirm: onConfirm,
onCancel: onCancel,
isDestructive: isDestructive,
showCancelButton: showCancelButton,
icon: icon,
iconColor: iconColor,
iconBackgroundColor: iconBackgroundColor,
confirmButtonColors: confirmButtonColors,
confirmButtonTextColor: confirmButtonTextColor,
cancelButtonColor: cancelButtonColor,
cancelButtonTextColor: cancelButtonTextColor,
);
},
);
}
@override
Widget build(BuildContext context) {
Color effectiveIconColor = iconColor ?? (isDestructive ? MyColors.redColor : MyColors.textMixColor);
Color effectiveIconBg = iconBackgroundColor ?? effectiveIconColor.withOpacity(0.1);
List<Color> effectiveConfirmColors = confirmButtonColors ?? (isDestructive ? const [MyColors.redA3Color, MyColors.redColor] : const [MyColors.gradiantEndColor, MyColors.gradiantStartColor]);
return SafeArea(
top: false,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 12, 20, 20),
decoration: const BoxDecoration(color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(24))),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Drag handle
Center(
child: Container(width: 44, height: 4.5, margin: const EdgeInsets.only(bottom: 18), decoration: BoxDecoration(color: const Color(0xFFCBD5E1), borderRadius: BorderRadius.circular(2.5))),
),
if (icon != null) ...[
Center(
child: Container(
width: 56,
height: 56,
alignment: Alignment.center,
decoration: BoxDecoration(color: effectiveIconBg, shape: BoxShape.circle),
child: Icon(icon, color: effectiveIconColor, size: 28),
),
),
16.height,
],
(title ?? LocaleKeys.confirm.tr()).toText18(isBold: true, isCentered: true, color: MyColors.darkTextColor),
8.height,
message.toText14(color: MyColors.grey70Color, textAlign: TextAlign.center),
24.height,
Row(
children: [
if (showCancelButton) ...[
Expanded(
child: DefaultButton(
cancelText ?? LocaleKeys.cancel.tr(),
() {
Navigator.pop(context);
onCancel?.call();
},
colors: [cancelButtonColor ?? MyColors.lightGreyEAColor, cancelButtonColor ?? MyColors.lightGreyEAColor],
textColor: cancelButtonTextColor ?? MyColors.darkTextColor,
),
),
12.width,
],
Expanded(
child: DefaultButton(
confirmText ?? LocaleKeys.ok.tr(),
() {
Navigator.pop(context);
onConfirm();
},
colors: effectiveConfirmColors,
textColor: confirmButtonTextColor ?? Colors.white,
),
),
],
),
],
),
),
);
}
}

@ -17,15 +17,11 @@ class DefaultButton extends StatelessWidget {
final Color? color; final Color? color;
final Color? disabledColor; final Color? disabledColor;
final IconData? iconData; final IconData? iconData;
final IconData? trailingIconData;
final String? svgIcon; final String? svgIcon;
final double? fontSize; final double? fontSize;
final bool isTextExpanded; final bool isTextExpanded;
final int count; final int count;
final List<Color>? colors; final List<Color>? colors;
final BoxBorder? border;
final double? borderRadius;
final double? height;
const DefaultButton( const DefaultButton(
this.text, this.text,
@ -38,12 +34,8 @@ class DefaultButton extends StatelessWidget {
this.count = 0, this.count = 0,
this.textColor = Colors.white, this.textColor = Colors.white,
this.iconData, this.iconData,
this.trailingIconData,
this.fontSize, this.fontSize,
this.colors, this.colors,
this.border,
this.borderRadius,
this.height,
}) : super(key: key); }) : super(key: key);
@override @override
@ -51,10 +43,9 @@ class DefaultButton extends StatelessWidget {
return InkWell( return InkWell(
onTap: onPress, onTap: onPress,
child: Container( child: Container(
height: height ?? 43, height: 43,
decoration: BoxDecoration( decoration: BoxDecoration(
border: border, borderRadius: BorderRadius.circular(6.0),
borderRadius: BorderRadius.circular(borderRadius ?? 6.0),
gradient: onPress == null gradient: onPress == null
? LinearGradient( ? LinearGradient(
colors: <Color>[ colors: <Color>[
@ -76,12 +67,12 @@ class DefaultButton extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
if (iconData != null) Icon(iconData, color: textColor, size: 20), if (iconData != null) Icon(iconData, color: textColor),
if (svgIcon != null) SvgPicture.asset(svgIcon ?? "", color: textColor), if (svgIcon != null) SvgPicture.asset(svgIcon ?? "", color: textColor),
if (!isTextExpanded) if (!isTextExpanded)
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.only(
horizontal: (iconData ?? svgIcon ?? trailingIconData) != null ? 8 : 0, left: (iconData ?? svgIcon) != null ? 6 : 0,
), ),
child: Text( child: Text(
text, text,
@ -107,7 +98,6 @@ class DefaultButton extends StatelessWidget {
), ),
), ),
), ),
if (trailingIconData != null) Icon(trailingIconData, color: textColor, size: 20),
if (count > 0) if (count > 0)
Align( Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,

@ -44,7 +44,7 @@ class ConfirmDialog extends StatelessWidget {
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
onPressed: () => onCloseTap ?? Navigator.pop(context), onPressed: () => onCloseTap ?? Navigator.pop(context),
// onPressed: () => Navigator.pop(context), // onPressed: () => Navigator.pop(context),
), )
], ],
), ),
message.toText16(color: MyColors.lightGrayColor), message.toText16(color: MyColors.lightGrayColor),

@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mohem_flutter_app/classes/colors.dart'; import 'package:mohem_flutter_app/classes/colors.dart';
class DynamicTextFieldWidget extends StatelessWidget { class DynamicTextFieldWidget extends StatelessWidget {
final String labelText; final String labelText;
final String hintText; final String hintText;
final TextEditingController? controller;
// final TextEditingController controller;
final VoidCallback? onTap; final VoidCallback? onTap;
final IconData? suffixIconData; final IconData? suffixIconData;
final bool isEnable; final bool isEnable;
@ -18,22 +18,9 @@ class DynamicTextFieldWidget extends StatelessWidget {
final bool isObscureText; final bool isObscureText;
final bool isBackgroundEnable; final bool isBackgroundEnable;
final void Function(String)? onChange; final void Function(String)? onChange;
final int? maxLength;
final List<TextInputFormatter>? inputFormatters;
final Color? backgroundColor;
final Color? borderColor;
final double? borderWidth;
final Color? labelColor;
final Color? textColor;
final Color? suffixIconColor;
final BorderRadius? borderRadius;
final List<BoxShadow>? boxShadow;
final BoxConstraints? constraints;
final EdgeInsetsGeometry? padding;
DynamicTextFieldWidget(this.labelText, this.hintText, DynamicTextFieldWidget(this.labelText, this.hintText, //this.controller,
{this.controller, {this.isObscureText = false,
this.isObscureText = false,
this.onTap, this.onTap,
this.suffixIconData, this.suffixIconData,
this.isEnable = true, this.isEnable = true,
@ -44,40 +31,20 @@ class DynamicTextFieldWidget extends StatelessWidget {
this.onChange, this.onChange,
this.isInputTypeNum = false, this.isInputTypeNum = false,
this.isInputTypeNumSigned = true, this.isInputTypeNumSigned = true,
this.isBackgroundEnable = false, this.isBackgroundEnable = false});
this.maxLength,
this.inputFormatters,
this.backgroundColor,
this.borderColor,
this.borderWidth,
this.labelColor,
this.textColor,
this.suffixIconColor,
this.borderRadius,
this.boxShadow,
this.constraints,
this.padding});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color effectiveBorderColor = borderColor ?? MyColors.lightGreyEFColor;
double effectiveBorderWidth = borderWidth ?? 1.0;
Color effectiveLabelColor = labelColor ?? const Color(0xff2B353E);
Color effectiveTextColor = textColor ?? const Color(0xff2B353E);
Color effectiveSuffixColor = suffixIconColor ?? MyColors.darkIconColor;
return Container( return Container(
constraints: constraints, padding: const EdgeInsets.only(left: 16, right: 16, bottom: 15, top: 15),
padding: padding ?? const EdgeInsets.only(left: 16, right: 16, bottom: 15, top: 15),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius ?? BorderRadius.circular(15), borderRadius: BorderRadius.circular(15),
color: backgroundColor ?? (isReadOnly ? MyColors.lightGreyEFColor : Colors.white), color: isReadOnly ? MyColors.lightGreyEFColor : Colors.white,
border: Border.all( border: Border.all(
color: effectiveBorderColor, color: MyColors.lightGreyEFColor,
width: effectiveBorderWidth, width: 1,
), ),
boxShadow: boxShadow,
), ),
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
@ -91,15 +58,14 @@ class DynamicTextFieldWidget extends StatelessWidget {
if (labelText.isNotEmpty) if (labelText.isNotEmpty)
Text( Text(
labelText, labelText,
style: TextStyle( style: const TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: effectiveLabelColor, color: Color(0xff2B353E),
letterSpacing: -0.44, letterSpacing: -0.44,
), ),
), ),
TextField( TextField(
controller: controller,
enabled: isEnable, enabled: isEnable,
scrollPadding: EdgeInsets.zero, scrollPadding: EdgeInsets.zero,
readOnly: isReadOnly, readOnly: isReadOnly,
@ -107,24 +73,22 @@ class DynamicTextFieldWidget extends StatelessWidget {
? (isInputTypeNumSigned ? const TextInputType.numberWithOptions(signed: true, decimal: true) : TextInputType.numberWithOptions(signed: true, decimal: true)) ? (isInputTypeNumSigned ? const TextInputType.numberWithOptions(signed: true, decimal: true) : TextInputType.numberWithOptions(signed: true, decimal: true))
: TextInputType.text, : TextInputType.text,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
//controller: controller,
maxLines: lines, maxLines: lines,
maxLength: maxLength,
inputFormatters: inputFormatters,
obscuringCharacter: "*", obscuringCharacter: "*",
obscureText: isObscureText, obscureText: isObscureText,
onChanged: onChange, onChanged: onChange,
style: TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
height: 21 / 14, height: 21 / 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: effectiveTextColor, color: Color(0xff2B353E),
letterSpacing: -0.44, letterSpacing: -0.44,
), ),
decoration: InputDecoration( decoration: InputDecoration(
counterText: "",
isDense: true, isDense: true,
hintText: hintText, hintText: hintText,
fillColor: backgroundColor ?? (isReadOnly ? MyColors.borderColor : null), fillColor: isReadOnly ? MyColors.borderColor : null,
hintStyle: const TextStyle( hintStyle: const TextStyle(
fontSize: 14, fontSize: 14,
height: 21 / 14, height: 21 / 14,
@ -132,6 +96,9 @@ class DynamicTextFieldWidget extends StatelessWidget {
color: Color(0xff575757), color: Color(0xff575757),
letterSpacing: -0.56, letterSpacing: -0.56,
), ),
// suffixIconConstraints: const BoxConstraints(minWidth: 50),
// suffixIcon: suffixIconData == null ? null : Icon(suffixIconData, color: MyColors.darkTextColor),
// suffixIcon: suffixTap == null ? null : IconButton(icon: Icon(suffixIconData, color: MyColors.darkTextColor), onPressed: suffixTap),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
border: InputBorder.none, border: InputBorder.none,
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,
@ -141,12 +108,8 @@ class DynamicTextFieldWidget extends StatelessWidget {
], ],
), ),
), ),
if (isPopup) if (isPopup) const Icon(Icons.keyboard_arrow_down_outlined, color: MyColors.darkIconColor),
Icon(Icons.keyboard_arrow_down_outlined, color: effectiveSuffixColor) if (onTap != null) Icon(suffixIconData ?? Icons.keyboard_arrow_down_outlined, color: MyColors.darkIconColor),
else if (onTap != null)
Icon(suffixIconData ?? Icons.keyboard_arrow_down_outlined, color: effectiveSuffixColor)
else if (suffixIconData != null)
Icon(suffixIconData, color: effectiveSuffixColor),
], ],
), ),
), ),

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

Loading…
Cancel
Save