diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 0ffb97db..2987d3b1 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -156,16 +156,16 @@ dependencies {
implementation("com.intuit.ssp:ssp-android:1.1.0")
implementation("com.intuit.sdp:sdp-android:1.1.0")
-// implementation("com.github.bumptech.glide:glide:4.16.0")
-// annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
+ implementation("com.github.bumptech.glide:glide:4.16.0")
+ annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
implementation("com.mapbox.maps:android:11.5.0")
// implementation("com.mapbox.maps:android:11.4.0")
// AARs
-// implementation(files("libs/PenNavUI.aar"))
-// implementation(files("libs/Penguin.aar"))
-// implementation(files("libs/PenguinRenderer.aar"))
+ implementation(files("libs/PenNavUI.aar"))
+ implementation(files("libs/Penguin.aar"))
+ implementation(files("libs/PenguinRenderer.aar"))
implementation("com.github.kittinunf.fuel:fuel:2.3.1")
implementation("com.github.kittinunf.fuel:fuel-android:2.3.1")
@@ -180,9 +180,11 @@ dependencies {
implementation("com.google.android.material:material:1.12.0")
implementation("pl.droidsonroids.gif:android-gif-drawable:1.2.25")
+ implementation("com.mapbox.mapboxsdk:mapbox-sdk-turf:7.3.1")
androidTestImplementation("androidx.test:core:1.6.1")
implementation("com.whatsapp.otp:whatsapp-otp-android-sdk:0.1.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
// implementation(project(":vitalSignEngine"))
+
}
\ No newline at end of file
diff --git a/android/app/libs/PenNavUI.aar b/android/app/libs/PenNavUI.aar
index d423bc11..7832df8c 100644
Binary files a/android/app/libs/PenNavUI.aar and b/android/app/libs/PenNavUI.aar differ
diff --git a/android/app/libs/Penguin.aar b/android/app/libs/Penguin.aar
index 5c789c6f..a769c7a2 100644
Binary files a/android/app/libs/Penguin.aar and b/android/app/libs/Penguin.aar differ
diff --git a/android/app/libs/PenguinRenderer.aar b/android/app/libs/PenguinRenderer.aar
index b657ac66..2926e9ad 100644
Binary files a/android/app/libs/PenguinRenderer.aar and b/android/app/libs/PenguinRenderer.aar differ
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 4f7ef74c..8c1388b5 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -49,7 +49,7 @@
-
+
@@ -58,6 +58,13 @@
+
+
+
+
+
+
+
,
+ grantResults: IntArray
+ ) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+
+ val granted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
+ val intent = Intent("PERMISSION_RESULT_ACTION").apply {
+ putExtra("PERMISSION_GRANTED", granted)
+ }
+ sendBroadcast(intent)
+
+ // Log the request code and permission results
+ Log.d("PermissionsResult", "Request Code: $requestCode")
+ Log.d("PermissionsResult", "Permissions: ${permissions.joinToString()}")
+ Log.d("PermissionsResult", "Grant Results: ${grantResults.joinToString()}")
+
+ }
+
+ override fun onResume() {
+ super.onResume()
+ }
+}
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PenguinInPlatformBridge.kt b/android/app/src/main/kotlin/com/ejada/hmg/PenguinInPlatformBridge.kt
new file mode 100644
index 00000000..4df25bc9
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PenguinInPlatformBridge.kt
@@ -0,0 +1,61 @@
+package com.ejada.hmg.penguin
+
+import com.ejada.hmg.MainActivity
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import com.ejada.hmg.penguin.PenguinView
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodCall
+import com.ejada.hmg.PermissionManager.HostNotificationPermissionManager
+import com.ejada.hmg.PermissionManager.HostBgLocationManager
+import com.ejada.hmg.PermissionManager.HostGpsStateManager
+import io.flutter.plugin.common.MethodChannel
+
+class PenguinInPlatformBridge(
+ private var flutterEngine: FlutterEngine,
+ private var mainActivity: MainActivity
+) {
+
+ private lateinit var channel: MethodChannel
+
+ companion object {
+ private const val CHANNEL = "launch_penguin_ui"
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ fun create() {
+// openTok = OpenTok(mainActivity, flutterEngine)
+ channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
+ channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
+ when (call.method) {
+ "launchPenguin" -> {
+ print("the platform channel is being called")
+
+ if (HostNotificationPermissionManager.isNotificationPermissionGranted(mainActivity))
+ else HostNotificationPermissionManager.requestNotificationPermission(mainActivity)
+ HostBgLocationManager.requestLocationBackgroundPermission(mainActivity)
+ HostGpsStateManager.requestLocationPermission(mainActivity)
+ val args = call.arguments as Map?
+ Log.d("TAG", "configureFlutterEngine: $args")
+ println("args")
+ args?.let {
+ PenguinView(
+ mainActivity,
+ 100,
+ args,
+ flutterEngine.dartExecutor.binaryMessenger,
+ activity = mainActivity,
+ channel
+ )
+ }
+ }
+
+ else -> {
+ result.notImplemented()
+ }
+ }
+ }
+ }
+
+}
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/AppPreferences.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/AppPreferences.java
new file mode 100644
index 00000000..d0127998
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/AppPreferences.java
@@ -0,0 +1,139 @@
+package com.ejada.hmg.PermissionManager;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+
+
+/**
+ * This preferences for app level
+ */
+
+public class AppPreferences {
+
+ public static final String PREF_NAME = "PenguinINUI_AppPreferences";
+ public static final int MODE = Context.MODE_PRIVATE;
+
+ public static final String campusIdKey = "campusId";
+
+ public static final String LANG = "Lang";
+
+ public static final String settingINFO = "SETTING-INFO";
+
+ public static final String userName = "userName";
+ public static final String passWord = "passWord";
+
+ private static HandlerThread handlerThread;
+ private static Handler handler;
+
+ static {
+ handlerThread = new HandlerThread("PreferencesHandlerThread");
+ handlerThread.start();
+ handler = new Handler(handlerThread.getLooper());
+ }
+
+
+
+ public static SharedPreferences getPreferences(final Context context) {
+ return context.getSharedPreferences(AppPreferences.PREF_NAME, AppPreferences.MODE);
+ }
+
+ public static SharedPreferences.Editor getEditor(final Context context) {
+ return getPreferences(context).edit();
+ }
+
+
+ public static void writeInt(final Context context, final String key, final int value) {
+ handler.post(() -> {
+ SharedPreferences.Editor editor = getEditor(context);
+ editor.putInt(key, value);
+ editor.apply();
+ });
+ }
+
+
+ public static int readInt(final Context context, final String key, final int defValue) {
+ Callable callable = () -> {
+ SharedPreferences preferences = getPreferences(context);
+ return preferences.getInt(key, -1);
+ };
+
+ Future future = new FutureTask<>(callable);
+ handler.post((Runnable) future);
+
+ try {
+ return future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace(); // Handle the exception appropriately
+ }
+
+ return -1; // Return the default value in case of an error
+ }
+
+ public static int getCampusId(final Context context) {
+ return readInt(context,campusIdKey,-1);
+ }
+
+
+
+ public static void writeString(final Context context, final String key, final String value) {
+ handler.post(() -> {
+ SharedPreferences.Editor editor = getEditor(context);
+ editor.putString(key, value);
+ editor.apply();
+ });
+ }
+
+
+ public static String readString(final Context context, final String key, final String defValue) {
+ Callable callable = () -> {
+ SharedPreferences preferences = getPreferences(context);
+ return preferences.getString(key, defValue);
+ };
+
+ Future future = new FutureTask<>(callable);
+ handler.post((Runnable) future);
+
+ try {
+ return future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace(); // Handle the exception appropriately
+ }
+
+ return defValue; // Return the default value in case of an error
+ }
+
+
+ public static void writeBoolean(final Context context, final String key, final boolean value) {
+ handler.post(() -> {
+ SharedPreferences.Editor editor = getEditor(context);
+ editor.putBoolean(key, value);
+ editor.apply();
+ });
+ }
+
+ public static boolean readBoolean(final Context context, final String key, final boolean defValue) {
+ Callable callable = () -> {
+ SharedPreferences preferences = getPreferences(context);
+ return preferences.getBoolean(key, defValue);
+ };
+
+ Future future = new FutureTask<>(callable);
+ handler.post((Runnable) future);
+
+ try {
+ return future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace(); // Handle the exception appropriately
+ }
+
+ return defValue; // Return the default value in case of an error
+ }
+
+}
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostBgLocationManager.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostBgLocationManager.java
new file mode 100644
index 00000000..5bc332dc
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostBgLocationManager.java
@@ -0,0 +1,136 @@
+package com.ejada.hmg.PermissionManager;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.peng.pennavmap.PlugAndPlaySDK;
+import com.peng.pennavmap.R;
+import com.peng.pennavmap.enums.InitializationErrorType;
+
+/**
+ * Manages background location permission requests and handling for the application.
+ */
+public class HostBgLocationManager {
+ /**
+ * Request code for background location permission
+ */
+ public static final int REQUEST_ACCESS_BACKGROUND_LOCATION_CODE = 301;
+
+ /**
+ * Request code for navigating to app settings
+ */
+ private static final int REQUEST_CODE_SETTINGS = 11234;
+
+ /**
+ * Alert dialog for denied permissions
+ */
+ private static AlertDialog deniedAlertDialog;
+
+ /**
+ * Checks if the background location permission has been granted.
+ *
+ * @param context the context of the application or activity
+ * @return true if the permission is granted, false otherwise
+ */
+
+ public static boolean isLocationBackgroundGranted(Context context) {
+ return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Requests the background location permission from the user.
+ *
+ * @param activity the activity from which the request is made
+ */
+ public static void requestLocationBackgroundPermission(Activity activity) {
+ // Check if the ACCESS_BACKGROUND_LOCATION permission is already granted
+ if (!isLocationBackgroundGranted(activity)) {
+ // Permission is not granted, so request it
+ ActivityCompat.requestPermissions(activity,
+ new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
+ REQUEST_ACCESS_BACKGROUND_LOCATION_CODE);
+ }
+ }
+
+ /**
+ * Displays a dialog prompting the user to grant the background location permission.
+ *
+ * @param activity the activity where the dialog is displayed
+ */
+ public static void showLocationBackgroundPermission(Activity activity) {
+ AlertDialog alertDialog = new AlertDialog.Builder(activity)
+ .setCancelable(false)
+ .setMessage(activity.getString(R.string.com_penguin_nav_ui_geofence_alert_msg))
+ .setPositiveButton(activity.getString(R.string.com_penguin_nav_ui_go_to_settings), (dialog, which) -> {
+ if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
+ HostBgLocationManager.requestLocationBackgroundPermission(activity);
+ } else {
+ openAppSettings(activity);
+ }
+ if (dialog != null) {
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton(activity.getString(R.string.com_penguin_nav_ui_later), (dialog, which) -> {
+ dialog.cancel();
+ })
+ .create();
+
+ alertDialog.show();
+ }
+
+ /**
+ * Handles the scenario where permissions are denied by the user.
+ * Displays a dialog to guide the user to app settings or exit the activity.
+ *
+ * @param activity the activity where the dialog is displayed
+ */
+ public static synchronized void handlePermissionsDenied(Activity activity) {
+ if (deniedAlertDialog != null && deniedAlertDialog.isShowing()) {
+ deniedAlertDialog.dismiss();
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setCancelable(false)
+ .setMessage(activity.getString(R.string.com_penguin_nav_ui_permission_denied_dialog_msg))
+ .setNegativeButton(activity.getString(R.string.com_penguin_nav_ui_cancel), (dialogInterface, i) -> {
+ if (PlugAndPlaySDK.externalPenNavUIDelegate != null) {
+ PlugAndPlaySDK.externalPenNavUIDelegate.onPenNavInitializationError(
+ InitializationErrorType.permissions.getTypeKey(),
+ InitializationErrorType.permissions);
+ }
+ activity.finish();
+ })
+ .setPositiveButton(activity.getString(R.string.com_penguin_nav_ui_go_settings), (dialogInterface, i) -> {
+ dialogInterface.dismiss();
+ openAppSettings(activity);
+ });
+ deniedAlertDialog = builder.create();
+ deniedAlertDialog.show();
+ }
+
+ /**
+ * Opens the application's settings screen to allow the user to modify permissions.
+ *
+ * @param activity the activity from which the settings screen is launched
+ */
+ private static void openAppSettings(Activity activity) {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
+ intent.setData(uri);
+
+ if (intent.resolveActivity(activity.getPackageManager()) != null) {
+ activity.startActivityForResult(intent, REQUEST_CODE_SETTINGS);
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostGpsStateManager.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostGpsStateManager.java
new file mode 100644
index 00000000..adde1206
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostGpsStateManager.java
@@ -0,0 +1,68 @@
+package com.ejada.hmg.PermissionManager;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.peng.pennavmap.managers.permissions.managers.BgLocationManager;
+
+public class HostGpsStateManager {
+ private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
+
+
+ public boolean checkGPSEnabled(Activity activity) {
+ LocationManager gpsStateManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
+ return gpsStateManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+ }
+
+ public static boolean isGpsGranted(Activity activity) {
+ return BgLocationManager.isLocationBackgroundGranted(activity)
+ || ContextCompat.checkSelfPermission(
+ activity,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED
+ && ContextCompat.checkSelfPermission(
+ activity,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED;
+ }
+
+
+ /**
+ * Checks if the location permission is granted.
+ *
+ * @param activity the Activity context
+ * @return true if permission is granted, false otherwise
+ */
+ public static boolean isLocationPermissionGranted(Activity activity) {
+ return ContextCompat.checkSelfPermission(
+ activity,
+ Manifest.permission.ACCESS_FINE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED &&
+ ContextCompat.checkSelfPermission(
+ activity,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Requests the location permission.
+ *
+ * @param activity the Activity context
+ */
+ public static void requestLocationPermission(Activity activity) {
+ ActivityCompat.requestPermissions(
+ activity,
+ new String[]{
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ },
+ LOCATION_PERMISSION_REQUEST_CODE
+ );
+ }
+}
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostNotificationPermissionManager.java b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostNotificationPermissionManager.java
new file mode 100644
index 00000000..5b9f19e6
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/HostNotificationPermissionManager.java
@@ -0,0 +1,73 @@
+package com.ejada.hmg.PermissionManager;
+
+import android.app.Activity;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.app.NotificationManagerCompat;
+
+public class HostNotificationPermissionManager {
+ private static final int REQUEST_NOTIFICATION_PERMISSION = 100;
+
+
+ /**
+ * Checks if the notification permission is granted.
+ *
+ * @return true if the notification permission is granted, false otherwise.
+ */
+ public static boolean isNotificationPermissionGranted(Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ try {
+ return ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.POST_NOTIFICATIONS)
+ == PackageManager.PERMISSION_GRANTED;
+ } catch (Exception e) {
+ // Handle cases where the API is unavailable
+ e.printStackTrace();
+ return NotificationManagerCompat.from(activity).areNotificationsEnabled();
+ }
+ } else {
+ // Permissions were not required below Android 13 for notifications
+ return NotificationManagerCompat.from(activity).areNotificationsEnabled();
+ }
+ }
+
+ /**
+ * Requests the notification permission.
+ */
+ public static void requestNotificationPermission(Activity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (!isNotificationPermissionGranted(activity)) {
+ ActivityCompat.requestPermissions(activity,
+ new String[]{android.Manifest.permission.POST_NOTIFICATIONS},
+ REQUEST_NOTIFICATION_PERMISSION);
+ }
+ }
+ }
+
+ /**
+ * Handles the result of the permission request.
+ *
+ * @param requestCode The request code passed in requestPermissions().
+ * @param permissions The requested permissions.
+ * @param grantResults The grant results for the corresponding permissions.
+ */
+ public static boolean handlePermissionResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (permissions.length > 0 &&
+ permissions[0].equals(android.Manifest.permission.POST_NOTIFICATIONS) &&
+ grantResults.length > 0 &&
+ grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // Permission granted
+ System.out.println("Notification permission granted.");
+ return true;
+ } else {
+ // Permission denied
+ System.out.println("Notification permission denied.");
+ return false;
+ }
+
+ }
+
+
+}
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionHelper.kt b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionHelper.kt
new file mode 100644
index 00000000..9856a49e
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionHelper.kt
@@ -0,0 +1,28 @@
+package com.ejada.hmg.PermissionManager
+
+import android.Manifest
+import android.os.Build
+
+object PermissionHelper {
+
+ fun getRequiredPermissions(): Array {
+ val permissions = mutableListOf(
+ Manifest.permission.INTERNET,
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION,
+ Manifest.permission.ACCESS_NETWORK_STATE,
+ Manifest.permission.BLUETOOTH,
+ Manifest.permission.BLUETOOTH_ADMIN,
+// Manifest.permission.ACTIVITY_RECOGNITION
+ )
+
+ // For Android 12 (API level 31) and above, add specific permissions
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12 (API 31) and above
+ permissions.add(Manifest.permission.BLUETOOTH_SCAN)
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
+ permissions.add(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS)
+// }
+
+ return permissions.toTypedArray()
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionManager.kt b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionManager.kt
new file mode 100644
index 00000000..d8aea7bd
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionManager.kt
@@ -0,0 +1,50 @@
+package com.ejada.hmg.PermissionManager
+
+import android.app.Activity
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+
+class PermissionManager(
+ private val context: Context,
+ val listener: PermissionListener,
+ private val requestCode: Int,
+ vararg permissions: String
+) {
+
+ private val permissionsArray = permissions
+
+ interface PermissionListener {
+ fun onPermissionGranted()
+ fun onPermissionDenied()
+ }
+
+ fun arePermissionsGranted(): Boolean {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ permissionsArray.all {
+ ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
+ }
+ } else {
+ true
+ }
+ }
+
+ fun requestPermissions(activity: Activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ ActivityCompat.requestPermissions(activity, permissionsArray, requestCode)
+ }
+ }
+
+ fun handlePermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ if (this.requestCode == requestCode) {
+ val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
+ if (allGranted) {
+ listener.onPermissionGranted()
+ } else {
+ listener.onPermissionDenied()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionResultReceiver.kt b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionResultReceiver.kt
new file mode 100644
index 00000000..c07d1ded
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/PermissionManager/PermissionResultReceiver.kt
@@ -0,0 +1,15 @@
+package com.ejada.hmg.PermissionManager
+
+// PermissionResultReceiver.kt
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+class PermissionResultReceiver(
+ private val callback: (Boolean) -> Unit
+) : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val granted = intent?.getBooleanExtra("PERMISSION_GRANTED", false) ?: false
+ callback(granted)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinMethod.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinMethod.kt
new file mode 100644
index 00000000..18463d26
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinMethod.kt
@@ -0,0 +1,13 @@
+package com.ejada.hmg.penguin
+
+enum class PenguinMethod {
+ // initializePenguin("initializePenguin"),
+ // configurePenguin("configurePenguin"),
+ // showPenguinUI("showPenguinUI"),
+ // onPenNavUIDismiss("onPenNavUIDismiss"),
+ // onReportIssue("onReportIssue"),
+ // onPenNavSuccess("onPenNavSuccess"),
+ onPenNavInitializationError // onLocationOffCampus("onLocationOffCampus"),
+ // navigateToPOI("navigateToPOI"),
+ // openSharedLocation("openSharedLocation");
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt
new file mode 100644
index 00000000..b822d676
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinNavigator.kt
@@ -0,0 +1,97 @@
+package com.ejada.hmg.penguin
+
+import android.content.Context
+import com.google.gson.Gson
+import com.peng.pennavmap.PlugAndPlaySDK
+import com.peng.pennavmap.connections.ApiController
+import com.peng.pennavmap.interfaces.RefIdDelegate
+import com.peng.pennavmap.models.TokenModel
+import com.peng.pennavmap.models.postmodels.PostToken
+import com.peng.pennavmap.utils.AppSharedData
+import okhttp3.ResponseBody
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import android.util.Log
+
+
+class PenguinNavigator() {
+
+ fun navigateTo(mContext: Context, refID: String, delegate: RefIdDelegate,clientID : String,clientKey : String ) {
+ val postToken = PostToken(clientID, clientKey)
+ getToken(mContext, postToken, object : RefIdDelegate {
+ override fun onRefByIDSuccess(PoiId: String?) {
+ Log.e("navigateTo", "PoiId is+++++++ $PoiId")
+
+ PlugAndPlaySDK.navigateTo(mContext, refID, object : RefIdDelegate {
+ override fun onRefByIDSuccess(PoiId: String?) {
+ Log.e("navigateTo", "PoiId 2is+++++++ $PoiId")
+
+ delegate.onRefByIDSuccess(refID)
+
+ }
+
+ override fun onGetByRefIDError(error: String?) {
+ delegate.onRefByIDSuccess(error)
+ }
+
+ })
+
+
+ }
+
+ override fun onGetByRefIDError(error: String?) {
+ delegate.onRefByIDSuccess(error)
+ }
+
+ })
+
+ }
+
+ fun getToken(mContext: Context, postToken: PostToken?, apiTokenCallBack: RefIdDelegate) {
+ try {
+ // Create the API call
+ val purposesCall: Call = ApiController.getInstance(mContext)
+ .apiMethods
+ .getToken(postToken)
+
+ // Enqueue the call for asynchronous execution
+ purposesCall.enqueue(object : Callback {
+ override fun onResponse(
+ call: Call,
+ response: Response
+ ) {
+ if (response.isSuccessful() && response.body() != null) {
+ try {
+ response.body()?.use { responseBody ->
+ val responseBodyString: String = responseBody.string() // Use `string()` to get the actual response content
+ if (responseBodyString.isNotEmpty()) {
+ val tokenModel = Gson().fromJson(responseBodyString, TokenModel::class.java)
+ if (tokenModel != null && tokenModel.token != null) {
+ AppSharedData.apiToken = tokenModel.token
+ apiTokenCallBack.onRefByIDSuccess(tokenModel.token)
+ } else {
+ apiTokenCallBack.onGetByRefIDError("Failed to parse token model")
+ }
+ } else {
+ apiTokenCallBack.onGetByRefIDError("Response body is empty")
+ }
+ }
+ } catch (e: Exception) {
+ apiTokenCallBack.onGetByRefIDError("An error occurred: ${e.message}")
+ }
+ } else {
+ apiTokenCallBack.onGetByRefIDError("Unsuccessful response: " + response.code())
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ apiTokenCallBack.onGetByRefIDError(t.message)
+ }
+ })
+ } catch (error: Exception) {
+ apiTokenCallBack.onGetByRefIDError("Exception during API call: $error")
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt
new file mode 100644
index 00000000..6c7306d8
--- /dev/null
+++ b/android/app/src/main/kotlin/com/ejada/hmg/penguin/PenguinView.kt
@@ -0,0 +1,376 @@
+package com.ejada.hmg.penguin
+
+import android.app.Activity
+import android.content.Context
+import android.content.Context.RECEIVER_EXPORTED
+import android.content.IntentFilter
+import android.graphics.Color
+import android.os.Build
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RelativeLayout
+import android.widget.Toast
+import androidx.annotation.RequiresApi
+import com.ejada.hmg.PermissionManager.PermissionManager
+import com.ejada.hmg.PermissionManager.PermissionResultReceiver
+import com.ejada.hmg.MainActivity
+import com.ejada.hmg.PermissionManager.PermissionHelper
+import com.peng.pennavmap.PlugAndPlayConfiguration
+import com.peng.pennavmap.PlugAndPlaySDK
+import com.peng.pennavmap.enums.InitializationErrorType
+import com.peng.pennavmap.interfaces.PenNavUIDelegate
+import com.peng.pennavmap.utils.Languages
+import io.flutter.plugin.common.BinaryMessenger
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.platform.PlatformView
+import com.ejada.hmg.penguin.PenguinNavigator
+import com.peng.pennavmap.interfaces.PIEventsDelegate
+import com.peng.pennavmap.interfaces.PILocationDelegate
+import com.peng.pennavmap.interfaces.RefIdDelegate
+import com.peng.pennavmap.models.LocationMessage
+import com.peng.pennavmap.models.PIReportIssue
+import java.util.ArrayList
+import penguin.com.pennav.renderer.PIRendererSettings
+
+/**
+ * Custom PlatformView for displaying Penguin UI components within a Flutter app.
+ * Implements `PlatformView` for rendering the view, `MethodChannel.MethodCallHandler` for handling method calls,
+ * and `PenNavUIDelegate` for handling SDK events.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+internal class PenguinView(
+ context: Context,
+ id: Int,
+ val creationParams: Map,
+ messenger: BinaryMessenger,
+ activity: MainActivity,
+ val channel: MethodChannel
+) : PlatformView, MethodChannel.MethodCallHandler, PenNavUIDelegate, PIEventsDelegate,
+ PILocationDelegate {
+ // The layout for displaying the Penguin UI
+ private val mapLayout: RelativeLayout = RelativeLayout(context)
+ private val _context: Context = context
+
+ private val permissionResultReceiver: PermissionResultReceiver
+ private val permissionIntentFilter = IntentFilter("PERMISSION_RESULT_ACTION")
+
+ private companion object {
+ const val PERMISSIONS_REQUEST_CODE = 1
+ }
+
+ private lateinit var permissionManager: PermissionManager
+
+ // Reference to the main activity
+ private var _activity: Activity = activity
+
+ private lateinit var mContext: Context
+
+ lateinit var navigator: PenguinNavigator
+
+ init {
+ // Set layout parameters for the mapLayout
+ mapLayout.layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ )
+
+ mContext = context
+
+
+ permissionResultReceiver = PermissionResultReceiver { granted ->
+ if (granted) {
+ onPermissionsGranted()
+ } else {
+ onPermissionsDenied()
+ }
+ }
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ mContext.registerReceiver(
+ permissionResultReceiver,
+ permissionIntentFilter,
+ RECEIVER_EXPORTED
+ )
+ } else {
+ mContext.registerReceiver(
+ permissionResultReceiver,
+ permissionIntentFilter,
+ )
+ }
+
+ // Set the background color of the layout
+ mapLayout.setBackgroundColor(Color.RED)
+
+ permissionManager = PermissionManager(
+ context = mContext,
+ listener = object : PermissionManager.PermissionListener {
+ override fun onPermissionGranted() {
+ // Handle permissions granted
+ onPermissionsGranted()
+ }
+
+ override fun onPermissionDenied() {
+ // Handle permissions denied
+ onPermissionsDenied()
+ }
+ },
+ requestCode = PERMISSIONS_REQUEST_CODE,
+ PermissionHelper.getRequiredPermissions().get(0)
+ )
+
+ if (!permissionManager.arePermissionsGranted()) {
+ permissionManager.requestPermissions(_activity)
+ } else {
+ // Permissions already granted
+ permissionManager.listener.onPermissionGranted()
+ }
+
+
+ }
+
+ private fun onPermissionsGranted() {
+ // Handle the actions when permissions are granted
+ Log.d("PermissionsResult", "onPermissionsGranted")
+ // Register the platform view factory for creating custom views
+
+ // Initialize the Penguin SDK
+ initPenguin()
+
+
+ }
+
+ private fun onPermissionsDenied() {
+ // Handle the actions when permissions are denied
+ Log.d("PermissionsResult", "onPermissionsDenied")
+
+ }
+
+ /**
+ * Returns the view associated with this PlatformView.
+ *
+ * @return The main view for this PlatformView.
+ */
+ override fun getView(): View {
+ return mapLayout
+ }
+
+ /**
+ * Cleans up resources associated with this PlatformView.
+ */
+ override fun dispose() {
+ // Cleanup code if needed
+ }
+
+ /**
+ * Handles method calls from Dart code.
+ *
+ * @param call The method call from Dart.
+ * @param result The result callback to send responses back to Dart.
+ */
+ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
+ // Handle method calls from Dart code here
+ }
+
+ /**
+ * Initializes the Penguin SDK with custom configuration and delegates.
+ */
+ private fun initPenguin() {
+ navigator = PenguinNavigator()
+ // Configure the PlugAndPlaySDK
+ val language = when (creationParams["languageCode"] as String) {
+ "ar" -> Languages.ar
+ "en" -> Languages.en
+ else -> {
+ Languages.en
+ }
+ }
+
+
+// PlugAndPlaySDK.configuration = Builder()
+// .setClientData(MConstantsDemo.CLIENT_ID, MConstantsDemo.CLIENT_KEY)
+// .setLanguageID(selectedLanguage)
+// .setBaseUrl(MConstantsDemo.DATA_URL, MConstantsDemo.POSITION_URL)
+// .setServiceName(MConstantsDemo.DATA_SERVICE_NAME, MConstantsDemo.POSITION_SERVICE_NAME)
+// .setUserName(name)
+// .setSimulationModeEnabled(isSimulation)
+// .setCustomizeColor(if (MConstantsDemo.APP_COLOR != null) MConstantsDemo.APP_COLOR else "#2CA0AF")
+// .setEnableBackButton(MConstantsDemo.SHOW_BACK_BUTTON)
+// .setCampusId(MConstantsDemo.selectedCampusId)
+//
+// .setShowUILoader(true)
+// .build()
+
+ PIRendererSettings.styleUri = "mapbox://styles/rwaid/cm3h30b36007v01qz7ik8a0sk"
+
+ PlugAndPlaySDK.configuration = PlugAndPlayConfiguration.Builder()
+ .setBaseUrl(
+ creationParams["dataURL"] as String,
+ creationParams["positionURL"] as String
+ )
+ .setServiceName(
+ creationParams["dataServiceName"] as String,
+ creationParams["positionServiceName"] as String
+ )
+ .setClientData(
+ creationParams["clientID"] as String,
+ creationParams["clientKey"] as String
+ )
+ .setUserName(creationParams["username"] as String)
+// .setLanguageID(Languages.en)
+ .setLanguageID(language)
+ .setSimulationModeEnabled(creationParams["isSimulationModeEnabled"] as Boolean)
+ .setEnableBackButton(true)
+// .setDeepLinkData("deeplink")
+ .setCustomizeColor("#2CA0AF")
+ .setDeepLinkSchema("", "")
+ .setIsEnableReportIssue(true)
+ .setDeepLinkData("")
+ .setEnableSharedLocationCallBack(false)
+ .setShowUILoader(true)
+ .setCampusId(creationParams["projectID"] as Int)
+ .build()
+
+
+ Log.d(
+ "TAG",
+ "initPenguin: ${creationParams["projectID"]}"
+ )
+
+ Log.d(
+ "TAG",
+ "initPenguin: creation param are ${creationParams}"
+ )
+
+ // Set location delegate to handle location updates
+// PlugAndPlaySDK.setPiLocationDelegate {
+ // Example code to handle location updates
+ // Uncomment and modify as needed
+ // if (location.size() > 0)
+ // Toast.makeText(_context, "Location Info Latitude: ${location[0]}, Longitude: ${location[1]}", Toast.LENGTH_SHORT).show()
+// }
+
+ // Set events delegate for reporting issues
+// PlugAndPlaySDK.setPiEventsDelegate(new PIEventsDelegate() {
+// @Override
+// public void onReportIssue(PIReportIssue issue) {
+// Log.e("Issue Reported: ", issue.getReportType());
+// }
+// // Implement issue reporting logic here }
+// @Override
+// public void onSharedLocation(String link) {
+// // Implement Shared location logic here
+// }
+// })
+
+ // Start the Penguin SDK
+ PlugAndPlaySDK.setPiEventsDelegate(this)
+ PlugAndPlaySDK.setPiLocationDelegate(this)
+ PlugAndPlaySDK.start(mContext, this)
+ }
+
+
+ /**
+ * Navigates to the specified reference ID.
+ *
+ * @param refID The reference ID to navigate to.
+ */
+ fun navigateTo(refID: String) {
+ try {
+ if (refID.isBlank()) {
+ Log.e("navigateTo", "Invalid refID: The reference ID is blank.")
+ }
+// referenceId = refID
+ navigator.navigateTo(mContext, refID,object : RefIdDelegate {
+ override fun onRefByIDSuccess(PoiId: String?) {
+ Log.e("navigateTo", "PoiId is penguin view+++++++ $PoiId")
+
+// channelFlutter.invokeMethod(
+// PenguinMethod.navigateToPOI.name,
+// "navigateTo Success"
+// )
+ }
+
+ override fun onGetByRefIDError(error: String?) {
+ Log.e("navigateTo", "error is penguin view+++++++ $error")
+
+// channelFlutter.invokeMethod(
+// PenguinMethod.navigateToPOI.name,
+// "navigateTo Failed: Invalid refID"
+// )
+ }
+ } , creationParams["clientID"] as String, creationParams["clientKey"] as String )
+
+ } catch (e: Exception) {
+ Log.e("navigateTo", "Exception occurred during navigation: ${e.message}", e)
+// channelFlutter.invokeMethod(
+// PenguinMethod.navigateToPOI.name,
+// "Failed: Exception - ${e.message}"
+// )
+ }
+ }
+
+ /**
+ * Called when Penguin UI setup is successful.
+ *
+ * @param warningCode Optional warning code received from the SDK.
+ */
+ override fun onPenNavSuccess(warningCode: String?) {
+ val clinicId = creationParams["clinicID"] as String
+
+ if(clinicId.isEmpty()) return
+
+ navigateTo(clinicId)
+ }
+
+ /**
+ * Called when there is an initialization error with Penguin UI.
+ *
+ * @param description Description of the error.
+ * @param errorType Type of initialization error.
+ */
+ override fun onPenNavInitializationError(
+ description: String?,
+ errorType: InitializationErrorType?
+ ) {
+ val arguments: Map = mapOf(
+ "description" to description,
+ "type" to errorType?.name
+ )
+ Log.d(
+ "description",
+ "description : ${description}"
+ )
+
+ channel.invokeMethod(PenguinMethod.onPenNavInitializationError.name, arguments)
+ Toast.makeText(mContext, "Navigation Error: $description", Toast.LENGTH_SHORT).show()
+ }
+
+ /**
+ * Called when Penguin UI is dismissed.
+ */
+ override fun onPenNavUIDismiss() {
+ // Handle UI dismissal if needed
+ try {
+ mContext.unregisterReceiver(permissionResultReceiver)
+ dispose();
+ } catch (e: IllegalArgumentException) {
+ Log.e("PenguinView", "Receiver not registered: $e")
+ }
+ }
+
+ override fun onReportIssue(issue: PIReportIssue?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onSharedLocation(link: String?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onLocationOffCampus(location: ArrayList?) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onLocationMessage(locationMessage: LocationMessage?) {
+ TODO("Not yet implemented")
+ }
+}
diff --git a/android/app/src/main/res/values/mapbox_access_token.xml b/android/app/src/main/res/values/mapbox_access_token.xml
index f1daf69d..65bc4b37 100644
--- a/android/app/src/main/res/values/mapbox_access_token.xml
+++ b/android/app/src/main/res/values/mapbox_access_token.xml
@@ -1,3 +1,3 @@
- sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 328e8fc8..2d103337 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -19,5 +19,5 @@
Geofence requests happened too frequently.
-
+ pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
index 866b2708..9b679bc9 100644
--- a/android/build/reports/problems/problems-report.html
+++ b/android/build/reports/problems/problems-report.html
@@ -650,7 +650,7 @@ code + .copy-button {
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
index 3e6502fc..6d0842d7 100644
--- a/android/settings.gradle.kts
+++ b/android/settings.gradle.kts
@@ -18,7 +18,7 @@ pluginManagement {
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
-// id("com.android.application") version "8.7.3" apply false
+// id("com.android.application") version "8.9.3" apply false
id("com.android.application") version "8.9.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
diff --git a/assets/images/png/bmi_image_1.png b/assets/images/png/bmi_image_1.png
new file mode 100644
index 00000000..db3a6133
Binary files /dev/null and b/assets/images/png/bmi_image_1.png differ
diff --git a/assets/images/svg/add_icon_dark.svg b/assets/images/svg/add_icon_dark.svg
new file mode 100644
index 00000000..399df3c7
--- /dev/null
+++ b/assets/images/svg/add_icon_dark.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/blood_pressure.svg b/assets/images/svg/blood_pressure.svg
new file mode 100644
index 00000000..67badbe7
--- /dev/null
+++ b/assets/images/svg/blood_pressure.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/bmi_2.svg b/assets/images/svg/bmi_2.svg
new file mode 100644
index 00000000..38468d70
--- /dev/null
+++ b/assets/images/svg/bmi_2.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/cup_add.svg b/assets/images/svg/cup_add.svg
new file mode 100644
index 00000000..ebe186a0
--- /dev/null
+++ b/assets/images/svg/cup_add.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/cup_empty.svg b/assets/images/svg/cup_empty.svg
new file mode 100644
index 00000000..fae08fe0
--- /dev/null
+++ b/assets/images/svg/cup_empty.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/cup_filled.svg b/assets/images/svg/cup_filled.svg
new file mode 100644
index 00000000..6a085bb7
--- /dev/null
+++ b/assets/images/svg/cup_filled.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/dumbell_icon.svg b/assets/images/svg/dumbell_icon.svg
new file mode 100644
index 00000000..1d6db5f3
--- /dev/null
+++ b/assets/images/svg/dumbell_icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/glass_icon.svg b/assets/images/svg/glass_icon.svg
new file mode 100644
index 00000000..1df8eec0
--- /dev/null
+++ b/assets/images/svg/glass_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/graph_icon.svg b/assets/images/svg/graph_icon.svg
new file mode 100644
index 00000000..7bb6fbbe
--- /dev/null
+++ b/assets/images/svg/graph_icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/assets/images/svg/green_tick_icon.svg b/assets/images/svg/green_tick_icon.svg
new file mode 100644
index 00000000..e0411917
--- /dev/null
+++ b/assets/images/svg/green_tick_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/heart_rate.svg b/assets/images/svg/heart_rate.svg
new file mode 100644
index 00000000..15c754ff
--- /dev/null
+++ b/assets/images/svg/heart_rate.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/height_2.svg b/assets/images/svg/height_2.svg
new file mode 100644
index 00000000..a1c361a2
--- /dev/null
+++ b/assets/images/svg/height_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/height_icon.svg b/assets/images/svg/height_icon.svg
new file mode 100644
index 00000000..78cefdc1
--- /dev/null
+++ b/assets/images/svg/height_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/list_icon.svg b/assets/images/svg/list_icon.svg
new file mode 100644
index 00000000..e68f20b6
--- /dev/null
+++ b/assets/images/svg/list_icon.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/images/svg/minimize_icon.svg b/assets/images/svg/minimize_icon.svg
new file mode 100644
index 00000000..b60a041d
--- /dev/null
+++ b/assets/images/svg/minimize_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/notification_icon_grey.svg b/assets/images/svg/notification_icon_grey.svg
new file mode 100644
index 00000000..9e5e8d55
--- /dev/null
+++ b/assets/images/svg/notification_icon_grey.svg
@@ -0,0 +1,4 @@
+
diff --git a/assets/images/svg/outer_bubbles.svg b/assets/images/svg/outer_bubbles.svg
new file mode 100644
index 00000000..cfe860d4
--- /dev/null
+++ b/assets/images/svg/outer_bubbles.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/images/svg/profile_icon.svg b/assets/images/svg/profile_icon.svg
new file mode 100644
index 00000000..20dfb2ba
--- /dev/null
+++ b/assets/images/svg/profile_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/resp_rate.svg b/assets/images/svg/resp_rate.svg
new file mode 100644
index 00000000..7038793b
--- /dev/null
+++ b/assets/images/svg/resp_rate.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/symptom_bottom_icon.svg b/assets/images/svg/symptom_bottom_icon.svg
new file mode 100644
index 00000000..bc729711
--- /dev/null
+++ b/assets/images/svg/symptom_bottom_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/symptom_checker_icon.svg b/assets/images/svg/symptom_checker_icon.svg
new file mode 100644
index 00000000..e41c1ddf
--- /dev/null
+++ b/assets/images/svg/symptom_checker_icon.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/images/svg/temperature.svg b/assets/images/svg/temperature.svg
new file mode 100644
index 00000000..14c7da4e
--- /dev/null
+++ b/assets/images/svg/temperature.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/water_bottle.svg b/assets/images/svg/water_bottle.svg
new file mode 100644
index 00000000..4763d7ea
--- /dev/null
+++ b/assets/images/svg/water_bottle.svg
@@ -0,0 +1,34 @@
+
diff --git a/assets/images/svg/weight_2.svg b/assets/images/svg/weight_2.svg
new file mode 100644
index 00000000..c22441fb
--- /dev/null
+++ b/assets/images/svg/weight_2.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/weight_scale_icon.svg b/assets/images/svg/weight_scale_icon.svg
new file mode 100644
index 00000000..c3329ff0
--- /dev/null
+++ b/assets/images/svg/weight_scale_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/assets/images/svg/yellow_arrow_down_icon.svg b/assets/images/svg/yellow_arrow_down_icon.svg
new file mode 100644
index 00000000..f2ca09fc
--- /dev/null
+++ b/assets/images/svg/yellow_arrow_down_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 00000000..fa0b357c
--- /dev/null
+++ b/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/ios/Controllers/MainFlutterVC.swift b/ios/Controllers/MainFlutterVC.swift
new file mode 100644
index 00000000..4f91d052
--- /dev/null
+++ b/ios/Controllers/MainFlutterVC.swift
@@ -0,0 +1,118 @@
+//
+// MainFlutterVC.swift
+// Runner
+//
+// Created by ZiKambrani on 25/03/1442 AH.
+//
+
+import UIKit
+import Flutter
+import NetworkExtension
+import SystemConfiguration.CaptiveNetwork
+
+class MainFlutterVC: FlutterViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+// flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in
+//
+// if methodCall.method == "connectHMGInternetWifi"{
+// self.connectHMGInternetWifi(methodCall:methodCall, result: result)
+//
+// }else if methodCall.method == "connectHMGGuestWifi"{
+// self.connectHMGGuestWifi(methodCall:methodCall, result: result)
+//
+// }else if methodCall.method == "isHMGNetworkAvailable"{
+// self.isHMGNetworkAvailable(methodCall:methodCall, result: result)
+//
+// }else if methodCall.method == "registerHmgGeofences"{
+// self.registerHmgGeofences(result: result)
+// }
+//
+// print("")
+// }
+//
+// FlutterText.with(key: "errorConnectingHmgNetwork") { (localized) in
+// print(localized)
+// }
+
+ }
+
+
+ // Connect HMG Wifi and Internet
+ func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+
+ guard let pateintId = (methodCall.arguments as? [Any])?.first as? String
+ else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") }
+
+
+ HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ // Connect HMG-Guest for App Access
+ func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+ HMG_GUEST.shared.connect() { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{
+ guard let ssid = methodCall.arguments as? String else {
+ assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'")
+ return false
+ }
+
+ let queue = DispatchQueue.init(label: "com.hmg.wifilist")
+ NEHotspotHelper.register(options: nil, queue: queue) { (command) in
+ print(command)
+
+ if(command.commandType == NEHotspotHelperCommandType.filterScanList) {
+ if let networkList = command.networkList{
+ for network in networkList{
+ print(network.ssid)
+ }
+ }
+ }
+ }
+ return false
+
+ }
+
+
+ // Message Dailog
+ func showMessage(title:String, message:String){
+ DispatchQueue.main.async {
+ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
+ alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
+ self.present(alert, animated: true) {
+
+ }
+ }
+ }
+
+ // Register Geofence
+ func registerHmgGeofences(result: @escaping FlutterResult){
+ flutterMethodChannel?.invokeMethod("getGeofencePreferenceKey", arguments: nil){ geoFencesJsonString in
+ if let jsonString = geoFencesJsonString as? String{
+ let allZones = GeoZoneModel.list(from: jsonString)
+ HMG_Geofence().register(geoZones: allZones)
+
+ }else{
+ }
+ }
+ }
+
+}
diff --git a/ios/Helper/API.swift b/ios/Helper/API.swift
new file mode 100644
index 00000000..b487f033
--- /dev/null
+++ b/ios/Helper/API.swift
@@ -0,0 +1,22 @@
+//
+// API.swift
+// Runner
+//
+// Created by ZiKambrani on 04/04/1442 AH.
+//
+
+import UIKit
+
+fileprivate let DOMAIN = "https://uat.hmgwebservices.com"
+fileprivate let SERVICE = "Services/Patients.svc/REST"
+fileprivate let BASE_URL = "\(DOMAIN)/\(SERVICE)"
+
+struct API {
+ static let WIFI_CREDENTIALS = "\(BASE_URL)/Hmg_SMS_Get_By_ProjectID_And_PatientID"
+}
+
+
+//struct API {
+// static let WIFI_CREDENTIALS = FlutterConstants.WIFI_CREDENTIALS_URL
+// static let LOG_GEOFENCE = FlutterConstants.LOG_GEOFENCE_URL
+//}
diff --git a/ios/Helper/Extensions.swift b/ios/Helper/Extensions.swift
new file mode 100644
index 00000000..de67f9b9
--- /dev/null
+++ b/ios/Helper/Extensions.swift
@@ -0,0 +1,150 @@
+//
+// Extensions.swift
+// Runner
+//
+// Created by ZiKambrani on 04/04/1442 AH.
+//
+
+import UIKit
+
+
+extension String{
+ func toUrl() -> URL?{
+ return URL(string: self)
+ }
+
+ func removeSpace() -> String?{
+ return self.replacingOccurrences(of: " ", with: "")
+ }
+}
+
+extension Date{
+ func toString(format:String) -> String{
+ let df = DateFormatter()
+ df.dateFormat = format
+ return df.string(from: self)
+ }
+}
+
+extension Dictionary{
+ func merge(dict:[String:Any?]) -> [String:Any?]{
+ var self_ = self as! [String:Any?]
+ dict.forEach { (kv) in
+ self_.updateValue(kv.value, forKey: kv.key)
+ }
+ return self_
+ }
+}
+
+extension Bundle {
+
+ func certificate(named name: String) -> SecCertificate {
+ let cerURL = self.url(forResource: name, withExtension: "cer")!
+ let cerData = try! Data(contentsOf: cerURL)
+ let cer = SecCertificateCreateWithData(nil, cerData as CFData)!
+ return cer
+ }
+
+ func identity(named name: String, password: String) -> SecIdentity {
+ let p12URL = self.url(forResource: name, withExtension: "p12")!
+ let p12Data = try! Data(contentsOf: p12URL)
+
+ var importedCF: CFArray? = nil
+ let options = [kSecImportExportPassphrase as String: password]
+ let err = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &importedCF)
+ precondition(err == errSecSuccess)
+ let imported = importedCF! as NSArray as! [[String:AnyObject]]
+ precondition(imported.count == 1)
+
+ return (imported[0][kSecImportItemIdentity as String]!) as! SecIdentity
+ }
+
+
+}
+
+extension SecCertificate{
+ func trust() -> Bool?{
+ var optionalTrust: SecTrust?
+ let policy = SecPolicyCreateBasicX509()
+
+ let status = SecTrustCreateWithCertificates([self] as AnyObject,
+ policy,
+ &optionalTrust)
+ guard status == errSecSuccess else { return false}
+ let trust = optionalTrust!
+
+ let stat = optionalTrust?.evaluateAllowing(rootCertificates: [self])
+ return stat
+ }
+
+ func secTrustObject() -> SecTrust?{
+ var optionalTrust: SecTrust?
+ let policy = SecPolicyCreateBasicX509()
+
+ let status = SecTrustCreateWithCertificates([self] as AnyObject,
+ policy,
+ &optionalTrust)
+ return optionalTrust
+ }
+}
+
+
+extension SecTrust {
+
+ func evaluate() -> Bool {
+ var trustResult: SecTrustResultType = .invalid
+ let err = SecTrustEvaluate(self, &trustResult)
+ guard err == errSecSuccess else { return false }
+ return [.proceed, .unspecified].contains(trustResult)
+ }
+
+ func evaluateAllowing(rootCertificates: [SecCertificate]) -> Bool {
+
+ // Apply our custom root to the trust object.
+
+ var err = SecTrustSetAnchorCertificates(self, rootCertificates as CFArray)
+ guard err == errSecSuccess else { return false }
+
+ // Re-enable the system's built-in root certificates.
+
+ err = SecTrustSetAnchorCertificatesOnly(self, false)
+ guard err == errSecSuccess else { return false }
+
+ // Run a trust evaluation and only allow the connection if it succeeds.
+
+ return self.evaluate()
+ }
+}
+
+
+extension UIView{
+ func show(){
+ self.alpha = 0.0
+ self.isHidden = false
+ UIView.animate(withDuration: 0.25, animations: {
+ self.alpha = 1
+ }) { (complete) in
+
+ }
+ }
+
+ func hide(){
+ UIView.animate(withDuration: 0.25, animations: {
+ self.alpha = 0.0
+ }) { (complete) in
+ self.isHidden = true
+ }
+ }
+}
+
+
+extension UIViewController{
+ func showAlert(withTitle: String, message: String){
+ let alert = UIAlertController(title: withTitle, message: message, preferredStyle: .alert)
+ alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
+ present(alert, animated: true) {
+
+ }
+ }
+}
+
diff --git a/ios/Helper/FlutterConstants.swift b/ios/Helper/FlutterConstants.swift
new file mode 100644
index 00000000..f1b3f098
--- /dev/null
+++ b/ios/Helper/FlutterConstants.swift
@@ -0,0 +1,36 @@
+//
+// FlutterConstants.swift
+// Runner
+//
+// Created by ZiKambrani on 22/12/2020.
+//
+
+import UIKit
+
+class FlutterConstants{
+ static var LOG_GEOFENCE_URL:String?
+ static var WIFI_CREDENTIALS_URL:String?
+ static var DEFAULT_HTTP_PARAMS:[String:Any?]?
+
+ class func set(){
+
+ // (FiX) Take a start with FlutterMethodChannel (kikstart)
+ /* First call to flutter method is not returning the correct value (Always returning 'NSObject') then after it wroking fine and returning correct value*/
+ FlutterText.with(key: "test") { (test) in
+
+ flutterMethodChannel?.invokeMethod("getDefaultHttpParameters", arguments: nil){ (response) in
+ if let defaultHTTPParams = response as? [String:Any?]{
+ DEFAULT_HTTP_PARAMS = defaultHTTPParams
+ }
+
+ }
+
+ flutterMethodChannel?.invokeMethod("getLogGeofenceFullUrl", arguments:nil){ (response) in
+ if let url = response as? String{
+ LOG_GEOFENCE_URL = url
+ }
+ }
+
+ }
+ }
+}
diff --git a/ios/Helper/GeoZoneModel.swift b/ios/Helper/GeoZoneModel.swift
new file mode 100644
index 00000000..e703b64c
--- /dev/null
+++ b/ios/Helper/GeoZoneModel.swift
@@ -0,0 +1,67 @@
+//
+// GeoZoneModel.swift
+// Runner
+//
+// Created by ZiKambrani on 13/12/2020.
+//
+
+import UIKit
+import CoreLocation
+
+class GeoZoneModel{
+ var geofenceId:Int = -1
+ var description:String = ""
+ var descriptionN:String?
+ var latitude:String?
+ var longitude:String?
+ var radius:Int?
+ var type:Int?
+ var projectID:Int?
+ var imageURL:String?
+ var isCity:String?
+
+ func identifier() -> String{
+ return "\(geofenceId)_hmg"
+ }
+
+ func message() -> String{
+ return description
+ }
+
+ func toRegion(locationManager:CLLocationManager) -> CLCircularRegion?{
+ if let rad = radius, let lat = latitude?.removeSpace(), let long = longitude?.removeSpace(),
+ let radius_d = Double("\(rad)"), let lat_d = Double(lat), let long_d = Double(long){
+
+ let coordinate = CLLocationCoordinate2D(latitude: lat_d, longitude: long_d)
+ let validatedRadius = min(radius_d, locationManager.maximumRegionMonitoringDistance)
+
+ let region = CLCircularRegion(center: coordinate, radius: validatedRadius, identifier: identifier())
+ region.notifyOnExit = true
+ region.notifyOnEntry = true
+ return region
+ }
+ return nil
+ }
+
+ class func from(json:[String:Any]) -> GeoZoneModel{
+ let model = GeoZoneModel()
+ model.geofenceId = json["GEOF_ID"] as? Int ?? 0
+ model.radius = json["Radius"] as? Int
+ model.projectID = json["ProjectID"] as? Int
+ model.type = json["Type"] as? Int
+ model.description = json["Description"] as? String ?? ""
+ model.descriptionN = json["DescriptionN"] as? String
+ model.latitude = json["Latitude"] as? String
+ model.longitude = json["Longitude"] as? String
+ model.imageURL = json["ImageURL"] as? String
+ model.isCity = json["IsCity"] as? String
+
+ return model
+ }
+
+ class func list(from jsonString:String) -> [GeoZoneModel]{
+ let value = dictionaryArray(from: jsonString)
+ let geoZones = value.map { GeoZoneModel.from(json: $0) }
+ return geoZones
+ }
+}
diff --git a/ios/Helper/GlobalHelper.swift b/ios/Helper/GlobalHelper.swift
new file mode 100644
index 00000000..37687806
--- /dev/null
+++ b/ios/Helper/GlobalHelper.swift
@@ -0,0 +1,119 @@
+//
+// GlobalHelper.swift
+// Runner
+//
+// Created by ZiKambrani on 29/03/1442 AH.
+//
+
+import UIKit
+
+func dictionaryArray(from:String) -> [[String:Any]]{
+ if let data = from.data(using: .utf8) {
+ do {
+ return try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] ?? []
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ return []
+
+}
+
+func dictionary(from:String) -> [String:Any]?{
+ if let data = from.data(using: .utf8) {
+ do {
+ return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ return nil
+
+}
+
+let HmgLocalNotificationCategoryIdentifier = "hmg.local.notification"
+func showNotification(identifier:String? = nil, title:String?, subtitle:String?, message:String?, sound:UNNotificationSound = UNNotificationSound.default, categoryIdentifier:String = HmgLocalNotificationCategoryIdentifier){
+ DispatchQueue.main.async {
+ let notificationContent = UNMutableNotificationContent()
+ notificationContent.categoryIdentifier = categoryIdentifier
+
+ if identifier != nil { notificationContent.categoryIdentifier = identifier! }
+ if title != nil { notificationContent.title = title! }
+ if subtitle != nil { notificationContent.body = message! }
+ if message != nil { notificationContent.subtitle = subtitle! }
+
+ notificationContent.sound = UNNotificationSound.default
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
+ let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSinceNow)", content: notificationContent, trigger: trigger)
+
+
+ UNUserNotificationCenter.current().add(request) { error in
+ if let error = error {
+ print("Error: \(error)")
+ }
+ }
+ }
+}
+
+func appLanguageCode() -> Int{
+ let lang = UserDefaults.standard.string(forKey: "language") ?? "ar"
+ return lang == "ar" ? 2 : 1
+}
+
+func userProfile() -> [String:Any?]?{
+ var userProf = UserDefaults.standard.string(forKey: "flutter.imei-user-data")
+ if(userProf == nil){
+ userProf = UserDefaults.standard.string(forKey: "flutter.user-profile")
+ }
+ return dictionary(from: userProf ?? "{}")
+}
+
+fileprivate let defaultHTTPParams:[String : Any?] = [
+ "ZipCode" : "966",
+ "VersionID" : 5.8,
+ "Channel" : 3,
+ "LanguageID" : appLanguageCode(),
+ "IPAdress" : "10.20.10.20",
+ "generalid" : "Cs2020@2016$2958",
+ "PatientOutSA" : 0,
+ "SessionID" : nil,
+ "isDentalAllowedBackend" : false,
+ "DeviceTypeID" : 2
+]
+
+func httpPostRequest(urlString:String, jsonBody:[String:Any?], completion:((Bool,[String:Any]?)->Void)?){
+ var json: [String: Any?] = jsonBody
+ json = json.merge(dict: defaultHTTPParams)
+ let jsonData = try? JSONSerialization.data(withJSONObject: json)
+
+ // create post request
+ let url = URL(string: urlString)!
+ var request = URLRequest(url: url)
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ request.addValue("*/*", forHTTPHeaderField: "Accept")
+ request.httpMethod = "POST"
+ request.httpBody = jsonData
+
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
+ guard let data = data, error == nil else {
+ print(error?.localizedDescription ?? "No data")
+ return
+ }
+
+ let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
+ if let responseJSON = responseJSON as? [String: Any], let status = responseJSON["MessageStatus"] as? Int{
+ print(responseJSON)
+ if status == 1{
+ completion?(true,responseJSON)
+ }else{
+ completion?(false,responseJSON)
+ }
+
+ }else{
+ completion?(false,nil)
+ }
+ }
+
+ task.resume()
+
+}
diff --git a/ios/Helper/HMGPenguinInPlatformBridge.swift b/ios/Helper/HMGPenguinInPlatformBridge.swift
new file mode 100644
index 00000000..c4a44243
--- /dev/null
+++ b/ios/Helper/HMGPenguinInPlatformBridge.swift
@@ -0,0 +1,94 @@
+import Foundation
+import FLAnimatedImage
+
+
+var flutterMethodChannelPenguinIn:FlutterMethodChannel? = nil
+fileprivate var mainViewController:MainFlutterVC!
+
+class HMGPenguinInPlatformBridge{
+
+ private let channelName = "launch_penguin_ui"
+ private static var shared_:HMGPenguinInPlatformBridge?
+
+ class func initialize(flutterViewController:MainFlutterVC){
+ shared_ = HMGPenguinInPlatformBridge()
+ mainViewController = flutterViewController
+ shared_?.openChannel()
+ }
+
+ func shared() -> HMGPenguinInPlatformBridge{
+ assert((HMGPenguinInPlatformBridge.shared_ != nil), "HMGPenguinInPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
+ return HMGPenguinInPlatformBridge.shared_!
+ }
+
+ private func openChannel(){
+ flutterMethodChannelPenguinIn = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger)
+
+ flutterMethodChannelPenguinIn?.setMethodCallHandler { (methodCall, result) in
+ print("Called function \(methodCall.method)")
+
+ if let arguments = methodCall.arguments as Any? {
+ if methodCall.method == "launchPenguin"{
+ print("====== launchPenguinView Launched =========")
+ self.launchPenguinView(arguments: arguments, result: result)
+ }
+ } else {
+ result(FlutterError(code: "INVALID_ARGUMENT", message: "Storyboard name is required", details: nil))
+ }
+ }
+ }
+
+ private func launchPenguinView(arguments: Any, result: @escaping FlutterResult) {
+
+ let penguinView = PenguinView(
+ frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height),
+ viewIdentifier: 0,
+ arguments: arguments,
+ binaryMessenger: mainViewController.binaryMessenger
+ )
+
+ let penguinUIView = penguinView.view()
+ penguinUIView.frame = mainViewController.view.bounds
+ penguinUIView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ mainViewController.view.addSubview(penguinUIView)
+
+ guard let args = arguments as? [String: Any],
+ let loaderImageData = args["loaderImage"] as? FlutterStandardTypedData else {
+ print("loaderImage data not found in arguments")
+ result(FlutterError(code: "ARGUMENT_ERROR", message: "Missing loaderImage data", details: nil))
+ return
+ }
+
+ let loadingOverlay = UIView(frame: UIScreen.main.bounds)
+ loadingOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Semi-transparent overlay
+ loadingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ // Display the GIF using FLAnimatedImage
+ let animatedImage = FLAnimatedImage(animatedGIFData: loaderImageData.data)
+ let gifImageView = FLAnimatedImageView()
+ gifImageView.animatedImage = animatedImage
+ gifImageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
+ gifImageView.center = loadingOverlay.center
+ gifImageView.contentMode = .scaleAspectFit
+ loadingOverlay.addSubview(gifImageView)
+
+
+ if let window = UIApplication.shared.windows.first {
+ window.addSubview(loadingOverlay)
+
+ } else {
+ print("Error: Main window not found")
+ }
+
+ penguinView.onSuccess = {
+ // Hide and remove the loader
+ DispatchQueue.main.async {
+ loadingOverlay.removeFromSuperview()
+
+ }
+ }
+
+ result(nil)
+ }
+}
diff --git a/ios/Helper/HMGPlatformBridge.swift b/ios/Helper/HMGPlatformBridge.swift
new file mode 100644
index 00000000..fd9fb401
--- /dev/null
+++ b/ios/Helper/HMGPlatformBridge.swift
@@ -0,0 +1,140 @@
+//
+// HMGPlatformBridge.swift
+// Runner
+//
+// Created by ZiKambrani on 14/12/2020.
+//
+
+import UIKit
+import NetworkExtension
+import SystemConfiguration.CaptiveNetwork
+
+var flutterMethodChannel:FlutterMethodChannel? = nil
+fileprivate var mainViewController:MainFlutterVC!
+
+class HMGPlatformBridge{
+ private let channelName = "HMG-Platform-Bridge"
+ private static var shared_:HMGPlatformBridge?
+
+ class func initialize(flutterViewController:MainFlutterVC){
+ shared_ = HMGPlatformBridge()
+ mainViewController = flutterViewController
+ shared_?.openChannel()
+ }
+
+ func shared() -> HMGPlatformBridge{
+ assert((HMGPlatformBridge.shared_ != nil), "HMGPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
+ return HMGPlatformBridge.shared_!
+ }
+
+ private func openChannel(){
+ flutterMethodChannel = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger)
+ flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in
+ print("Called function \(methodCall.method)")
+ if methodCall.method == "connectHMGInternetWifi"{
+ self.connectHMGInternetWifi(methodCall:methodCall, result: result)
+
+ }else if methodCall.method == "connectHMGGuestWifi"{
+ self.connectHMGGuestWifi(methodCall:methodCall, result: result)
+
+ }else if methodCall.method == "isHMGNetworkAvailable"{
+ self.isHMGNetworkAvailable(methodCall:methodCall, result: result)
+
+ }else if methodCall.method == "registerHmgGeofences"{
+ self.registerHmgGeofences(result: result)
+
+ }else if methodCall.method == "unRegisterHmgGeofences"{
+ self.unRegisterHmgGeofences(result: result)
+ }
+
+ print("")
+ }
+ Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
+ FlutterConstants.set()
+ }
+ }
+
+
+
+ // Connect HMG Wifi and Internet
+ func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+
+ guard let pateintId = (methodCall.arguments as? [Any])?.first as? String
+ else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") }
+
+
+ HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ // Connect HMG-Guest for App Access
+ func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+ HMG_GUEST.shared.connect() { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{
+ guard let ssid = methodCall.arguments as? String else {
+ assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'")
+ return false
+ }
+
+ let queue = DispatchQueue.init(label: "com.hmg.wifilist")
+ NEHotspotHelper.register(options: nil, queue: queue) { (command) in
+ print(command)
+
+ if(command.commandType == NEHotspotHelperCommandType.filterScanList) {
+ if let networkList = command.networkList{
+ for network in networkList{
+ print(network.ssid)
+ }
+ }
+ }
+ }
+ return false
+
+ }
+
+
+ // Message Dailog
+ func showMessage(title:String, message:String){
+ DispatchQueue.main.async {
+ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
+ alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
+ mainViewController.present(alert, animated: true) {
+
+ }
+ }
+ }
+
+ // Register Geofence
+ func registerHmgGeofences(result: @escaping FlutterResult){
+ flutterMethodChannel?.invokeMethod("getGeoZones", arguments: nil){ geoFencesJsonString in
+ if let jsonString = geoFencesJsonString as? String{
+ let allZones = GeoZoneModel.list(from: jsonString)
+ HMG_Geofence.shared().register(geoZones: allZones)
+ result(true)
+ }else{
+ }
+ }
+ }
+
+ // Register Geofence
+ func unRegisterHmgGeofences(result: @escaping FlutterResult){
+ HMG_Geofence.shared().unRegisterAll()
+ result(true)
+ }
+
+}
diff --git a/ios/Helper/HMG_Geofence.swift b/ios/Helper/HMG_Geofence.swift
new file mode 100644
index 00000000..47454d3e
--- /dev/null
+++ b/ios/Helper/HMG_Geofence.swift
@@ -0,0 +1,183 @@
+//
+// HMG_Geofence.swift
+// Runner
+//
+// Created by ZiKambrani on 13/12/2020.
+//
+
+import UIKit
+import CoreLocation
+
+fileprivate var df = DateFormatter()
+fileprivate var transition = ""
+
+enum Transition:Int {
+ case entry = 1
+ case exit = 2
+ func name() -> String{
+ return self.rawValue == 1 ? "Enter" : "Exit"
+ }
+}
+
+class HMG_Geofence:NSObject{
+
+ var geoZones:[GeoZoneModel]?
+ var locationManager:CLLocationManager!{
+ didSet{
+ // https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati
+
+ locationManager.allowsBackgroundLocationUpdates = true
+ locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
+ locationManager.activityType = .other
+ locationManager.delegate = self
+ locationManager.requestAlwaysAuthorization()
+ // locationManager.distanceFilter = 500
+ // locationManager.startMonitoringSignificantLocationChanges()
+ }
+ }
+
+ private static var shared_:HMG_Geofence?
+ class func shared() -> HMG_Geofence{
+ if HMG_Geofence.shared_ == nil{
+ HMG_Geofence.initGeofencing()
+ }
+ return shared_!
+ }
+
+ class func initGeofencing(){
+ shared_ = HMG_Geofence()
+ shared_?.locationManager = CLLocationManager()
+ }
+
+ func register(geoZones:[GeoZoneModel]){
+
+ self.geoZones = geoZones
+
+ let monitoredRegions_ = monitoredRegions()
+ self.geoZones?.forEach({ (zone) in
+ if let region = zone.toRegion(locationManager: locationManager){
+ if let already = monitoredRegions_.first(where: {$0.identifier == zone.identifier()}){
+ debugPrint("Already monitering region: \(already)")
+ }else{
+ startMonitoring(region: region)
+ }
+ }else{
+ debugPrint("Invalid region: \(zone.latitude ?? "invalid_latitude"),\(zone.longitude ?? "invalid_longitude"),r\(zone.radius ?? 0) | \(zone.identifier())")
+ }
+ })
+ }
+
+ func monitoredRegions() -> Set{
+ return locationManager.monitoredRegions
+ }
+
+ func unRegisterAll(){
+ for region in locationManager.monitoredRegions {
+ locationManager.stopMonitoring(for: region)
+ }
+ }
+
+}
+
+// CLLocationManager Delegates
+extension HMG_Geofence : CLLocationManagerDelegate{
+
+ func startMonitoring(region: CLCircularRegion) {
+ if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
+ return
+ }
+
+ if CLLocationManager.authorizationStatus() != .authorizedAlways {
+ let message = """
+ Your geotification is saved but will only be activated once you grant
+ HMG permission to access the device location.
+ """
+ debugPrint(message)
+ }
+
+ locationManager.startMonitoring(for: region)
+ locationManager.requestState(for: region)
+ debugPrint("Starts monitering region: \(region)")
+ }
+
+ func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
+ debugPrint("didEnterRegion: \(region)")
+ if region is CLCircularRegion {
+ handleEvent(for: region,transition: .entry, location: manager.location)
+ }
+ }
+
+ func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
+ debugPrint("didExitRegion: \(region)")
+ if region is CLCircularRegion {
+ handleEvent(for: region,transition: .exit, location: manager.location)
+ }
+ }
+
+ func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
+ debugPrint("didDetermineState: \(state)")
+ }
+
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
+ debugPrint("didUpdateLocations: \(locations)")
+ }
+
+
+}
+
+// Helpers
+extension HMG_Geofence{
+
+ func handleEvent(for region: CLRegion!, transition:Transition, location:CLLocation?) {
+ if let userProfile = userProfile(){
+ notifyUser(forRegion: region, transition: transition, location: locationManager.location, userProfile: userProfile)
+ notifyServer(forRegion: region, transition: transition, location: locationManager.location, userProfile: userProfile)
+ }
+ }
+
+ func geoZone(by id: String) -> GeoZoneModel? {
+ var zone:GeoZoneModel? = nil
+ if let zones_ = geoZones{
+ zone = zones_.first(where: { $0.identifier() == id})
+ }else{
+ // let jsonArray = UserDefaults.standard.string(forKey: "hmg-geo-fences")
+ }
+ return zone
+ }
+
+
+ func notifyUser(forRegion:CLRegion, transition:Transition, location:CLLocation?, userProfile:[String:Any?]){
+ if let patientId = userProfile["PatientID"] as? Int{
+
+ }
+ }
+
+ func notifyServer(forRegion:CLRegion, transition:Transition, location:CLLocation?, userProfile:[String:Any?]){
+ if let patientId = userProfile["PatientID"] as? Int{
+
+ if let idString = forRegion.identifier.split(separator: "_").first, let idInt = Int(idString){
+ let body:[String:Any] = [
+ "PointsID":idInt,
+ "GeoType":transition.rawValue,
+ "PatientID":patientId
+ ]
+
+ var logs = UserDefaults.init(suiteName: "GeoFenceLog")?.dictionary(forKey: "GEOFENCE_LOGS") ?? [:]
+ var geo = (logs[forRegion.identifier] as? [String]) ?? []
+
+ let url = "https://hmgwebservices.com/Services/Patients.svc/REST/GeoF_InsertPatientFileInfo"
+ httpPostRequest(urlString: url, jsonBody: body){ (status,json) in
+ let status_ = status ? "Notified successfully:" : "Failed to notify:"
+ showNotification(title: transition.name(), subtitle: forRegion.identifier, message: status_)
+
+
+ geo.append("\(status_) \(transition.name()) at \(Date().toString(format: "dd/MMM/yyy hh:mm:ss"))")
+ logs.updateValue( geo, forKey: forRegion.identifier)
+
+ UserDefaults.init(suiteName: "GeoFenceLog")?.set(logs, forKey: "GEOFENCE_LOGS")
+ }
+ }
+ }
+ }
+}
+
diff --git a/ios/Helper/LocalizedFromFlutter.swift b/ios/Helper/LocalizedFromFlutter.swift
new file mode 100644
index 00000000..88530649
--- /dev/null
+++ b/ios/Helper/LocalizedFromFlutter.swift
@@ -0,0 +1,22 @@
+//
+// LocalizedFromFlutter.swift
+// Runner
+//
+// Created by ZiKambrani on 10/04/1442 AH.
+//
+
+import UIKit
+
+class FlutterText{
+
+ class func with(key:String,completion: @escaping (String)->Void){
+ flutterMethodChannel?.invokeMethod("localizedValue", arguments: key, result: { (result) in
+ if let localized = result as? String{
+ completion(localized)
+ }else{
+ completion(key)
+ }
+ })
+ }
+
+}
diff --git a/ios/Helper/OpenTokPlatformBridge.swift b/ios/Helper/OpenTokPlatformBridge.swift
new file mode 100644
index 00000000..4da39dc4
--- /dev/null
+++ b/ios/Helper/OpenTokPlatformBridge.swift
@@ -0,0 +1,61 @@
+//
+// HMGPlatformBridge.swift
+// Runner
+//
+// Created by ZiKambrani on 14/12/2020.
+//
+
+import UIKit
+import NetworkExtension
+import SystemConfiguration.CaptiveNetwork
+
+
+fileprivate var openTok:OpenTok?
+
+class OpenTokPlatformBridge : NSObject{
+ private var methodChannel:FlutterMethodChannel? = nil
+ private var mainViewController:MainFlutterVC!
+ private static var shared_:OpenTokPlatformBridge?
+
+ class func initialize(flutterViewController:MainFlutterVC, registrar:FlutterPluginRegistrar?){
+ shared_ = OpenTokPlatformBridge()
+ shared_?.mainViewController = flutterViewController
+
+ shared_?.openChannel()
+ openTok = OpenTok(mainViewController: flutterViewController, registrar: registrar)
+ }
+
+ func shared() -> OpenTokPlatformBridge{
+ assert((OpenTokPlatformBridge.shared_ != nil), "OpenTokPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
+ return OpenTokPlatformBridge.shared_!
+ }
+
+ private func openChannel(){
+ methodChannel = FlutterMethodChannel(name: "OpenTok-Platform-Bridge", binaryMessenger: mainViewController.binaryMessenger)
+ methodChannel?.setMethodCallHandler { (call, result) in
+ print("Called function \(call.method)")
+
+ switch(call.method) {
+ case "initSession":
+ openTok?.initSession(call: call, result: result)
+
+ case "swapCamera":
+ openTok?.swapCamera(call: call, result: result)
+
+ case "toggleAudio":
+ openTok?.toggleAudio(call: call, result: result)
+
+ case "toggleVideo":
+ openTok?.toggleVideo(call: call, result: result)
+
+ case "hangupCall":
+ openTok?.hangupCall(call: call, result: result)
+
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+
+ print("")
+ }
+ }
+}
diff --git a/ios/Penguin/PenguinModel.swift b/ios/Penguin/PenguinModel.swift
new file mode 100644
index 00000000..e41979d6
--- /dev/null
+++ b/ios/Penguin/PenguinModel.swift
@@ -0,0 +1,76 @@
+//
+// PenguinModel.swift
+// Runner
+//
+// Created by Amir on 06/08/2024.
+//
+
+import Foundation
+
+// Define the model class
+struct PenguinModel {
+ let baseURL: String
+ let dataURL: String
+ let dataServiceName: String
+ let positionURL: String
+ let clientKey: String
+ let storyboardName: String
+ let mapBoxKey: String
+ let clientID: String
+ let positionServiceName: String
+ let username: String
+ let isSimulationModeEnabled: Bool
+ let isShowUserName: Bool
+ let isUpdateUserLocationSmoothly: Bool
+ let isEnableReportIssue: Bool
+ let languageCode: String
+ let clinicID: String
+ let patientID: String
+ let projectID: String
+
+ // Initialize the model from a dictionary
+ init?(from dictionary: [String: Any]) {
+ guard
+ let baseURL = dictionary["baseURL"] as? String,
+ let dataURL = dictionary["dataURL"] as? String,
+ let dataServiceName = dictionary["dataServiceName"] as? String,
+ let positionURL = dictionary["positionURL"] as? String,
+ let clientKey = dictionary["clientKey"] as? String,
+ let storyboardName = dictionary["storyboardName"] as? String,
+ let mapBoxKey = dictionary["mapBoxKey"] as? String,
+ let clientID = dictionary["clientID"] as? String,
+ let positionServiceName = dictionary["positionServiceName"] as? String,
+ let username = dictionary["username"] as? String,
+ let isSimulationModeEnabled = dictionary["isSimulationModeEnabled"] as? Bool,
+ let isShowUserName = dictionary["isShowUserName"] as? Bool,
+ let isUpdateUserLocationSmoothly = dictionary["isUpdateUserLocationSmoothly"] as? Bool,
+ let isEnableReportIssue = dictionary["isEnableReportIssue"] as? Bool,
+ let languageCode = dictionary["languageCode"] as? String,
+ let clinicID = dictionary["clinicID"] as? String,
+ let patientID = dictionary["patientID"] as? String,
+ let projectID = dictionary["projectID"] as? String
+ else {
+ print("Initialization failed due to missing or invalid keys.")
+ return nil
+ }
+
+ self.baseURL = baseURL
+ self.dataURL = dataURL
+ self.dataServiceName = dataServiceName
+ self.positionURL = positionURL
+ self.clientKey = clientKey
+ self.storyboardName = storyboardName
+ self.mapBoxKey = mapBoxKey
+ self.clientID = clientID
+ self.positionServiceName = positionServiceName
+ self.username = username
+ self.isSimulationModeEnabled = isSimulationModeEnabled
+ self.isShowUserName = isShowUserName
+ self.isUpdateUserLocationSmoothly = isUpdateUserLocationSmoothly
+ self.isEnableReportIssue = isEnableReportIssue
+ self.languageCode = languageCode
+ self.clinicID = clinicID
+ self.patientID = patientID
+ self.projectID = projectID
+ }
+}
diff --git a/ios/Penguin/PenguinNavigator.swift b/ios/Penguin/PenguinNavigator.swift
new file mode 100644
index 00000000..e7ce55b4
--- /dev/null
+++ b/ios/Penguin/PenguinNavigator.swift
@@ -0,0 +1,57 @@
+import PenNavUI
+import UIKit
+
+class PenguinNavigator {
+ private var config: PenguinModel
+
+ init(config: PenguinModel) {
+ self.config = config
+ }
+
+ private func logError(_ message: String) {
+ // Centralized logging function
+ print("PenguinSDKNavigator Error: \(message)")
+ }
+
+ func navigateToPOI( referenceId:String,completion: @escaping (Bool, String?) -> Void) {
+ PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey) { [weak self] token, error in
+
+ if let error = error {
+ let errorMessage = "Token error while getting the for Navigate to method"
+ completion(false, "Failed to get token: \(errorMessage)")
+
+ print("Failed to get token: \(errorMessage)")
+ return
+ }
+
+ guard let token = token else {
+ completion(false, "Token is nil")
+ print("Token is nil")
+ return
+ }
+ print("Token Generated")
+ print(token);
+
+
+ }
+ }
+
+ private func handleNavigation(referenceId: String, token: String, completion: @escaping (Bool, String?) -> Void) {
+ DispatchQueue.main.async {
+ PenNavUIManager.shared.setToken(token: token)
+
+ PenNavUIManager.shared.navigate(to: referenceId) { [weak self] _, navError in
+ guard let self = self else { return }
+
+ if let navError = navError {
+ self.logError("Navigation error: Reference ID invalid")
+ completion(false, "Navigation error: \(navError.localizedDescription)")
+ return
+ }
+
+ // Navigation successful
+ completion(true, nil)
+ }
+ }
+ }
+}
diff --git a/ios/Penguin/PenguinPlugin.swift b/ios/Penguin/PenguinPlugin.swift
new file mode 100644
index 00000000..029bec35
--- /dev/null
+++ b/ios/Penguin/PenguinPlugin.swift
@@ -0,0 +1,31 @@
+//
+// BlueGpsPlugin.swift
+// Runner
+//
+// Created by Penguin .
+//
+
+//import Foundation
+//import Flutter
+//
+///**
+// * A Flutter plugin for integrating Penguin SDK functionality.
+// * This class registers a view factory with the Flutter engine to create native views.
+// */
+//class PenguinPlugin: NSObject, FlutterPlugin {
+//
+// /**
+// * Registers the plugin with the Flutter engine.
+// *
+// * @param registrar The [FlutterPluginRegistrar] used to register the plugin.
+// * This method is called when the plugin is initialized, and it sets up the communication
+// * between Flutter and native code.
+// */
+// public static func register(with registrar: FlutterPluginRegistrar) {
+// // Create an instance of PenguinViewFactory with the binary messenger from the registrar
+// let factory = PenguinViewFactory(messenger: registrar.messenger())
+//
+// // Register the view factory with a unique ID for use in Flutter code
+// registrar.register(factory, withId: "penguin_native")
+// }
+//}
diff --git a/ios/Penguin/PenguinView.swift b/ios/Penguin/PenguinView.swift
new file mode 100644
index 00000000..b5161eb0
--- /dev/null
+++ b/ios/Penguin/PenguinView.swift
@@ -0,0 +1,445 @@
+//
+
+// BlueGpsView.swift
+
+// Runner
+
+//
+
+// Created by Penguin.
+
+//
+
+
+
+import Foundation
+import UIKit
+import Flutter
+import PenNavUI
+
+import Foundation
+import Flutter
+import UIKit
+
+
+
+/**
+
+ * A custom Flutter platform view for displaying Penguin UI components.
+
+ * This class integrates with the Penguin navigation SDK and handles UI events.
+
+ */
+
+class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitializationDelegate
+
+{
+ // The main view displayed within the platform view
+
+ private var _view: UIView
+
+ private var model: PenguinModel?
+
+ private var methodChannel: FlutterMethodChannel
+
+ var onSuccess: (() -> Void)?
+
+
+
+
+
+
+
+ /**
+
+ * Initializes the PenguinView with the provided parameters.
+
+ *
+
+ * @param frame The frame of the view, specifying its size and position.
+
+ * @param viewId A unique identifier for this view instance.
+
+ * @param args Optional arguments provided for creating the view.
+
+ * @param messenger The [FlutterBinaryMessenger] used for communication with Dart.
+
+ */
+
+ init(
+
+ frame: CGRect,
+
+ viewIdentifier viewId: Int64,
+
+ arguments args: Any?,
+
+ binaryMessenger messenger: FlutterBinaryMessenger?
+
+ ) {
+
+ _view = UIView()
+
+ methodChannel = FlutterMethodChannel(name: "launch_penguin_ui", binaryMessenger: messenger!)
+
+
+
+ super.init()
+
+
+
+ // Get the screen's width and height to set the view's frame
+
+ let screenWidth = UIScreen.main.bounds.width
+
+ let screenHeight = UIScreen.main.bounds.height
+
+
+
+ // Uncomment to set the background color of the view
+
+ // _view.backgroundColor = UIColor.red
+
+
+
+ // Set the frame of the view to cover the entire screen
+
+ _view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
+
+ print("========Inside Penguin View ========")
+
+ print(args)
+
+ guard let arguments = args as? [String: Any] else {
+
+ print("Error: Arguments are not in the expected format.")
+
+ return
+
+ }
+
+ print("===== i got tha Args=======")
+
+
+
+ // Initialize the model from the arguments
+
+ if let penguinModel = PenguinModel(from: arguments) {
+
+ self.model = penguinModel
+
+ initPenguin(args: penguinModel)
+
+ } else {
+
+ print("Error: Failed to initialize PenguinModel from arguments ")
+
+ }
+
+ // Initialize the Penguin SDK with required configurations
+
+ // initPenguin( arguments: args)
+
+ }
+
+
+
+ /**
+
+ * Initializes the Penguin SDK with custom configuration settings.
+
+ */
+
+ func initPenguin(args: PenguinModel) {
+
+// Set the initialization delegate to handle SDK initialization events
+
+ PenNavUIManager.shared.initializationDelegate = self
+
+ // Configure the Penguin SDK with necessary parameters
+
+ PenNavUIManager.shared
+
+ .setClientKey(args.clientKey)
+
+ .setClientID(args.clientID)
+
+ .setUsername(args.username)
+
+ .setSimulationModeEnabled(isEnable: args.isSimulationModeEnabled)
+
+ .setBaseURL(dataURL: args.dataURL, positionURL: args.positionURL)
+
+ .setServiceName(dataServiceName: args.dataServiceName, positionServiceName: args.positionServiceName)
+
+ .setIsShowUserName(args.isShowUserName)
+
+ .setIsUpdateUserLocationSmoothly(args.isUpdateUserLocationSmoothly)
+
+ .setEnableReportIssue(enable: args.isEnableReportIssue)
+
+ .setLanguage(args.languageCode)
+
+ .setBackButtonVisibility(true)
+
+ .build()
+
+ }
+
+
+
+
+
+ /**
+
+ * Returns the main view associated with this platform view.
+
+ *
+
+ * @return The UIView instance that represents this platform view.
+
+ */
+
+ func view() -> UIView {
+
+ return _view
+
+ }
+
+
+
+ // MARK: - PIEventsDelegate Methods
+
+
+
+
+
+
+
+
+
+ /**
+
+ * Called when the Penguin UI is dismissed.
+
+ */
+
+ func onPenNavUIDismiss() {
+
+ // Handle UI dismissal if needed
+
+ print("====== onPenNavUIDismiss =========")
+
+
+
+
+
+ self.view().removeFromSuperview()
+
+ }
+
+
+
+ /**
+
+ * Called when a report issue is generated.
+
+ *
+
+ * @param issue The type of issue reported.
+
+ */
+
+ func onReportIssue(_ issue: PenNavUI.IssueType) {
+
+ // Handle report issue events if needed
+
+ print("====== onReportIssueError =========")
+
+ methodChannel.invokeMethod("onReportIssue", arguments: ["issueType": issue])
+
+
+
+ }
+
+
+
+ /**
+
+ * Called when the Penguin UI setup is successful.
+
+ */
+
+ func onPenNavSuccess() {
+
+ print("====== onPenNavSuccess =========")
+
+ onSuccess?()
+
+ methodChannel.invokeMethod("onPenNavSuccess", arguments: nil)
+
+ // Obtain the FlutterViewController instance
+
+ let controller: FlutterViewController = UIApplication.shared.windows.first?.rootViewController as! FlutterViewController
+
+
+
+ print("====== after controller onPenNavSuccess =========")
+
+
+
+ // Set the events delegate to handle SDK events
+
+ PenNavUIManager.shared.eventsDelegate = self
+
+
+
+ print("====== after eventsDelegate onPenNavSuccess =========")
+
+
+
+ // Present the Penguin UI on top of the Flutter view controller
+
+ PenNavUIManager.shared.present(root: controller, view: _view)
+
+
+
+
+
+ print("====== after present onPenNavSuccess =========")
+
+ print(model?.clinicID)
+
+ print("====== after present onPenNavSuccess =========")
+
+
+
+ guard let config = self.model else {
+
+ print("Error: Config Model is nil")
+
+ return
+
+ }
+
+
+
+ guard let clinicID = self.model?.clinicID,
+
+ let clientID = self.model?.clientID, !clientID.isEmpty else {
+
+ print("Error: Config Client ID is nil or empty")
+
+ return
+
+ }
+
+
+
+ let navigator = PenguinNavigator(config: config)
+
+
+
+ PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey) { [weak self] token, error in
+
+ if let error = error {
+
+ let errorMessage = "Token error while getting the for Navigate to method"
+
+ print("Failed to get token: \(errorMessage)")
+
+ return
+
+ }
+
+
+
+ guard let token = token else {
+
+ print("Token is nil")
+
+ return
+
+ }
+
+ print("Token Generated")
+
+ print(token);
+
+
+
+ self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in
+
+ if success {
+
+ print("Navigation successful")
+
+ } else {
+
+ print("Navigation failed: \(errorMessage ?? "Unknown error")")
+
+ }
+
+
+
+ }
+
+
+
+ print("====== after Token onPenNavSuccess =========")
+
+ }
+
+
+
+ }
+
+
+
+
+
+
+
+ private func handleNavigation(clinicID: String, token: String, completion: @escaping (Bool, String?) -> Void) {
+
+ DispatchQueue.main.async {
+
+ PenNavUIManager.shared.setToken(token: token)
+
+ PenNavUIManager.shared.navigate(to: clinicID)
+
+ completion(true,nil)
+
+ }
+
+ }
+
+
+
+
+
+
+
+
+
+ /**
+
+ * Called when there is an initialization error with the Penguin UI.
+
+ *
+
+ * @param errorType The type of initialization error.
+
+ * @param errorDescription A description of the error.
+
+ */
+
+ func onPenNavInitializationError(errorType: PenNavUI.PenNavUIError, errorDescription: String) {
+
+ // Handle initialization errors if needed
+
+ print("onPenNavInitializationErrorType: \(errorType.rawValue)")
+
+ print("onPenNavInitializationError: \(errorDescription)")
+ }
+}
diff --git a/ios/Penguin/PenguinViewFactory.swift b/ios/Penguin/PenguinViewFactory.swift
new file mode 100644
index 00000000..a88bb5d0
--- /dev/null
+++ b/ios/Penguin/PenguinViewFactory.swift
@@ -0,0 +1,59 @@
+//
+// BlueGpsViewFactory.swift
+// Runner
+//
+// Created by Penguin .
+//
+
+import Foundation
+import Flutter
+
+/**
+ * A factory class for creating instances of [PenguinView].
+ * This class implements `FlutterPlatformViewFactory` to create and manage native views.
+ */
+class PenguinViewFactory: NSObject, FlutterPlatformViewFactory {
+
+ // The binary messenger used for communication with the Flutter engine
+ private var messenger: FlutterBinaryMessenger
+
+ /**
+ * Initializes the PenguinViewFactory with the given messenger.
+ *
+ * @param messenger The [FlutterBinaryMessenger] used to communicate with Dart code.
+ */
+ init(messenger: FlutterBinaryMessenger) {
+ self.messenger = messenger
+ super.init()
+ }
+
+ /**
+ * Creates a new instance of [PenguinView].
+ *
+ * @param frame The frame of the view, specifying its size and position.
+ * @param viewId A unique identifier for this view instance.
+ * @param args Optional arguments provided for creating the view.
+ * @return An instance of [PenguinView] configured with the provided parameters.
+ */
+ func create(
+ withFrame frame: CGRect,
+ viewIdentifier viewId: Int64,
+ arguments args: Any?
+ ) -> FlutterPlatformView {
+ return PenguinView(
+ frame: frame,
+ viewIdentifier: viewId,
+ arguments: args,
+ binaryMessenger: messenger)
+ }
+
+ /**
+ * Returns the codec used for encoding and decoding method channel arguments.
+ * This method is required when `arguments` in `create` is not `nil`.
+ *
+ * @return A [FlutterMessageCodec] instance used for serialization.
+ */
+ public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
+ return FlutterStandardMessageCodec.sharedInstance()
+ }
+}
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 2eab03ad..7a41ae2c 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -11,11 +11,23 @@
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; };
+ 61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; };
+ 61243B4C2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */; };
+ 61243B502EC5FA3700D46FA0 /* PenguinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B452EC5FA3700D46FA0 /* PenguinView.swift */; };
+ 61243B542EC5FA3700D46FA0 /* PenguinPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */; };
+ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */; };
+ 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; };
+ 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; };
+ 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; };
+ 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- B976FB9C47411C32B24D5E01 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ACE60DF9393168FD748550B3 /* Pods_Runner.framework */; };
+ DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -35,6 +47,9 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
+ 766D8CB72EC60BE600D05E07 /* Penguin.xcframework in Embed Frameworks */,
+ 766D8CBB2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Embed Frameworks */,
+ 766D8CB92EC60BE600D05E07 /* PenNavUI.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
@@ -49,9 +64,18 @@
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
+ 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMGPenguinInPlatformBridge.swift; sourceTree = ""; };
+ 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinModel.swift; sourceTree = ""; };
+ 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinNavigator.swift; sourceTree = ""; };
+ 61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinPlugin.swift; sourceTree = ""; };
+ 61243B452EC5FA3700D46FA0 /* PenguinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinView.swift; sourceTree = ""; };
+ 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinViewFactory.swift; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
7595037DD52211B91157B0F3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = ""; };
+ 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = ""; };
+ 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = ""; };
769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
8E12CEEB8E334EE22D5259D7 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
@@ -62,7 +86,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- ACE60DF9393168FD748550B3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D6BB17A036DF7FCE75271203 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
@@ -71,7 +95,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- B976FB9C47411C32B24D5E01 /* Pods_Runner.framework in Frameworks */,
+ 766D8CB62EC60BE600D05E07 /* Penguin.xcframework in Frameworks */,
+ 766D8CBA2EC60BE600D05E07 /* PenguinINRenderer.xcframework in Frameworks */,
+ 766D8CB82EC60BE600D05E07 /* PenNavUI.xcframework in Frameworks */,
+ DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -86,6 +113,37 @@
path = RunnerTests;
sourceTree = "";
};
+ 61243B412EC5FA3700D46FA0 /* Helper */ = {
+ isa = PBXGroup;
+ children = (
+ 61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */,
+ );
+ path = Helper;
+ sourceTree = "";
+ };
+ 61243B472EC5FA3700D46FA0 /* Penguin */ = {
+ isa = PBXGroup;
+ children = (
+ 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */,
+ 61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */,
+ 61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */,
+ 61243B452EC5FA3700D46FA0 /* PenguinView.swift */,
+ 61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */,
+ );
+ path = Penguin;
+ sourceTree = "";
+ };
+ 766D8CB22EC60BE600D05E07 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 766D8CB32EC60BE600D05E07 /* Penguin.xcframework */,
+ 766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */,
+ 766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */,
+ D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
79DD2093A1D9674C94359FC8 /* Pods */ = {
isa = PBXGroup;
children = (
@@ -115,7 +173,7 @@
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
79DD2093A1D9674C94359FC8 /* Pods */,
- A07D637C76A0ABB38659D189 /* Frameworks */,
+ 766D8CB22EC60BE600D05E07 /* Frameworks */,
);
sourceTree = "";
};
@@ -131,6 +189,8 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
+ 61243B412EC5FA3700D46FA0 /* Helper */,
+ 61243B472EC5FA3700D46FA0 /* Penguin */,
769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */,
478CFA952E6E20A60064F3D7 /* Runner.entitlements */,
478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */,
@@ -146,14 +206,6 @@
path = Runner;
sourceTree = "";
};
- A07D637C76A0ABB38659D189 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- ACE60DF9393168FD748550B3 /* Pods_Runner.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -362,6 +414,12 @@
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */,
+ 61243B4C2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift in Sources */,
+ 61243B502EC5FA3700D46FA0 /* PenguinView.swift in Sources */,
+ 61243B542EC5FA3700D46FA0 /* PenguinPlugin.swift in Sources */,
+ 61243B562EC5FA3700D46FA0 /* PenguinNavigator.swift in Sources */,
+ 61243B572EC5FA3700D46FA0 /* PenguinViewFactory.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 6a5d34f1..64d7428e 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -1,7 +1,7 @@
import Flutter
import UIKit
-//import FirebaseCore
-//import FirebaseMessaging
+import FirebaseCore
+import FirebaseMessaging
import GoogleMaps
@main
@objc class AppDelegate: FlutterAppDelegate {
@@ -10,11 +10,18 @@ import GoogleMaps
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GMSServices.provideAPIKey("AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng")
-// FirebaseApp.configure()
+ FirebaseApp.configure()
+ initializePlatformChannels()
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
-
+ func initializePlatformChannels(){
+ if let mainViewController = window?.rootViewController as? FlutterViewController{ // platform initialization suppose to be in foreground
+
+ HMGPenguinInPlatformBridge.initialize(flutterViewController: mainViewController)
+
+ }
+ }
override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){
// Messaging.messaging().apnsToken = deviceToken
super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
diff --git a/ios/Runner/Controllers/MainFlutterVC.swift b/ios/Runner/Controllers/MainFlutterVC.swift
new file mode 100644
index 00000000..4f91d052
--- /dev/null
+++ b/ios/Runner/Controllers/MainFlutterVC.swift
@@ -0,0 +1,118 @@
+//
+// MainFlutterVC.swift
+// Runner
+//
+// Created by ZiKambrani on 25/03/1442 AH.
+//
+
+import UIKit
+import Flutter
+import NetworkExtension
+import SystemConfiguration.CaptiveNetwork
+
+class MainFlutterVC: FlutterViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+// flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in
+//
+// if methodCall.method == "connectHMGInternetWifi"{
+// self.connectHMGInternetWifi(methodCall:methodCall, result: result)
+//
+// }else if methodCall.method == "connectHMGGuestWifi"{
+// self.connectHMGGuestWifi(methodCall:methodCall, result: result)
+//
+// }else if methodCall.method == "isHMGNetworkAvailable"{
+// self.isHMGNetworkAvailable(methodCall:methodCall, result: result)
+//
+// }else if methodCall.method == "registerHmgGeofences"{
+// self.registerHmgGeofences(result: result)
+// }
+//
+// print("")
+// }
+//
+// FlutterText.with(key: "errorConnectingHmgNetwork") { (localized) in
+// print(localized)
+// }
+
+ }
+
+
+ // Connect HMG Wifi and Internet
+ func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+
+ guard let pateintId = (methodCall.arguments as? [Any])?.first as? String
+ else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") }
+
+
+ HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ // Connect HMG-Guest for App Access
+ func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+ HMG_GUEST.shared.connect() { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{
+ guard let ssid = methodCall.arguments as? String else {
+ assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'")
+ return false
+ }
+
+ let queue = DispatchQueue.init(label: "com.hmg.wifilist")
+ NEHotspotHelper.register(options: nil, queue: queue) { (command) in
+ print(command)
+
+ if(command.commandType == NEHotspotHelperCommandType.filterScanList) {
+ if let networkList = command.networkList{
+ for network in networkList{
+ print(network.ssid)
+ }
+ }
+ }
+ }
+ return false
+
+ }
+
+
+ // Message Dailog
+ func showMessage(title:String, message:String){
+ DispatchQueue.main.async {
+ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
+ alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
+ self.present(alert, animated: true) {
+
+ }
+ }
+ }
+
+ // Register Geofence
+ func registerHmgGeofences(result: @escaping FlutterResult){
+ flutterMethodChannel?.invokeMethod("getGeofencePreferenceKey", arguments: nil){ geoFencesJsonString in
+ if let jsonString = geoFencesJsonString as? String{
+ let allZones = GeoZoneModel.list(from: jsonString)
+ HMG_Geofence().register(geoZones: allZones)
+
+ }else{
+ }
+ }
+ }
+
+}
diff --git a/ios/Runner/Helper/API.swift b/ios/Runner/Helper/API.swift
new file mode 100644
index 00000000..b487f033
--- /dev/null
+++ b/ios/Runner/Helper/API.swift
@@ -0,0 +1,22 @@
+//
+// API.swift
+// Runner
+//
+// Created by ZiKambrani on 04/04/1442 AH.
+//
+
+import UIKit
+
+fileprivate let DOMAIN = "https://uat.hmgwebservices.com"
+fileprivate let SERVICE = "Services/Patients.svc/REST"
+fileprivate let BASE_URL = "\(DOMAIN)/\(SERVICE)"
+
+struct API {
+ static let WIFI_CREDENTIALS = "\(BASE_URL)/Hmg_SMS_Get_By_ProjectID_And_PatientID"
+}
+
+
+//struct API {
+// static let WIFI_CREDENTIALS = FlutterConstants.WIFI_CREDENTIALS_URL
+// static let LOG_GEOFENCE = FlutterConstants.LOG_GEOFENCE_URL
+//}
diff --git a/ios/Runner/Helper/Extensions.swift b/ios/Runner/Helper/Extensions.swift
new file mode 100644
index 00000000..de67f9b9
--- /dev/null
+++ b/ios/Runner/Helper/Extensions.swift
@@ -0,0 +1,150 @@
+//
+// Extensions.swift
+// Runner
+//
+// Created by ZiKambrani on 04/04/1442 AH.
+//
+
+import UIKit
+
+
+extension String{
+ func toUrl() -> URL?{
+ return URL(string: self)
+ }
+
+ func removeSpace() -> String?{
+ return self.replacingOccurrences(of: " ", with: "")
+ }
+}
+
+extension Date{
+ func toString(format:String) -> String{
+ let df = DateFormatter()
+ df.dateFormat = format
+ return df.string(from: self)
+ }
+}
+
+extension Dictionary{
+ func merge(dict:[String:Any?]) -> [String:Any?]{
+ var self_ = self as! [String:Any?]
+ dict.forEach { (kv) in
+ self_.updateValue(kv.value, forKey: kv.key)
+ }
+ return self_
+ }
+}
+
+extension Bundle {
+
+ func certificate(named name: String) -> SecCertificate {
+ let cerURL = self.url(forResource: name, withExtension: "cer")!
+ let cerData = try! Data(contentsOf: cerURL)
+ let cer = SecCertificateCreateWithData(nil, cerData as CFData)!
+ return cer
+ }
+
+ func identity(named name: String, password: String) -> SecIdentity {
+ let p12URL = self.url(forResource: name, withExtension: "p12")!
+ let p12Data = try! Data(contentsOf: p12URL)
+
+ var importedCF: CFArray? = nil
+ let options = [kSecImportExportPassphrase as String: password]
+ let err = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &importedCF)
+ precondition(err == errSecSuccess)
+ let imported = importedCF! as NSArray as! [[String:AnyObject]]
+ precondition(imported.count == 1)
+
+ return (imported[0][kSecImportItemIdentity as String]!) as! SecIdentity
+ }
+
+
+}
+
+extension SecCertificate{
+ func trust() -> Bool?{
+ var optionalTrust: SecTrust?
+ let policy = SecPolicyCreateBasicX509()
+
+ let status = SecTrustCreateWithCertificates([self] as AnyObject,
+ policy,
+ &optionalTrust)
+ guard status == errSecSuccess else { return false}
+ let trust = optionalTrust!
+
+ let stat = optionalTrust?.evaluateAllowing(rootCertificates: [self])
+ return stat
+ }
+
+ func secTrustObject() -> SecTrust?{
+ var optionalTrust: SecTrust?
+ let policy = SecPolicyCreateBasicX509()
+
+ let status = SecTrustCreateWithCertificates([self] as AnyObject,
+ policy,
+ &optionalTrust)
+ return optionalTrust
+ }
+}
+
+
+extension SecTrust {
+
+ func evaluate() -> Bool {
+ var trustResult: SecTrustResultType = .invalid
+ let err = SecTrustEvaluate(self, &trustResult)
+ guard err == errSecSuccess else { return false }
+ return [.proceed, .unspecified].contains(trustResult)
+ }
+
+ func evaluateAllowing(rootCertificates: [SecCertificate]) -> Bool {
+
+ // Apply our custom root to the trust object.
+
+ var err = SecTrustSetAnchorCertificates(self, rootCertificates as CFArray)
+ guard err == errSecSuccess else { return false }
+
+ // Re-enable the system's built-in root certificates.
+
+ err = SecTrustSetAnchorCertificatesOnly(self, false)
+ guard err == errSecSuccess else { return false }
+
+ // Run a trust evaluation and only allow the connection if it succeeds.
+
+ return self.evaluate()
+ }
+}
+
+
+extension UIView{
+ func show(){
+ self.alpha = 0.0
+ self.isHidden = false
+ UIView.animate(withDuration: 0.25, animations: {
+ self.alpha = 1
+ }) { (complete) in
+
+ }
+ }
+
+ func hide(){
+ UIView.animate(withDuration: 0.25, animations: {
+ self.alpha = 0.0
+ }) { (complete) in
+ self.isHidden = true
+ }
+ }
+}
+
+
+extension UIViewController{
+ func showAlert(withTitle: String, message: String){
+ let alert = UIAlertController(title: withTitle, message: message, preferredStyle: .alert)
+ alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
+ present(alert, animated: true) {
+
+ }
+ }
+}
+
diff --git a/ios/Runner/Helper/FlutterConstants.swift b/ios/Runner/Helper/FlutterConstants.swift
new file mode 100644
index 00000000..f1b3f098
--- /dev/null
+++ b/ios/Runner/Helper/FlutterConstants.swift
@@ -0,0 +1,36 @@
+//
+// FlutterConstants.swift
+// Runner
+//
+// Created by ZiKambrani on 22/12/2020.
+//
+
+import UIKit
+
+class FlutterConstants{
+ static var LOG_GEOFENCE_URL:String?
+ static var WIFI_CREDENTIALS_URL:String?
+ static var DEFAULT_HTTP_PARAMS:[String:Any?]?
+
+ class func set(){
+
+ // (FiX) Take a start with FlutterMethodChannel (kikstart)
+ /* First call to flutter method is not returning the correct value (Always returning 'NSObject') then after it wroking fine and returning correct value*/
+ FlutterText.with(key: "test") { (test) in
+
+ flutterMethodChannel?.invokeMethod("getDefaultHttpParameters", arguments: nil){ (response) in
+ if let defaultHTTPParams = response as? [String:Any?]{
+ DEFAULT_HTTP_PARAMS = defaultHTTPParams
+ }
+
+ }
+
+ flutterMethodChannel?.invokeMethod("getLogGeofenceFullUrl", arguments:nil){ (response) in
+ if let url = response as? String{
+ LOG_GEOFENCE_URL = url
+ }
+ }
+
+ }
+ }
+}
diff --git a/ios/Runner/Helper/GeoZoneModel.swift b/ios/Runner/Helper/GeoZoneModel.swift
new file mode 100644
index 00000000..e703b64c
--- /dev/null
+++ b/ios/Runner/Helper/GeoZoneModel.swift
@@ -0,0 +1,67 @@
+//
+// GeoZoneModel.swift
+// Runner
+//
+// Created by ZiKambrani on 13/12/2020.
+//
+
+import UIKit
+import CoreLocation
+
+class GeoZoneModel{
+ var geofenceId:Int = -1
+ var description:String = ""
+ var descriptionN:String?
+ var latitude:String?
+ var longitude:String?
+ var radius:Int?
+ var type:Int?
+ var projectID:Int?
+ var imageURL:String?
+ var isCity:String?
+
+ func identifier() -> String{
+ return "\(geofenceId)_hmg"
+ }
+
+ func message() -> String{
+ return description
+ }
+
+ func toRegion(locationManager:CLLocationManager) -> CLCircularRegion?{
+ if let rad = radius, let lat = latitude?.removeSpace(), let long = longitude?.removeSpace(),
+ let radius_d = Double("\(rad)"), let lat_d = Double(lat), let long_d = Double(long){
+
+ let coordinate = CLLocationCoordinate2D(latitude: lat_d, longitude: long_d)
+ let validatedRadius = min(radius_d, locationManager.maximumRegionMonitoringDistance)
+
+ let region = CLCircularRegion(center: coordinate, radius: validatedRadius, identifier: identifier())
+ region.notifyOnExit = true
+ region.notifyOnEntry = true
+ return region
+ }
+ return nil
+ }
+
+ class func from(json:[String:Any]) -> GeoZoneModel{
+ let model = GeoZoneModel()
+ model.geofenceId = json["GEOF_ID"] as? Int ?? 0
+ model.radius = json["Radius"] as? Int
+ model.projectID = json["ProjectID"] as? Int
+ model.type = json["Type"] as? Int
+ model.description = json["Description"] as? String ?? ""
+ model.descriptionN = json["DescriptionN"] as? String
+ model.latitude = json["Latitude"] as? String
+ model.longitude = json["Longitude"] as? String
+ model.imageURL = json["ImageURL"] as? String
+ model.isCity = json["IsCity"] as? String
+
+ return model
+ }
+
+ class func list(from jsonString:String) -> [GeoZoneModel]{
+ let value = dictionaryArray(from: jsonString)
+ let geoZones = value.map { GeoZoneModel.from(json: $0) }
+ return geoZones
+ }
+}
diff --git a/ios/Runner/Helper/GlobalHelper.swift b/ios/Runner/Helper/GlobalHelper.swift
new file mode 100644
index 00000000..37687806
--- /dev/null
+++ b/ios/Runner/Helper/GlobalHelper.swift
@@ -0,0 +1,119 @@
+//
+// GlobalHelper.swift
+// Runner
+//
+// Created by ZiKambrani on 29/03/1442 AH.
+//
+
+import UIKit
+
+func dictionaryArray(from:String) -> [[String:Any]]{
+ if let data = from.data(using: .utf8) {
+ do {
+ return try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] ?? []
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ return []
+
+}
+
+func dictionary(from:String) -> [String:Any]?{
+ if let data = from.data(using: .utf8) {
+ do {
+ return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ return nil
+
+}
+
+let HmgLocalNotificationCategoryIdentifier = "hmg.local.notification"
+func showNotification(identifier:String? = nil, title:String?, subtitle:String?, message:String?, sound:UNNotificationSound = UNNotificationSound.default, categoryIdentifier:String = HmgLocalNotificationCategoryIdentifier){
+ DispatchQueue.main.async {
+ let notificationContent = UNMutableNotificationContent()
+ notificationContent.categoryIdentifier = categoryIdentifier
+
+ if identifier != nil { notificationContent.categoryIdentifier = identifier! }
+ if title != nil { notificationContent.title = title! }
+ if subtitle != nil { notificationContent.body = message! }
+ if message != nil { notificationContent.subtitle = subtitle! }
+
+ notificationContent.sound = UNNotificationSound.default
+ let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
+ let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSinceNow)", content: notificationContent, trigger: trigger)
+
+
+ UNUserNotificationCenter.current().add(request) { error in
+ if let error = error {
+ print("Error: \(error)")
+ }
+ }
+ }
+}
+
+func appLanguageCode() -> Int{
+ let lang = UserDefaults.standard.string(forKey: "language") ?? "ar"
+ return lang == "ar" ? 2 : 1
+}
+
+func userProfile() -> [String:Any?]?{
+ var userProf = UserDefaults.standard.string(forKey: "flutter.imei-user-data")
+ if(userProf == nil){
+ userProf = UserDefaults.standard.string(forKey: "flutter.user-profile")
+ }
+ return dictionary(from: userProf ?? "{}")
+}
+
+fileprivate let defaultHTTPParams:[String : Any?] = [
+ "ZipCode" : "966",
+ "VersionID" : 5.8,
+ "Channel" : 3,
+ "LanguageID" : appLanguageCode(),
+ "IPAdress" : "10.20.10.20",
+ "generalid" : "Cs2020@2016$2958",
+ "PatientOutSA" : 0,
+ "SessionID" : nil,
+ "isDentalAllowedBackend" : false,
+ "DeviceTypeID" : 2
+]
+
+func httpPostRequest(urlString:String, jsonBody:[String:Any?], completion:((Bool,[String:Any]?)->Void)?){
+ var json: [String: Any?] = jsonBody
+ json = json.merge(dict: defaultHTTPParams)
+ let jsonData = try? JSONSerialization.data(withJSONObject: json)
+
+ // create post request
+ let url = URL(string: urlString)!
+ var request = URLRequest(url: url)
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ request.addValue("*/*", forHTTPHeaderField: "Accept")
+ request.httpMethod = "POST"
+ request.httpBody = jsonData
+
+ let task = URLSession.shared.dataTask(with: request) { data, response, error in
+ guard let data = data, error == nil else {
+ print(error?.localizedDescription ?? "No data")
+ return
+ }
+
+ let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
+ if let responseJSON = responseJSON as? [String: Any], let status = responseJSON["MessageStatus"] as? Int{
+ print(responseJSON)
+ if status == 1{
+ completion?(true,responseJSON)
+ }else{
+ completion?(false,responseJSON)
+ }
+
+ }else{
+ completion?(false,nil)
+ }
+ }
+
+ task.resume()
+
+}
diff --git a/ios/Runner/Helper/HMGPenguinInPlatformBridge.swift b/ios/Runner/Helper/HMGPenguinInPlatformBridge.swift
new file mode 100644
index 00000000..db02e8f9
--- /dev/null
+++ b/ios/Runner/Helper/HMGPenguinInPlatformBridge.swift
@@ -0,0 +1,94 @@
+import Foundation
+import FLAnimatedImage
+
+
+var flutterMethodChannelPenguinIn:FlutterMethodChannel? = nil
+fileprivate var mainViewController:FlutterViewController!
+
+class HMGPenguinInPlatformBridge{
+
+ private let channelName = "launch_penguin_ui"
+ private static var shared_:HMGPenguinInPlatformBridge?
+
+ class func initialize(flutterViewController:FlutterViewController){
+ shared_ = HMGPenguinInPlatformBridge()
+ mainViewController = flutterViewController
+ shared_?.openChannel()
+ }
+
+ func shared() -> HMGPenguinInPlatformBridge{
+ assert((HMGPenguinInPlatformBridge.shared_ != nil), "HMGPenguinInPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
+ return HMGPenguinInPlatformBridge.shared_!
+ }
+
+ private func openChannel(){
+ flutterMethodChannelPenguinIn = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger)
+
+ flutterMethodChannelPenguinIn?.setMethodCallHandler { (methodCall, result) in
+ print("Called function \(methodCall.method)")
+
+ if let arguments = methodCall.arguments as Any? {
+ if methodCall.method == "launchPenguin"{
+ print("====== launchPenguinView Launched =========")
+ self.launchPenguinView(arguments: arguments, result: result)
+ }
+ } else {
+ result(FlutterError(code: "INVALID_ARGUMENT", message: "Storyboard name is required", details: nil))
+ }
+ }
+ }
+
+ private func launchPenguinView(arguments: Any, result: @escaping FlutterResult) {
+
+ let penguinView = PenguinView(
+ frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height),
+ viewIdentifier: 0,
+ arguments: arguments,
+ binaryMessenger: mainViewController.binaryMessenger
+ )
+
+ let penguinUIView = penguinView.view()
+ penguinUIView.frame = mainViewController.view.bounds
+ penguinUIView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ mainViewController.view.addSubview(penguinUIView)
+
+ let args = arguments as? [String: Any]
+// let loaderImageData = args["loaderImage"] as? FlutterStandardTypedData else {
+// print("loaderImage data not found in arguments")
+// result(FlutterError(code: "ARGUMENT_ERROR", message: "Missing loaderImage data", details: nil))
+// return
+// }
+
+// let loadingOverlay = UIView(frame: UIScreen.main.bounds)
+// loadingOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Semi-transparent overlay
+// loadingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+
+ // Display the GIF using FLAnimatedImage
+// let animatedImage = FLAnimatedImage(animatedGIFData: loaderImageData.data)
+// let gifImageView = FLAnimatedImageView()
+// gifImageView.animatedImage = animatedImage
+// gifImageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
+// gifImageView.center = loadingOverlay.center
+// gifImageView.contentMode = .scaleAspectFit
+// loadingOverlay.addSubview(gifImageView)
+
+
+// if let window = UIApplication.shared.windows.first {
+// window.addSubview(loadingOverlay)
+//
+// } else {
+// print("Error: Main window not found")
+// }
+
+ penguinView.onSuccess = {
+ // Hide and remove the loader
+// DispatchQueue.main.async {
+// loadingOverlay.removeFromSuperview()
+//
+// }
+ }
+
+ result(nil)
+ }
+}
diff --git a/ios/Runner/Helper/HMGPlatformBridge.swift b/ios/Runner/Helper/HMGPlatformBridge.swift
new file mode 100644
index 00000000..fd9fb401
--- /dev/null
+++ b/ios/Runner/Helper/HMGPlatformBridge.swift
@@ -0,0 +1,140 @@
+//
+// HMGPlatformBridge.swift
+// Runner
+//
+// Created by ZiKambrani on 14/12/2020.
+//
+
+import UIKit
+import NetworkExtension
+import SystemConfiguration.CaptiveNetwork
+
+var flutterMethodChannel:FlutterMethodChannel? = nil
+fileprivate var mainViewController:MainFlutterVC!
+
+class HMGPlatformBridge{
+ private let channelName = "HMG-Platform-Bridge"
+ private static var shared_:HMGPlatformBridge?
+
+ class func initialize(flutterViewController:MainFlutterVC){
+ shared_ = HMGPlatformBridge()
+ mainViewController = flutterViewController
+ shared_?.openChannel()
+ }
+
+ func shared() -> HMGPlatformBridge{
+ assert((HMGPlatformBridge.shared_ != nil), "HMGPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
+ return HMGPlatformBridge.shared_!
+ }
+
+ private func openChannel(){
+ flutterMethodChannel = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger)
+ flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in
+ print("Called function \(methodCall.method)")
+ if methodCall.method == "connectHMGInternetWifi"{
+ self.connectHMGInternetWifi(methodCall:methodCall, result: result)
+
+ }else if methodCall.method == "connectHMGGuestWifi"{
+ self.connectHMGGuestWifi(methodCall:methodCall, result: result)
+
+ }else if methodCall.method == "isHMGNetworkAvailable"{
+ self.isHMGNetworkAvailable(methodCall:methodCall, result: result)
+
+ }else if methodCall.method == "registerHmgGeofences"{
+ self.registerHmgGeofences(result: result)
+
+ }else if methodCall.method == "unRegisterHmgGeofences"{
+ self.unRegisterHmgGeofences(result: result)
+ }
+
+ print("")
+ }
+ Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
+ FlutterConstants.set()
+ }
+ }
+
+
+
+ // Connect HMG Wifi and Internet
+ func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+
+ guard let pateintId = (methodCall.arguments as? [Any])?.first as? String
+ else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") }
+
+
+ HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ // Connect HMG-Guest for App Access
+ func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
+ HMG_GUEST.shared.connect() { (status, message) in
+ result(status ? 1 : 0)
+ if status{
+ self.showMessage(title:"Congratulations", message:message)
+ }else{
+ self.showMessage(title:"Ooops,", message:message)
+ }
+ }
+ }
+
+ func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{
+ guard let ssid = methodCall.arguments as? String else {
+ assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'")
+ return false
+ }
+
+ let queue = DispatchQueue.init(label: "com.hmg.wifilist")
+ NEHotspotHelper.register(options: nil, queue: queue) { (command) in
+ print(command)
+
+ if(command.commandType == NEHotspotHelperCommandType.filterScanList) {
+ if let networkList = command.networkList{
+ for network in networkList{
+ print(network.ssid)
+ }
+ }
+ }
+ }
+ return false
+
+ }
+
+
+ // Message Dailog
+ func showMessage(title:String, message:String){
+ DispatchQueue.main.async {
+ let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
+ alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
+ mainViewController.present(alert, animated: true) {
+
+ }
+ }
+ }
+
+ // Register Geofence
+ func registerHmgGeofences(result: @escaping FlutterResult){
+ flutterMethodChannel?.invokeMethod("getGeoZones", arguments: nil){ geoFencesJsonString in
+ if let jsonString = geoFencesJsonString as? String{
+ let allZones = GeoZoneModel.list(from: jsonString)
+ HMG_Geofence.shared().register(geoZones: allZones)
+ result(true)
+ }else{
+ }
+ }
+ }
+
+ // Register Geofence
+ func unRegisterHmgGeofences(result: @escaping FlutterResult){
+ HMG_Geofence.shared().unRegisterAll()
+ result(true)
+ }
+
+}
diff --git a/ios/Runner/Helper/HMG_Geofence.swift b/ios/Runner/Helper/HMG_Geofence.swift
new file mode 100644
index 00000000..47454d3e
--- /dev/null
+++ b/ios/Runner/Helper/HMG_Geofence.swift
@@ -0,0 +1,183 @@
+//
+// HMG_Geofence.swift
+// Runner
+//
+// Created by ZiKambrani on 13/12/2020.
+//
+
+import UIKit
+import CoreLocation
+
+fileprivate var df = DateFormatter()
+fileprivate var transition = ""
+
+enum Transition:Int {
+ case entry = 1
+ case exit = 2
+ func name() -> String{
+ return self.rawValue == 1 ? "Enter" : "Exit"
+ }
+}
+
+class HMG_Geofence:NSObject{
+
+ var geoZones:[GeoZoneModel]?
+ var locationManager:CLLocationManager!{
+ didSet{
+ // https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati
+
+ locationManager.allowsBackgroundLocationUpdates = true
+ locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
+ locationManager.activityType = .other
+ locationManager.delegate = self
+ locationManager.requestAlwaysAuthorization()
+ // locationManager.distanceFilter = 500
+ // locationManager.startMonitoringSignificantLocationChanges()
+ }
+ }
+
+ private static var shared_:HMG_Geofence?
+ class func shared() -> HMG_Geofence{
+ if HMG_Geofence.shared_ == nil{
+ HMG_Geofence.initGeofencing()
+ }
+ return shared_!
+ }
+
+ class func initGeofencing(){
+ shared_ = HMG_Geofence()
+ shared_?.locationManager = CLLocationManager()
+ }
+
+ func register(geoZones:[GeoZoneModel]){
+
+ self.geoZones = geoZones
+
+ let monitoredRegions_ = monitoredRegions()
+ self.geoZones?.forEach({ (zone) in
+ if let region = zone.toRegion(locationManager: locationManager){
+ if let already = monitoredRegions_.first(where: {$0.identifier == zone.identifier()}){
+ debugPrint("Already monitering region: \(already)")
+ }else{
+ startMonitoring(region: region)
+ }
+ }else{
+ debugPrint("Invalid region: \(zone.latitude ?? "invalid_latitude"),\(zone.longitude ?? "invalid_longitude"),r\(zone.radius ?? 0) | \(zone.identifier())")
+ }
+ })
+ }
+
+ func monitoredRegions() -> Set{
+ return locationManager.monitoredRegions
+ }
+
+ func unRegisterAll(){
+ for region in locationManager.monitoredRegions {
+ locationManager.stopMonitoring(for: region)
+ }
+ }
+
+}
+
+// CLLocationManager Delegates
+extension HMG_Geofence : CLLocationManagerDelegate{
+
+ func startMonitoring(region: CLCircularRegion) {
+ if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
+ return
+ }
+
+ if CLLocationManager.authorizationStatus() != .authorizedAlways {
+ let message = """
+ Your geotification is saved but will only be activated once you grant
+ HMG permission to access the device location.
+ """
+ debugPrint(message)
+ }
+
+ locationManager.startMonitoring(for: region)
+ locationManager.requestState(for: region)
+ debugPrint("Starts monitering region: \(region)")
+ }
+
+ func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
+ debugPrint("didEnterRegion: \(region)")
+ if region is CLCircularRegion {
+ handleEvent(for: region,transition: .entry, location: manager.location)
+ }
+ }
+
+ func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
+ debugPrint("didExitRegion: \(region)")
+ if region is CLCircularRegion {
+ handleEvent(for: region,transition: .exit, location: manager.location)
+ }
+ }
+
+ func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
+ debugPrint("didDetermineState: \(state)")
+ }
+
+ func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
+ debugPrint("didUpdateLocations: \(locations)")
+ }
+
+
+}
+
+// Helpers
+extension HMG_Geofence{
+
+ func handleEvent(for region: CLRegion!, transition:Transition, location:CLLocation?) {
+ if let userProfile = userProfile(){
+ notifyUser(forRegion: region, transition: transition, location: locationManager.location, userProfile: userProfile)
+ notifyServer(forRegion: region, transition: transition, location: locationManager.location, userProfile: userProfile)
+ }
+ }
+
+ func geoZone(by id: String) -> GeoZoneModel? {
+ var zone:GeoZoneModel? = nil
+ if let zones_ = geoZones{
+ zone = zones_.first(where: { $0.identifier() == id})
+ }else{
+ // let jsonArray = UserDefaults.standard.string(forKey: "hmg-geo-fences")
+ }
+ return zone
+ }
+
+
+ func notifyUser(forRegion:CLRegion, transition:Transition, location:CLLocation?, userProfile:[String:Any?]){
+ if let patientId = userProfile["PatientID"] as? Int{
+
+ }
+ }
+
+ func notifyServer(forRegion:CLRegion, transition:Transition, location:CLLocation?, userProfile:[String:Any?]){
+ if let patientId = userProfile["PatientID"] as? Int{
+
+ if let idString = forRegion.identifier.split(separator: "_").first, let idInt = Int(idString){
+ let body:[String:Any] = [
+ "PointsID":idInt,
+ "GeoType":transition.rawValue,
+ "PatientID":patientId
+ ]
+
+ var logs = UserDefaults.init(suiteName: "GeoFenceLog")?.dictionary(forKey: "GEOFENCE_LOGS") ?? [:]
+ var geo = (logs[forRegion.identifier] as? [String]) ?? []
+
+ let url = "https://hmgwebservices.com/Services/Patients.svc/REST/GeoF_InsertPatientFileInfo"
+ httpPostRequest(urlString: url, jsonBody: body){ (status,json) in
+ let status_ = status ? "Notified successfully:" : "Failed to notify:"
+ showNotification(title: transition.name(), subtitle: forRegion.identifier, message: status_)
+
+
+ geo.append("\(status_) \(transition.name()) at \(Date().toString(format: "dd/MMM/yyy hh:mm:ss"))")
+ logs.updateValue( geo, forKey: forRegion.identifier)
+
+ UserDefaults.init(suiteName: "GeoFenceLog")?.set(logs, forKey: "GEOFENCE_LOGS")
+ }
+ }
+ }
+ }
+}
+
diff --git a/ios/Runner/Helper/LocalizedFromFlutter.swift b/ios/Runner/Helper/LocalizedFromFlutter.swift
new file mode 100644
index 00000000..88530649
--- /dev/null
+++ b/ios/Runner/Helper/LocalizedFromFlutter.swift
@@ -0,0 +1,22 @@
+//
+// LocalizedFromFlutter.swift
+// Runner
+//
+// Created by ZiKambrani on 10/04/1442 AH.
+//
+
+import UIKit
+
+class FlutterText{
+
+ class func with(key:String,completion: @escaping (String)->Void){
+ flutterMethodChannel?.invokeMethod("localizedValue", arguments: key, result: { (result) in
+ if let localized = result as? String{
+ completion(localized)
+ }else{
+ completion(key)
+ }
+ })
+ }
+
+}
diff --git a/ios/Runner/Helper/OpenTokPlatformBridge.swift b/ios/Runner/Helper/OpenTokPlatformBridge.swift
new file mode 100644
index 00000000..4da39dc4
--- /dev/null
+++ b/ios/Runner/Helper/OpenTokPlatformBridge.swift
@@ -0,0 +1,61 @@
+//
+// HMGPlatformBridge.swift
+// Runner
+//
+// Created by ZiKambrani on 14/12/2020.
+//
+
+import UIKit
+import NetworkExtension
+import SystemConfiguration.CaptiveNetwork
+
+
+fileprivate var openTok:OpenTok?
+
+class OpenTokPlatformBridge : NSObject{
+ private var methodChannel:FlutterMethodChannel? = nil
+ private var mainViewController:MainFlutterVC!
+ private static var shared_:OpenTokPlatformBridge?
+
+ class func initialize(flutterViewController:MainFlutterVC, registrar:FlutterPluginRegistrar?){
+ shared_ = OpenTokPlatformBridge()
+ shared_?.mainViewController = flutterViewController
+
+ shared_?.openChannel()
+ openTok = OpenTok(mainViewController: flutterViewController, registrar: registrar)
+ }
+
+ func shared() -> OpenTokPlatformBridge{
+ assert((OpenTokPlatformBridge.shared_ != nil), "OpenTokPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
+ return OpenTokPlatformBridge.shared_!
+ }
+
+ private func openChannel(){
+ methodChannel = FlutterMethodChannel(name: "OpenTok-Platform-Bridge", binaryMessenger: mainViewController.binaryMessenger)
+ methodChannel?.setMethodCallHandler { (call, result) in
+ print("Called function \(call.method)")
+
+ switch(call.method) {
+ case "initSession":
+ openTok?.initSession(call: call, result: result)
+
+ case "swapCamera":
+ openTok?.swapCamera(call: call, result: result)
+
+ case "toggleAudio":
+ openTok?.toggleAudio(call: call, result: result)
+
+ case "toggleVideo":
+ openTok?.toggleVideo(call: call, result: result)
+
+ case "hangupCall":
+ openTok?.hangupCall(call: call, result: result)
+
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+
+ print("")
+ }
+ }
+}
diff --git a/ios/Runner/Penguin/PenguinModel.swift b/ios/Runner/Penguin/PenguinModel.swift
new file mode 100644
index 00000000..7b6ab2d1
--- /dev/null
+++ b/ios/Runner/Penguin/PenguinModel.swift
@@ -0,0 +1,77 @@
+//
+// PenguinModel.swift
+// Runner
+//
+// Created by Amir on 06/08/2024.
+//
+
+import Foundation
+
+// Define the model class
+struct PenguinModel {
+ let baseURL: String
+ let dataURL: String
+ let dataServiceName: String
+ let positionURL: String
+ let clientKey: String
+ let storyboardName: String
+ let mapBoxKey: String
+ let clientID: String
+ let positionServiceName: String
+ let username: String
+ let isSimulationModeEnabled: Bool
+ let isShowUserName: Bool
+ let isUpdateUserLocationSmoothly: Bool
+ let isEnableReportIssue: Bool
+ let languageCode: String
+ let clinicID: String
+ let patientID: String
+ let projectID: Int
+
+ // Initialize the model from a dictionary
+ init?(from dictionary: [String: Any]) {
+
+ guard
+ let baseURL = dictionary["baseURL"] as? String,
+ let dataURL = dictionary["dataURL"] as? String,
+ let dataServiceName = dictionary["dataServiceName"] as? String,
+ let positionURL = dictionary["positionURL"] as? String,
+ let clientKey = dictionary["clientKey"] as? String,
+ let storyboardName = dictionary["storyboardName"] as? String,
+ let mapBoxKey = dictionary["mapBoxKey"] as? String,
+ let clientID = dictionary["clientID"] as? String,
+ let positionServiceName = dictionary["positionServiceName"] as? String,
+ let username = dictionary["username"] as? String,
+ let isSimulationModeEnabled = dictionary["isSimulationModeEnabled"] as? Bool,
+ let isShowUserName = dictionary["isShowUserName"] as? Bool,
+ let isUpdateUserLocationSmoothly = dictionary["isUpdateUserLocationSmoothly"] as? Bool,
+ let isEnableReportIssue = dictionary["isEnableReportIssue"] as? Bool,
+ let languageCode = dictionary["languageCode"] as? String,
+ let clinicID = dictionary["clinicID"] as? String,
+ let patientID = dictionary["patientID"] as? String,
+ let projectID = dictionary["projectID"] as? Int
+ else {
+ print("Initialization failed due to missing or invalid keys.")
+ return nil
+ }
+
+ self.baseURL = baseURL
+ self.dataURL = dataURL
+ self.dataServiceName = dataServiceName
+ self.positionURL = positionURL
+ self.clientKey = clientKey
+ self.storyboardName = storyboardName
+ self.mapBoxKey = mapBoxKey
+ self.clientID = clientID
+ self.positionServiceName = positionServiceName
+ self.username = username
+ self.isSimulationModeEnabled = isSimulationModeEnabled
+ self.isShowUserName = isShowUserName
+ self.isUpdateUserLocationSmoothly = isUpdateUserLocationSmoothly
+ self.isEnableReportIssue = isEnableReportIssue
+ self.languageCode = languageCode
+ self.clinicID = clinicID
+ self.patientID = patientID
+ self.projectID = projectID
+ }
+}
diff --git a/ios/Runner/Penguin/PenguinNavigator.swift b/ios/Runner/Penguin/PenguinNavigator.swift
new file mode 100644
index 00000000..31cf6262
--- /dev/null
+++ b/ios/Runner/Penguin/PenguinNavigator.swift
@@ -0,0 +1,57 @@
+import PenNavUI
+import UIKit
+
+class PenguinNavigator {
+ private var config: PenguinModel
+
+ init(config: PenguinModel) {
+ self.config = config
+ }
+
+ private func logError(_ message: String) {
+ // Centralized logging function
+ print("PenguinSDKNavigator Error: \(message)")
+ }
+
+ func navigateToPOI( referenceId:String,completion: @escaping (Bool, String?) -> Void) {
+ PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey, showProgress: true) { [weak self] token, error in
+
+ if let error = error {
+ let errorMessage = "Token error while getting the for Navigate to method"
+ completion(false, "Failed to get token: \(errorMessage)")
+
+ print("Failed to get token: \(errorMessage)")
+ return
+ }
+
+ guard let token = token else {
+ completion(false, "Token is nil")
+ print("Token is nil")
+ return
+ }
+ print("Token Generated")
+ print(token);
+
+
+ }
+ }
+
+ private func handleNavigation(referenceId: String, token: String, completion: @escaping (Bool, String?) -> Void) {
+ DispatchQueue.main.async {
+ PenNavUIManager.shared.setToken(token: token)
+
+ PenNavUIManager.shared.navigate(to: referenceId) { [weak self] _, navError in
+ guard let self = self else { return }
+
+ if let navError = navError {
+ self.logError("Navigation error: Reference ID invalid")
+ completion(false, "Navigation error: \(navError.localizedDescription)")
+ return
+ }
+
+ // Navigation successful
+ completion(true, nil)
+ }
+ }
+ }
+}
diff --git a/ios/Runner/Penguin/PenguinPlugin.swift b/ios/Runner/Penguin/PenguinPlugin.swift
new file mode 100644
index 00000000..029bec35
--- /dev/null
+++ b/ios/Runner/Penguin/PenguinPlugin.swift
@@ -0,0 +1,31 @@
+//
+// BlueGpsPlugin.swift
+// Runner
+//
+// Created by Penguin .
+//
+
+//import Foundation
+//import Flutter
+//
+///**
+// * A Flutter plugin for integrating Penguin SDK functionality.
+// * This class registers a view factory with the Flutter engine to create native views.
+// */
+//class PenguinPlugin: NSObject, FlutterPlugin {
+//
+// /**
+// * Registers the plugin with the Flutter engine.
+// *
+// * @param registrar The [FlutterPluginRegistrar] used to register the plugin.
+// * This method is called when the plugin is initialized, and it sets up the communication
+// * between Flutter and native code.
+// */
+// public static func register(with registrar: FlutterPluginRegistrar) {
+// // Create an instance of PenguinViewFactory with the binary messenger from the registrar
+// let factory = PenguinViewFactory(messenger: registrar.messenger())
+//
+// // Register the view factory with a unique ID for use in Flutter code
+// registrar.register(factory, withId: "penguin_native")
+// }
+//}
diff --git a/ios/Runner/Penguin/PenguinView.swift b/ios/Runner/Penguin/PenguinView.swift
new file mode 100644
index 00000000..d5303e2e
--- /dev/null
+++ b/ios/Runner/Penguin/PenguinView.swift
@@ -0,0 +1,462 @@
+//
+
+// BlueGpsView.swift
+
+// Runner
+
+//
+
+// Created by Penguin.
+
+//
+
+
+
+import Foundation
+import UIKit
+import Flutter
+import PenNavUI
+import PenguinINRenderer
+
+import Foundation
+import Flutter
+import UIKit
+
+
+
+/**
+
+ * A custom Flutter platform view for displaying Penguin UI components.
+
+ * This class integrates with the Penguin navigation SDK and handles UI events.
+
+ */
+
+class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitializationDelegate
+
+{
+ // The main view displayed within the platform view
+
+ private var _view: UIView
+
+ private var model: PenguinModel?
+
+ private var methodChannel: FlutterMethodChannel
+
+ var onSuccess: (() -> Void)?
+
+
+
+
+
+
+
+ /**
+
+ * Initializes the PenguinView with the provided parameters.
+
+ *
+
+ * @param frame The frame of the view, specifying its size and position.
+
+ * @param viewId A unique identifier for this view instance.
+
+ * @param args Optional arguments provided for creating the view.
+
+ * @param messenger The [FlutterBinaryMessenger] used for communication with Dart.
+
+ */
+
+ init(
+
+ frame: CGRect,
+
+ viewIdentifier viewId: Int64,
+
+ arguments args: Any?,
+
+ binaryMessenger messenger: FlutterBinaryMessenger?
+
+ ) {
+
+ _view = UIView()
+
+ methodChannel = FlutterMethodChannel(name: "launch_penguin_ui", binaryMessenger: messenger!)
+
+
+
+ super.init()
+
+
+
+ // Get the screen's width and height to set the view's frame
+
+ let screenWidth = UIScreen.main.bounds.width
+
+ let screenHeight = UIScreen.main.bounds.height
+
+
+
+ // Uncomment to set the background color of the view
+
+ // _view.backgroundColor = UIColor.red
+
+
+
+ // Set the frame of the view to cover the entire screen
+
+ _view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
+
+ print("========Inside Penguin View ========")
+
+ print(args)
+
+ guard let arguments = args as? [String: Any] else {
+
+ print("Error: Arguments are not in the expected format.")
+
+ return
+
+ }
+
+ print("===== i got tha Args=======")
+
+
+
+ // Initialize the model from the arguments
+
+ if let penguinModel = PenguinModel(from: arguments) {
+
+ self.model = penguinModel
+
+ initPenguin(args: penguinModel)
+
+ } else {
+
+ print("Error: Failed to initialize PenguinModel from arguments ")
+
+ }
+
+ // Initialize the Penguin SDK with required configurations
+
+ // initPenguin( arguments: args)
+
+ }
+
+
+
+ /**
+
+ * Initializes the Penguin SDK with custom configuration settings.
+
+ */
+
+ func initPenguin(args: PenguinModel) {
+
+// Set the initialization delegate to handle SDK initialization events
+
+ PenNavUIManager.shared.initializationDelegate = self
+
+ // Configure the Penguin SDK with necessary parameters
+
+ PIRendererSettings.styleUri = "mapbox://styles/rwaid/cm3h30b36007v01qz7ik8a0sk"
+
+ PenNavUIManager.shared
+
+ .setClientKey(args.clientKey)
+
+ .setClientID(args.clientID)
+
+ .setUsername(args.username)
+
+ .setSimulationModeEnabled(isEnable: args.isSimulationModeEnabled)
+
+ .setBaseURL(dataURL: args.dataURL, positionURL: args.positionURL)
+
+ .setServiceName(dataServiceName: args.dataServiceName, positionServiceName: args.positionServiceName)
+
+ .setIsShowUserName(args.isShowUserName)
+
+ .setIsUpdateUserLocationSmoothly(args.isUpdateUserLocationSmoothly)
+
+ .setEnableReportIssue(enable: args.isEnableReportIssue)
+
+ .setLanguage(args.languageCode)
+
+ .setBackButtonVisibility(visible: true)
+
+ .setCampusID(args.projectID)
+
+ .build()
+
+ }
+
+
+
+
+
+ /**
+
+ * Returns the main view associated with this platform view.
+
+ *
+
+ * @return The UIView instance that represents this platform view.
+
+ */
+
+ func view() -> UIView {
+
+ return _view
+
+ }
+
+
+
+ // MARK: - PIEventsDelegate Methods
+
+
+
+
+
+
+
+
+
+ /**
+
+ * Called when the Penguin UI is dismissed.
+
+ */
+
+ func onPenNavUIDismiss() {
+
+ // Handle UI dismissal if needed
+
+ print("====== onPenNavUIDismiss =========")
+
+ self.view().removeFromSuperview()
+
+ }
+
+
+
+ /**
+
+ * Called when a report issue is generated.
+
+ *
+
+ * @param issue The type of issue reported.
+
+ */
+
+ func onReportIssue(_ issue: PenNavUI.IssueType) {
+
+ // Handle report issue events if needed
+
+ print("====== onReportIssueError =========")
+
+ methodChannel.invokeMethod("onReportIssue", arguments: ["issueType": issue])
+
+
+
+ }
+
+
+
+ /**
+
+ * Called when the Penguin UI setup is successful.
+
+ */
+
+// func onPenNavInitializationSuccess() {
+// isInitilized = true
+// if let referenceId = referenceId {
+// navigator?.navigateToPOI(referenceId: referenceId){ [self] success, errorMessage in
+//
+// channel?.invokeMethod(PenguinMethod.navigateToPOI.rawValue, arguments: errorMessage)
+//
+// }
+// }
+//
+// channel?.invokeMethod(PenguinMethod.onPenNavSuccess.rawValue, arguments: nil)
+// }
+
+ func onPenNavInitializationSuccess() {
+
+ print("====== onPenNavSuccess =========")
+
+ onSuccess?()
+
+ methodChannel.invokeMethod("onPenNavSuccess", arguments: nil)
+
+ // Obtain the FlutterViewController instance
+
+ let controller: FlutterViewController = UIApplication.shared.windows.first?.rootViewController as! FlutterViewController
+
+
+
+ print("====== after controller onPenNavSuccess =========")
+
+ _view = UIView(frame: UIScreen.main.bounds)
+ _view.backgroundColor = .clear
+
+ controller.view.addSubview(_view)
+
+ // Set the events delegate to handle SDK events
+
+ PenNavUIManager.shared.eventsDelegate = self
+
+
+
+ print("====== after eventsDelegate onPenNavSuccess =========")
+
+
+
+ // Present the Penguin UI on top of the Flutter view controller
+
+ PenNavUIManager.shared.present(root: controller, view: _view)
+
+
+
+
+
+ print("====== after present onPenNavSuccess =========")
+
+ print(model?.clinicID)
+
+ print("====== after present onPenNavSuccess =========")
+
+
+
+ guard let config = self.model else {
+
+ print("Error: Config Model is nil")
+
+ return
+
+ }
+
+
+
+ guard let clinicID = self.model?.clinicID,
+
+ let clientID = self.model?.clientID, !clientID.isEmpty else {
+
+ print("Error: Config Client ID is nil or empty")
+
+ return
+
+ }
+
+
+
+ let navigator = PenguinNavigator(config: config)
+
+
+
+ PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey, showProgress: false) { [weak self] token, error in
+
+ if let error = error {
+
+ let errorMessage = "Token error while getting the for Navigate to method"
+
+ print("Failed to get token: \(errorMessage)")
+
+ return
+
+ }
+
+
+
+ guard let token = token else {
+
+ print("Token is nil")
+
+ return
+
+ }
+
+ print("Token Generated")
+
+ print(token);
+
+
+
+ self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in
+
+ if success {
+
+ print("Navigation successful")
+
+ } else {
+
+ print("Navigation failed: \(errorMessage ?? "Unknown error")")
+
+ }
+
+
+
+ }
+
+
+
+ print("====== after Token onPenNavSuccess =========")
+
+ }
+
+
+
+ }
+
+
+
+
+
+
+
+ private func handleNavigation(clinicID: String, token: String, completion: @escaping (Bool, String?) -> Void) {
+
+ DispatchQueue.main.async {
+
+ PenNavUIManager.shared.setToken(token: token)
+
+ PenNavUIManager.shared.navigate(to: clinicID)
+
+ completion(true,nil)
+
+ }
+
+ }
+
+
+
+
+
+
+
+
+
+ /**
+
+ * Called when there is an initialization error with the Penguin UI.
+
+ *
+
+ * @param errorType The type of initialization error.
+
+ * @param errorDescription A description of the error.
+
+ */
+
+ func onPenNavInitializationError(errorType: PenNavUI.PenNavUIError, errorDescription: String) {
+
+ // Handle initialization errors if needed
+
+ print("onPenNavInitializationErrorType: \(errorType.rawValue)")
+
+ print("onPenNavInitializationError: \(errorDescription)")
+ }
+}
diff --git a/ios/Runner/Penguin/PenguinViewFactory.swift b/ios/Runner/Penguin/PenguinViewFactory.swift
new file mode 100644
index 00000000..a88bb5d0
--- /dev/null
+++ b/ios/Runner/Penguin/PenguinViewFactory.swift
@@ -0,0 +1,59 @@
+//
+// BlueGpsViewFactory.swift
+// Runner
+//
+// Created by Penguin .
+//
+
+import Foundation
+import Flutter
+
+/**
+ * A factory class for creating instances of [PenguinView].
+ * This class implements `FlutterPlatformViewFactory` to create and manage native views.
+ */
+class PenguinViewFactory: NSObject, FlutterPlatformViewFactory {
+
+ // The binary messenger used for communication with the Flutter engine
+ private var messenger: FlutterBinaryMessenger
+
+ /**
+ * Initializes the PenguinViewFactory with the given messenger.
+ *
+ * @param messenger The [FlutterBinaryMessenger] used to communicate with Dart code.
+ */
+ init(messenger: FlutterBinaryMessenger) {
+ self.messenger = messenger
+ super.init()
+ }
+
+ /**
+ * Creates a new instance of [PenguinView].
+ *
+ * @param frame The frame of the view, specifying its size and position.
+ * @param viewId A unique identifier for this view instance.
+ * @param args Optional arguments provided for creating the view.
+ * @return An instance of [PenguinView] configured with the provided parameters.
+ */
+ func create(
+ withFrame frame: CGRect,
+ viewIdentifier viewId: Int64,
+ arguments args: Any?
+ ) -> FlutterPlatformView {
+ return PenguinView(
+ frame: frame,
+ viewIdentifier: viewId,
+ arguments: args,
+ binaryMessenger: messenger)
+ }
+
+ /**
+ * Returns the codec used for encoding and decoding method channel arguments.
+ * This method is required when `arguments` in `create` is not `nil`.
+ *
+ * @return A [FlutterMessageCodec] instance used for serialization.
+ */
+ public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
+ return FlutterStandardMessageCodec.sharedInstance()
+ }
+}
diff --git a/ios/Runner/RunnerDebug.entitlements b/ios/Runner/RunnerDebug.entitlements
new file mode 100644
index 00000000..319178a4
--- /dev/null
+++ b/ios/Runner/RunnerDebug.entitlements
@@ -0,0 +1,17 @@
+
+
+
+
+ aps-environment
+ development
+ com.apple.developer.in-app-payments
+
+ merchant.com.hmgwebservices
+ merchant.com.hmgwebservices.uat
+
+ com.apple.developer.nfc.readersession.formats
+
+ TAG
+
+
+
diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart
index 888f7040..f3663294 100644
--- a/lib/core/api/api_client.dart
+++ b/lib/core/api/api_client.dart
@@ -19,7 +19,7 @@ abstract class ApiClient {
Future post(
String endPoint, {
- required Map body,
+ required dynamic body,
required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess,
required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure,
bool isAllowAny,
@@ -27,6 +27,8 @@ abstract class ApiClient {
bool isRCService,
bool isPaymentServices,
bool bypassConnectionCheck,
+ Map apiHeaders,
+ bool isBodyPlainText,
});
Future get(
@@ -89,7 +91,7 @@ class ApiClientImp implements ApiClient {
@override
post(
String endPoint, {
- required Map body,
+ required dynamic body,
required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess,
required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure,
bool isAllowAny = false,
@@ -97,6 +99,8 @@ class ApiClientImp implements ApiClient {
bool isRCService = false,
bool isPaymentServices = false,
bool bypassConnectionCheck = true,
+ Map? apiHeaders,
+ bool isBodyPlainText = false,
}) async {
String url;
if (isExternal) {
@@ -110,80 +114,84 @@ class ApiClientImp implements ApiClient {
}
// try {
var user = _appState.getAuthenticatedUser();
- Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'};
- if (!isExternal) {
- String? token = _appState.appAuthToken;
+ Map headers = apiHeaders ?? {'Content-Type': 'application/json', 'Accept': 'application/json'};
- if (body.containsKey('SetupID')) {
- body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID;
- } else {}
+ // When isBodyPlainText is true, skip all body manipulation and use body as-is
+ if (!isBodyPlainText) {
+ if (!isExternal) {
+ String? token = _appState.appAuthToken;
- if (body.containsKey('isDentalAllowedBackend')) {
- body['isDentalAllowedBackend'] =
- body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND;
- }
+ if (body.containsKey('SetupID')) {
+ body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID;
+ } else {}
- if (!body.containsKey('IsPublicRequest')) {
- // if (!body.containsKey('PatientType')) {
- if (user != null && user.patientType != null) {
- body['PatientType'] = user.patientType;
- } else {
- body['PatientType'] = PATIENT_TYPE.toString();
+ if (body.containsKey('isDentalAllowedBackend')) {
+ body['isDentalAllowedBackend'] =
+ body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND;
}
- if (user != null && user.patientType != null) {
- body['PatientTypeID'] = user.patientType;
- } else {
- body['PatientType'] = PATIENT_TYPE_ID.toString();
- }
+ if (!body.containsKey('IsPublicRequest')) {
+ // if (!body.containsKey('PatientType')) {
+ if (user != null && user.patientType != null) {
+ body['PatientType'] = user.patientType;
+ } else {
+ body['PatientType'] = PATIENT_TYPE.toString();
+ }
+
+ if (user != null && user.patientType != null) {
+ body['PatientTypeID'] = user.patientType;
+ } else {
+ body['PatientType'] = PATIENT_TYPE_ID.toString();
+ }
- if (user != null) {
- body['TokenID'] = body['TokenID'] ?? token;
+ if (user != null) {
+ body['TokenID'] = body['TokenID'] ?? token;
- body['PatientID'] = body['PatientID'] ?? user.patientId;
+ body['PatientID'] = body['PatientID'] ?? user.patientId;
- body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa;
- body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe
+ body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa;
+ body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe
+ }
+ // else {
+ // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe
+ //
+ // }
}
- // else {
- // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe
- //
- // }
}
- }
- // request.versionID = VERSION_ID;
- // request.channel = CHANNEL;
- // request.iPAdress = IP_ADDRESS;
- // request.generalid = GENERAL_ID;
- // request.languageID = (languageID == 'ar' ? 1 : 2);
- // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1;
-
- // body['VersionID'] = ApiConsts.appVersionID.toString();
- if (!isExternal) {
- body['VersionID'] = ApiConsts.appVersionID.toString();
- body['Channel'] = ApiConsts.appChannelId.toString();
- body['IPAdress'] = ApiConsts.appIpAddress;
- body['generalid'] = ApiConsts.appGeneralId;
-
- body['LanguageID'] = _appState.getLanguageID().toString();
- body['Latitude'] = _appState.userLat.toString();
- body['Longitude'] = _appState.userLong.toString();
- body['DeviceTypeID'] = _appState.deviceTypeID;
- if (_appState.appAuthToken.isNotEmpty) {
- body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken;
+ // request.versionID = VERSION_ID;
+ // request.channel = CHANNEL;
+ // request.iPAdress = IP_ADDRESS;
+ // request.generalid = GENERAL_ID;
+ // request.languageID = (languageID == 'ar' ? 1 : 2);
+ // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1;
+
+ // body['VersionID'] = ApiConsts.appVersionID.toString();
+ if (!isExternal) {
+ body['VersionID'] = ApiConsts.appVersionID.toString();
+ body['Channel'] = ApiConsts.appChannelId.toString();
+ body['IPAdress'] = ApiConsts.appIpAddress;
+ body['generalid'] = ApiConsts.appGeneralId;
+
+ body['LanguageID'] = _appState.getLanguageID().toString();
+ body['Latitude'] = _appState.userLat.toString();
+ body['Longitude'] = _appState.userLong.toString();
+ body['DeviceTypeID'] = _appState.deviceTypeID;
+ if (_appState.appAuthToken.isNotEmpty) {
+ body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken;
+ }
+
+ // body['TokenID'] = "@dm!n";
+ // body['PatientID'] = 1018977;
+ // body['PatientTypeID'] = 1;
+ //
+ // body['PatientOutSA'] = 0;
+ // body['SessionID'] = "45786230487560q";
}
- // body['TokenID'] = "@dm!n";
- // body['PatientID'] = 1018977;
- // body['PatientTypeID'] = 1;
- //
- // body['PatientOutSA'] = 0;
- // body['SessionID'] = "45786230487560q";
+ body.removeWhere((key, value) => value == null);
}
- body.removeWhere((key, value) => value == null);
-
final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck);
if (!networkStatus) {
@@ -196,12 +204,13 @@ class ApiClientImp implements ApiClient {
return;
}
- final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers);
+ // Handle body encoding based on isBodyPlainText flag
+ final dynamic requestBody = isBodyPlainText ? body : json.encode(body);
+ final response = await http.post(Uri.parse(url.trim()), body: requestBody, headers: headers);
final int statusCode = response.statusCode;
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
// log("response.body: ${response.body}");
- // log("response.body: ${response.body}");
if (statusCode < 200 || statusCode >= 400) {
onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data"));
logApiEndpointError(endPoint, 'Error While Fetching data', statusCode);
diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart
index b0fb2a83..f0a8a726 100644
--- a/lib/core/api_consts.dart
+++ b/lib/core/api_consts.dart
@@ -334,6 +334,8 @@ var GET_PATIENT_SHARE_FOR_WALKIN_APPOINTMENT = 'Services/Doctors.svc/REST/GetChe
var CAN_PAY_FOR_FOR_WALKIN_APPOINTMENT = 'Services/Doctors.svc/REST/CanPayForWalkinAppointment';
+var GET_APPOINTMENT_NEAREST_GATE = 'Services/OUTPs.svc/REST/getGateByProjectIDandClinicID';
+
//URL to get medicine and pharmacies list
var CHANNEL = 3;
var GENERAL_ID = 'Cs2020@2016\$2958';
@@ -437,14 +439,6 @@ var RATE_DOCTOR_RESPONSE = 'Services/OUTPs.svc/REST/insertAppointmentQuestionRat
var GET_PATIENT_ALLERGIES = 'Services/Patients.svc/REST/GetPatientAllergies';
-// H2O
-var H2O_GET_USER_PROGRESS = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
-var H2O_INSERT_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";
-var H2O_GET_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New";
-var H2O_UPDATE_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New";
-var H2O_UNDO_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
-//E_Referral Services
-
// Encillary Orders
var GET_ANCILLARY_ORDERS = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList';
@@ -670,25 +664,6 @@ var addPayFortApplePayResponse = "Services/PayFort_Serv.svc/REST/AddResponse";
// Auth Provider Consts
-const String INSERT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI';
-const String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI';
-const String CHECK_PATIENT_AUTH = 'Services/Authentication.svc/REST/CheckPatientAuthentication';
-const GET_MOBILE_INFO = 'Services/Authentication.svc/REST/GetMobileLoginInfo';
-
-const FORGOT_PASSWORD = 'Services/Authentication.svc/REST/CheckActivationCodeForSendFileNo';
-const CHECK_PATIENT_FOR_REGISTRATION = "Services/Authentication.svc/REST/CheckPatientForRegisteration";
-
-const CHECK_USER_STATUS = "Services/NHIC.svc/REST/GetPatientInfo";
-const REGISTER_USER = 'Services/Authentication.svc/REST/PatientRegistration';
-const LOGGED_IN_USER_URL = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo';
-
-const FORGOT_PATIENT_ID = 'Services/Authentication.svc/REST/SendPatientIDSMSByMobileNumber';
-const DASHBOARD = 'Services/Patients.svc/REST/PatientDashboard';
-const PROFILE_SETTING = 'Services/Patients.svc/REST/GetPateintInfoForUpdate';
-const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo';
-
-const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_InsertUpdate';
-
var ER_CREATE_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_CreateAdvancePaymentForClinic";
//family Files
@@ -858,12 +833,11 @@ class ApiConsts {
// SYMPTOMS CHECKER
static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName';
static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors';
- static final String getGeneralSuggestion = '$symptomsCheckerApi/GetGeneralSggestion';
+ static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion';
static final String diagnosis = '$symptomsCheckerApi/diagnosis';
static final String explain = '$symptomsCheckerApi/explain';
//E-REFERRAL SERVICES
-
static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes";
static final sendActivationCodeForEReferral = 'Services/Authentication.svc/REST/SendActivationCodeForEReferral';
static final checkActivationCodeForEReferral = 'Services/Authentication.svc/REST/CheckActivationCodeForEReferral';
@@ -871,6 +845,14 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
+ //WATER CONSUMPTION
+ static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
+ static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";
+ static String h2oInsertUserDetailsNew = "Services/H2ORemainder.svc/REST/H2O_InsertUserDetails_New";
+ static String h2oGetUserDetail = "Services/H2ORemainder.svc/REST/H2O_GetUserDetails_New";
+ static String h2oUpdateUserDetail = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetails_New";
+ static String h2oUndoUserActivity = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
+
// ************ static values for Api ****************
static final double appVersionID = 50.3;
static final int appChannelId = 3;
@@ -882,3 +864,34 @@ class ApiConsts {
class ApiKeyConstants {
static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng';
}
+
+//flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_InsertUserActivity
+// flutter: {"IdentificationNo":"2530976584","MobileNumber":"504278212","QuantityIntake":200,"VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":true,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":200.00,"PercentageConsumed":9.41,"PercentageLeft":90.59,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[{"Quantity":200.000,"CreatedDate":"\/Date(1766911222217+0300)\/"}],"VerificationCode":null,"isSMSSent":false}
+
+// URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2o_UndoUserActivity
+// flutter: {"Progress":1,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":0.00,"PercentageConsumed":0.00,"PercentageLeft":100.00,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
+
+// Progress":2 means weekly data
+
+// flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
+// flutter: {"Progress":2,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// [log] {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":null,"UserProgressForWeekData":[{"DayNumber":1,"DayDate":null,"DayName":"Sunday","PercentageConsumed":0},{"DayNumber":7,"DayDate":null,"DayName":"Saturday","PercentageConsumed":0},{"DayNumber":6,"DayDate":null,"DayName":"Friday","PercentageConsumed":0},{"DayNumber":5,"DayDate":null,"DayName":"Thursday","PercentageConsumed":0},{"DayNumber":4,"DayDate":null,"DayName":"Wednesday","PercentageConsumed":0},{"DayNumber":3,"DayDate":null,"DayName":"Tuesday","PercentageConsumed":0},{"DayNumber":2,"DayDate":null,"DayName":"Monday","PercentageConsumed":0}],"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
+
+// Progress":1 means daily data
+
+//URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
+// flutter: {"Progress":1,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":0.00,"PercentageConsumed":0.00,"PercentageLeft":100.00,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
+
+// Progress":1 means monthly data
+
+// flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
+// flutter: {"Progress":3,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
+// flutter: response.body:
+// [log] {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":[{"MonthNumber":1,"MonthName":"January","PercentageConsumed":0},{"MonthNumber":2,"MonthName":"February","PercentageConsumed":0},{"MonthNumber":3,"MonthName":"March","PercentageConsumed":0},{"MonthNumber":4,"MonthName":"April","PercentageConsumed":0},{"MonthNumber":5,"MonthName":"May","PercentageConsumed":0},{"MonthNumber":6,"MonthName":"June","PercentageConsumed":0},{"MonthNumber":7,"MonthName":"July","PercentageConsumed":0},{"MonthNumber":8,"MonthName":"August","PercentageConsumed":0},{"MonthNumber":9,"MonthName":"September","PercentageConsumed":0},{"MonthNumber":10,"MonthName":"October","PercentageConsumed":0},{"MonthNumber":11,"MonthName":"November","PercentageConsumed":0},{"MonthNumber":12,"MonthName":"December","PercentageConsumed":0}],"UserProgressForTodayData":null,"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart
index 3c11b096..1a630906 100644
--- a/lib/core/app_assets.dart
+++ b/lib/core/app_assets.dart
@@ -246,7 +246,29 @@ class AppAssets {
static const String rotateIcon = '$svgBasePath/rotate_icon.svg';
static const String refreshIcon = '$svgBasePath/refresh.svg';
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
+ static const String symptomCheckerIcon = '$svgBasePath/symptom_checker_icon.svg';
+ static const String symptomCheckerBottomIcon = '$svgBasePath/symptom_bottom_icon.svg';
+ // Water Monitor
+ static const String waterBottle = '$svgBasePath/water_bottle.svg';
+ static const String cupAdd = '$svgBasePath/cup_add.svg';
+ static const String cupFilled = '$svgBasePath/cup_filled.svg';
+ static const String waterBottleOuterBubbles = '$svgBasePath/outer_bubbles.svg';
+ static const String cupEmpty = '$svgBasePath/cup_empty.svg';
+ static const String dumbellIcon = '$svgBasePath/dumbell_icon.svg';
+ static const String weightScaleIcon = '$svgBasePath/weight_scale_icon.svg';
+ static const String heightIcon = '$svgBasePath/height_icon.svg';
+ static const String profileIcon = '$svgBasePath/profile_icon.svg';
+ static const String notificationIconGrey = '$svgBasePath/notification_icon_grey.svg';
+ static const String minimizeIcon = '$svgBasePath/minimize_icon.svg';
+ static const String addIconDark = '$svgBasePath/add_icon_dark.svg';
+ static const String glassIcon = '$svgBasePath/glass_icon.svg';
+ static const String graphIcon = '$svgBasePath/graph_icon.svg';
+ static const String listIcon = '$svgBasePath/list_icon.svg';
+ static const String yellowArrowDownIcon = '$svgBasePath/yellow_arrow_down_icon.svg';
+ static const String greenTickIcon = '$svgBasePath/green_tick_icon.svg';
+
+ // PNGS
static const String bloodSugar = '$svgBasePath/bloodsugar.svg';
@@ -264,6 +286,19 @@ class AppAssets {
static const String covid19icon = '$svgBasePath/covid_19.svg';
+
+ //vital sign
+
+ static const String heartRate = '$svgBasePath/heart_rate.svg';
+ static const String respRate = '$svgBasePath/resp_rate.svg';
+ static const String weightVital = '$svgBasePath/weight_2.svg';
+ static const String bmiVital = '$svgBasePath/bmi_2.svg';
+ static const String heightVital = '$svgBasePath/height_2.svg';
+ static const String bloodPressure = '$svgBasePath/blood_pressure.svg';
+ static const String temperature = '$svgBasePath/temperature.svg';
+
+
+
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';
static const String liveCareService = '$pngBasePath/livecare_service.png';
@@ -289,7 +324,7 @@ class AppAssets {
static const String fullBodyFront = '$pngBasePath/full_body_front.png';
static const String fullBodyBack = '$pngBasePath/full_body_back.png';
-
+ static const String bmiFullBody = '$pngBasePath/bmi_image_1.png';
}
diff --git a/lib/core/cache_consts.dart b/lib/core/cache_consts.dart
index bcbb1853..c1e06aa3 100644
--- a/lib/core/cache_consts.dart
+++ b/lib/core/cache_consts.dart
@@ -63,6 +63,7 @@ class CacheConst {
static const String pharmacyAutorzieToken = 'PHARMACY_AUTORZIE_TOKEN';
static const String h2oUnit = 'H2O_UNIT';
static const String h2oReminder = 'H2O_REMINDER';
+ static const String waterReminderEnabled = 'WATER_REMINDER_ENABLED';
static const String livecareClinicData = 'LIVECARE_CLINIC_DATA';
static const String doctorScheduleDateSel = 'DOCTOR_SCHEDULE_DATE_SEL';
static const String appointmentHistoryMedical = 'APPOINTMENT_HISTORY_MEDICAL';
@@ -74,6 +75,7 @@ class CacheConst {
static const String patientOccupationList = 'patient-occupation-list';
static const String hasEnabledQuickLogin = 'has-enabled-quick-login';
static const String quickLoginEnabled = 'quick-login-enabled';
+ static const String isMonthlyReportEnabled = 'is-monthly-report-enabled';
static const String zoomRoomID = 'zoom-room-id';
static String isAppOpenedFromCall = "is_app_opened_from_call";
diff --git a/lib/core/common_models/data_points.dart b/lib/core/common_models/data_points.dart
index 3f5065cb..f156ecb9 100644
--- a/lib/core/common_models/data_points.dart
+++ b/lib/core/common_models/data_points.dart
@@ -1,26 +1,26 @@
-
-
///class used to provide value for the [DynamicResultChart] to plot the values
class DataPoint {
///values that is displayed on the graph and dot is plotted on this
final double value;
+
///label shown on the bottom of the graph
String label;
String referenceValue;
String actualValue;
- String? unitOfMeasurement ;
+ String? unitOfMeasurement;
+
DateTime time;
String displayTime;
- DataPoint(
- {required this.value,
- required this.label,
- required this.referenceValue,
- required this.actualValue,
- required this.time,
- required this.displayTime,
- this.unitOfMeasurement
- });
+ DataPoint({
+ required this.value,
+ required this.label,
+ required this.actualValue,
+ required this.time,
+ required this.displayTime,
+ this.unitOfMeasurement,
+ this.referenceValue = '',
+ });
@override
String toString() {
diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart
index 872fe69b..93100aa4 100644
--- a/lib/core/dependencies.dart
+++ b/lib/core/dependencies.dart
@@ -1,4 +1,5 @@
import 'package:firebase_messaging/firebase_messaging.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@@ -31,11 +32,14 @@ import 'package:hmg_patient_app_new/features/location/location_repo.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
-import 'package:hmg_patient_app_new/features/monthly_reports/monthly_reports_repo.dart';
+import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
+import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart';
@@ -49,7 +53,8 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_r
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
-// import 'package:hmg_patient_app_new/presentation/health_calculators/health_calculator_view_model.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart';
+import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/presentation/monthly_reports/monthly_reports_page.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
@@ -59,6 +64,7 @@ import 'package:hmg_patient_app_new/services/firebase_service.dart';
import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
+import 'package:hmg_patient_app_new/services/notification_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
import 'package:local_auth/local_auth.dart';
import 'package:logger/web.dart';
@@ -112,6 +118,13 @@ class AppDependencies {
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
+
+ final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
+ getIt.registerLazySingleton(() => NotificationServiceImp(
+ flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin,
+ loggerService: getIt(),
+ ));
+
getIt.registerLazySingleton(() => ApiClientImp(appState: getIt()));
getIt.registerLazySingleton(
() => LocalAuthService(loggerService: getIt(), localAuth: getIt()),
@@ -137,6 +150,9 @@ class AppDependencies {
getIt.registerLazySingleton(() => HmgServicesRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt()));
+ getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt()));
+ getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt()));
+ getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt()));
getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(),
@@ -151,7 +167,6 @@ class AppDependencies {
),
);
-
// ViewModels
// Global/shared VMs → LazySingleton
@@ -161,25 +176,25 @@ class AppDependencies {
() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
);
- getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
+ getIt.registerLazySingleton(
+ () => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt()));
- getIt.registerLazySingleton(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
+ getIt.registerLazySingleton(
+ () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
- getIt.registerLazySingleton(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
+ getIt.registerLazySingleton(
+ () => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton(
- () => PayfortViewModel(
- payfortRepo: getIt(),
- errorHandlerService: getIt(),
- ),
+ () => PayfortViewModel(payfortRepo: getIt(), errorHandlerService: getIt()),
);
getIt.registerLazySingleton(
() => HabibWalletViewModel(
habibWalletRepo: getIt(),
- errorHandlerService: getIt(),
+ errorHandlerService: getIt()
),
);
@@ -192,7 +207,12 @@ class AppDependencies {
getIt.registerLazySingleton(
() => BookAppointmentsViewModel(
- bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()),
+ bookAppointmentsRepo: getIt(),
+ errorHandlerService: getIt(),
+ navigationService: getIt(),
+ myAppointmentsViewModel: getIt(),
+ locationUtils: getIt(),
+ dialogService: getIt()),
);
getIt.registerLazySingleton(
@@ -206,8 +226,15 @@ class AppDependencies {
getIt.registerLazySingleton(
() => AuthenticationViewModel(
- authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()),
+ authenticationRepo: getIt(),
+ cacheService: getIt(),
+ navigationService: getIt(),
+ dialogService: getIt(),
+ appState: getIt(),
+ errorHandlerService: getIt(),
+ localAuthService: getIt()),
);
+
getIt.registerLazySingleton(() => ProfileSettingsViewModel());
getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel());
@@ -220,7 +247,14 @@ class AppDependencies {
getIt.registerLazySingleton(
() => EmergencyServicesViewModel(
- locationUtils: getIt(), navServices: getIt(), emergencyServicesRepo: getIt(), appState: getIt(), errorHandlerService: getIt(), appointmentRepo: getIt(), dialogService: getIt()),
+ locationUtils: getIt(),
+ navServices: getIt(),
+ emergencyServicesRepo: getIt(),
+ appState: getIt(),
+ errorHandlerService: getIt(),
+ appointmentRepo: getIt(),
+ dialogService: getIt(),
+ ),
);
getIt.registerLazySingleton(
@@ -233,45 +267,55 @@ class AppDependencies {
getIt.registerLazySingleton(() => HealthCalcualtorViewModel());
- getIt.registerLazySingleton(
- () => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()),
+ getIt.registerLazySingleton(() => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()));
+
+ getIt.registerLazySingleton(
+ () => SymptomsCheckerViewModel(
+ errorHandlerService: getIt(),
+ symptomsCheckerRepo: getIt(),
+ appState: getIt(),
+ ),
);
- getIt.registerLazySingleton(() => SymptomsCheckerViewModel(errorHandlerService: getIt(), symptomsCheckerRepo: getIt()));
getIt.registerLazySingleton(
- () => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
+ () => HmgServicesViewModel(
+ bookAppointmentsRepo: getIt(),
+ hmgServicesRepo: getIt(),
+ errorHandlerService: getIt(),
+ navigationService: getIt(),
+ ),
);
getIt.registerLazySingleton(
- () => BloodDonationViewModel(bloodDonationRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt()),
+ () => BloodDonationViewModel(
+ bloodDonationRepo: getIt(),
+ errorHandlerService: getIt(),
+ navigationService: getIt(),
+ dialogService: getIt(),
+ appState: getIt(),
+ ),
);
- getIt.registerLazySingleton(
- () => HealthProvider(),
- );
+ getIt.registerLazySingleton(() => HealthProvider());
+
+ getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt()));
+
+ getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
+
+ getIt.registerLazySingleton(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt()));
getIt.registerLazySingleton(
() => ActivePrescriptionsViewModel(
- errorHandlerService: getIt(),
- activePrescriptionsRepo: getIt()
+ errorHandlerService: getIt(),
+ activePrescriptionsRepo: getIt()
),
);
getIt.registerFactory(
() => QrParkingViewModel(
qrParkingRepo: getIt(),
errorHandlerService: getIt(),
- cacheService: getIt(),
+ cacheService: getIt(),
),
);
-
-
- // Screen-specific VMs → Factory
- // getIt.registerFactory(
- // () => BookAppointmentsViewModel(
- // bookAppointmentsRepo: getIt(),
- // dialogService: getIt(),
- // errorHandlerService: getIt(),
- // ),
- // );
}
}
diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart
index 487b2282..9dcdbb5d 100644
--- a/lib/core/location_util.dart
+++ b/lib/core/location_util.dart
@@ -12,8 +12,9 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
-import 'package:huawei_location/huawei_location.dart' as HmsLocation show FusedLocationProviderClient, Location, LocationSettingsRequest, LocationRequest;
-import 'package:location/location.dart' show Location, PermissionStatus, LocationData;
+import 'package:huawei_location/huawei_location.dart' as HmsLocation
+ show FusedLocationProviderClient, Location, LocationSettingsRequest, LocationRequest;
+import 'package:location/location.dart' show Location;
import 'package:permission_handler/permission_handler.dart' show Permission, PermissionListActions, PermissionStatusGetters, openAppSettings;
class LocationUtils {
@@ -59,37 +60,22 @@ class LocationUtils {
// }
void getLocation(
- {Function(LatLng)? onSuccess,
- VoidCallback? onFailure,
- bool isShowConfirmDialog = false,
- VoidCallback? onLocationDeniedForever}) async {
+ {Function(LatLng)? onSuccess, VoidCallback? onFailure, bool isShowConfirmDialog = false, VoidCallback? onLocationDeniedForever}) async {
this.isShowConfirmDialog = isShowConfirmDialog;
if (Platform.isIOS) {
- getCurrentLocation(
- onFailure: onFailure,
- onSuccess: onSuccess,
- onLocationDeniedForever: onLocationDeniedForever);
+ getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess, onLocationDeniedForever: onLocationDeniedForever);
return;
}
if (await isGMSDevice ?? true) {
- getCurrentLocation(
- onFailure: onFailure,
- onSuccess: onSuccess,
- onLocationDeniedForever: onLocationDeniedForever);
+ getCurrentLocation(onFailure: onFailure, onSuccess: onSuccess, onLocationDeniedForever: onLocationDeniedForever);
return;
}
- getHMSLocation(
- onFailure: onFailure,
- onSuccess: onSuccess,
- onLocationDeniedForever: onLocationDeniedForever);
+ getHMSLocation(onFailure: onFailure, onSuccess: onSuccess, onLocationDeniedForever: onLocationDeniedForever);
}
- void getCurrentLocation(
- {Function(LatLng)? onSuccess,
- VoidCallback? onFailure,
- VoidCallback? onLocationDeniedForever}) async {
+ void getCurrentLocation({Function(LatLng)? onSuccess, VoidCallback? onFailure, VoidCallback? onLocationDeniedForever}) async {
var location = Location();
bool isLocationEnabled = await location.serviceEnabled();
@@ -113,14 +99,12 @@ class LocationUtils {
}
} else if (permissionGranted == LocationPermission.deniedForever) {
appState.resetLocation();
- if(onLocationDeniedForever == null && isShowConfirmDialog){
+ if (onLocationDeniedForever == null && isShowConfirmDialog) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
- loadingText:
- "Please grant location permission from app settings to see better results"
- .needTranslation,
+ loadingText: "Please grant location permission from app settings to see better results".needTranslation,
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
@@ -253,10 +237,7 @@ class LocationUtils {
appState.userLong = locationData.longitude;
}
- void getHMSLocation(
- {VoidCallback? onFailure,
- Function(LatLng p1)? onSuccess,
- VoidCallback? onLocationDeniedForever}) async {
+ void getHMSLocation({VoidCallback? onFailure, Function(LatLng p1)? onSuccess, VoidCallback? onLocationDeniedForever}) async {
try {
var location = Location();
HmsLocation.FusedLocationProviderClient locationService = HmsLocation.FusedLocationProviderClient()..initFusedLocationService();
@@ -279,14 +260,12 @@ class LocationUtils {
permissionGranted = await Geolocator.requestPermission();
if (permissionGranted == LocationPermission.deniedForever) {
appState.resetLocation();
- if(onLocationDeniedForever == null && isShowConfirmDialog){
+ if (onLocationDeniedForever == null && isShowConfirmDialog) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!),
navigationService.navigatorKey.currentContext!,
child: Utils.getWarningWidget(
- loadingText:
- "Please grant location permission from app settings to see better results"
- .needTranslation,
+ loadingText: "Please grant location permission from app settings to see better results".needTranslation,
isShowActionButtons: true,
onCancelTap: () {
navigationService.pop();
@@ -311,7 +290,7 @@ class LocationUtils {
HmsLocation.Location data = await locationService.getLastLocation();
if (data.latitude == null || data.longitude == null) {
- appState.resetLocation();
+ appState.resetLocation();
HmsLocation.LocationRequest request = HmsLocation.LocationRequest()
..priority = HmsLocation.LocationRequest.PRIORITY_HIGH_ACCURACY
..interval = 1000 // 1 second
diff --git a/lib/core/post_params_model.dart b/lib/core/post_params_model.dart
index cf52306e..e13eb5cd 100644
--- a/lib/core/post_params_model.dart
+++ b/lib/core/post_params_model.dart
@@ -14,19 +14,20 @@ class PostParamsModel {
String? sessionID;
String? setupID;
- PostParamsModel(
- {this.versionID,
- this.channel,
- this.languageID,
- this.logInTokenID,
- this.tokenID,
- this.language,
- this.ipAddress,
- this.generalId,
- this.latitude,
- this.longitude,
- this.deviceTypeID,
- this.sessionID});
+ PostParamsModel({
+ this.versionID,
+ this.channel,
+ this.languageID,
+ this.logInTokenID,
+ this.tokenID,
+ this.language,
+ this.ipAddress,
+ this.generalId,
+ this.latitude,
+ this.longitude,
+ this.deviceTypeID,
+ this.sessionID,
+ });
PostParamsModel.fromJson(Map json) {
versionID = json['VersionID'];
diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart
index a42a44d0..746d2a7a 100644
--- a/lib/core/utils/date_util.dart
+++ b/lib/core/utils/date_util.dart
@@ -6,8 +6,6 @@ class DateUtil {
/// convert String To Date function
/// [date] String we want to convert
static DateTime convertStringToDate(String? date) {
-
-
if (date == null) return DateTime.now();
if (date.isEmpty) return DateTime.now();
@@ -522,6 +520,64 @@ class DateUtil {
}
return "";
}
+
+ /// Get short month name from full month name
+ /// [monthName] Full month name like "January"
+ /// Returns short form like "Jan"
+ static String getShortMonthName(String monthName) {
+ switch (monthName.toLowerCase()) {
+ case 'january':
+ return 'Jan';
+ case 'february':
+ return 'Feb';
+ case 'march':
+ return 'Mar';
+ case 'april':
+ return 'Apr';
+ case 'may':
+ return 'May';
+ case 'june':
+ return 'Jun';
+ case 'july':
+ return 'Jul';
+ case 'august':
+ return 'Aug';
+ case 'september':
+ return 'Sep';
+ case 'october':
+ return 'Oct';
+ case 'november':
+ return 'Nov';
+ case 'december':
+ return 'Dec';
+ default:
+ return monthName; // Return as-is if not recognized
+ }
+ }
+
+ /// Get short weekday name from full weekday name
+ /// [weekDayName] Full weekday name like "Monday"
+ /// Returns short form like "Mon"
+ static String getShortWeekDayName(String weekDayName) {
+ switch (weekDayName.toLowerCase().trim()) {
+ case 'monday':
+ return 'Mon';
+ case 'tuesday':
+ return 'Tue';
+ case 'wednesday':
+ return 'Wed';
+ case 'thursday':
+ return 'Thu';
+ case 'friday':
+ return 'Fri';
+ case 'saturday':
+ return 'Sat';
+ case 'sunday':
+ return 'Sun';
+ default:
+ return weekDayName; // Return as-is if not recognized
+ }
+ }
}
extension OnlyDate on DateTime {
diff --git a/lib/core/utils/local_notifications.dart b/lib/core/utils/local_notifications.dart
deleted file mode 100644
index aba01f85..00000000
--- a/lib/core/utils/local_notifications.dart
+++ /dev/null
@@ -1,191 +0,0 @@
-import 'dart:math';
-import 'dart:typed_data';
-
-import 'package:flutter_local_notifications/flutter_local_notifications.dart';
-
-final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
-
-class LocalNotification {
- Function(String payload)? _onNotificationClick;
- static LocalNotification? _instance;
-
- static LocalNotification? getInstance() {
- return _instance;
- }
-
- static init({required Function(String payload) onNotificationClick}) {
- if (_instance == null) {
- _instance = LocalNotification();
- _instance?._onNotificationClick = onNotificationClick;
- _instance?._initialize();
- } else {
- // assert(false,(){
- // //TODO fix it
- // "LocalNotification Already Initialized";
- // });
- }
- }
-
- _initialize() async {
- try {
- var initializationSettingsAndroid = new AndroidInitializationSettings('app_icon');
- var initializationSettingsIOS = DarwinInitializationSettings();
- var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
- await flutterLocalNotificationsPlugin.initialize(
- initializationSettings,
- onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) {
- switch (notificationResponse.notificationResponseType) {
- case NotificationResponseType.selectedNotification:
- // selectNotificationStream.add(notificationResponse.payload);
- break;
- case NotificationResponseType.selectedNotificationAction:
- // if (notificationResponse.actionId == navigationActionId) {
- // selectNotificationStream.add(notificationResponse.payload);
- // }
- break;
- }
- },
- // onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
- );
- } catch (ex) {
- print(ex.toString());
- }
- // flutterLocalNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse notificationResponse)
- // {
- // switch (notificationResponse.notificationResponseType) {
- // case NotificationResponseType.selectedNotification:
- // // selectNotificationStream.add(notificationResponse.payload);
- // break;
- // case NotificationResponseType.selectedNotificationAction:
- // // if (notificationResponse.actionId == navigationActionId) {
- // // selectNotificationStream.add(notificationResponse.payload);
- // }
- // // break;
- // },}
- //
- // ,
- //
- // );
- }
-
- // void notificationTapBackground(NotificationResponse notificationResponse) {
- // // ignore: avoid_print
- // print('notification(${notificationResponse.id}) action tapped: '
- // '${notificationResponse.actionId} with'
- // ' payload: ${notificationResponse.payload}');
- // if (notificationResponse.input?.isNotEmpty ?? false) {
- // // ignore: avoid_print
- // print('notification action tapped with input: ${notificationResponse.input}');
- // }
- // }
-
- var _random = new Random();
-
- _randomNumber({int from = 100000}) {
- return _random.nextInt(from);
- }
-
- _vibrationPattern() {
- var vibrationPattern = Int64List(4);
- vibrationPattern[0] = 0;
- vibrationPattern[1] = 1000;
- vibrationPattern[2] = 5000;
- vibrationPattern[3] = 2000;
-
- return vibrationPattern;
- }
-
- Future? showNow({required String title, required String subtitle, required String payload}) {
- Future.delayed(Duration(seconds: 1)).then((result) async {
- var androidPlatformChannelSpecifics = AndroidNotificationDetails(
- 'com.hmg.local_notification',
- 'HMG',
- channelDescription: 'HMG',
- importance: Importance.max,
- priority: Priority.high,
- ticker: 'ticker',
- vibrationPattern: _vibrationPattern(),
- ongoing: true,
- autoCancel: false,
- usesChronometer: true,
- when: DateTime.now().millisecondsSinceEpoch - 120 * 1000,
- );
- var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
- var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics);
- await flutterLocalNotificationsPlugin.show(25613, title, subtitle, platformChannelSpecifics, payload: payload).catchError((err) {
- print(err);
- });
- });
- }
-
- Future scheduleNotification({required DateTime scheduledNotificationDateTime, required String title, required String description}) async {
- ///vibrationPattern
- var vibrationPattern = Int64List(4);
- vibrationPattern[0] = 0;
- vibrationPattern[1] = 1000;
- vibrationPattern[2] = 5000;
- vibrationPattern[3] = 2000;
-
- // var androidPlatformChannelSpecifics = AndroidNotificationDetails('active-prescriptions', 'ActivePrescriptions',
- // channelDescription: 'ActivePrescriptionsDescription',
- // // icon: 'secondary_icon',
- // sound: RawResourceAndroidNotificationSound('slow_spring_board'),
- //
- // ///change it to be as ionic
- // // largeIcon: DrawableResourceAndroidBitmap('sample_large_icon'),///change it to be as ionic
- // vibrationPattern: vibrationPattern,
- // enableLights: true,
- // color: const Color.fromARGB(255, 255, 0, 0),
- // ledColor: const Color.fromARGB(255, 255, 0, 0),
- // ledOnMs: 1000,
- // ledOffMs: 500);
- // var iOSPlatformChannelSpecifics = DarwinNotificationDetails(sound: 'slow_spring_board.aiff');
-
- // /change it to be as ionic
- // var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics);
- // await flutterLocalNotificationsPlugin.schedule(0, title, description, scheduledNotificationDateTime, platformChannelSpecifics);
- }
-
- ///Repeat notification every day at approximately 10:00:00 am
- Future showDailyAtTime() async {
- // var time = Time(10, 0, 0);
- // var androidPlatformChannelSpecifics = AndroidNotificationDetails('repeatDailyAtTime channel id', 'repeatDailyAtTime channel name', channelDescription: 'repeatDailyAtTime description');
- // var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
- // var platformChannelSpecifics = NotificationDetails(
- // androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
- // await flutterLocalNotificationsPlugin.showDailyAtTime(
- // 0,
- // 'show daily title',
- // 'Daily notification shown at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
- // time,
- // platformChannelSpecifics);
- }
-
- ///Repeat notification weekly on Monday at approximately 10:00:00 am
- Future showWeeklyAtDayAndTime() async {
- // var time = Time(10, 0, 0);
- // var androidPlatformChannelSpecifics = AndroidNotificationDetails('show weekly channel id', 'show weekly channel name', channelDescription: 'show weekly description');
- // var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
- // var platformChannelSpecifics = NotificationDetails(
- // androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
- // await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
- // 0,
- // 'show weekly title',
- // 'Weekly notification shown on Monday at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
- // Day.Monday,
- // time,
- // platformChannelSpecifics);
- }
-
- String _toTwoDigitString(int value) {
- return value.toString().padLeft(2, '0');
- }
-
- Future cancelNotification() async {
- await flutterLocalNotificationsPlugin.cancel(0);
- }
-
- Future cancelAllNotifications() async {
- await flutterLocalNotificationsPlugin.cancelAll();
- }
-}
diff --git a/lib/core/utils/penguin_method_channel.dart b/lib/core/utils/penguin_method_channel.dart
new file mode 100644
index 00000000..1f190376
--- /dev/null
+++ b/lib/core/utils/penguin_method_channel.dart
@@ -0,0 +1,105 @@
+import 'package:flutter/services.dart';
+
+class PenguinMethodChannel {
+ static const MethodChannel _channel = MethodChannel('launch_penguin_ui');
+
+ Future loadGif() async {
+ return await rootBundle.load("assets/images/progress-loading-red-crop-1.gif").then((data) => data.buffer.asUint8List());
+ }
+
+ Future launch(String storyboardName, String languageCode, String username, {NavigationClinicDetails? details}) async {
+ // Uint8List image = await loadGif();
+ try {
+ await _channel.invokeMethod('launchPenguin', {
+ "storyboardName": storyboardName,
+ "baseURL": "https://penguinuat.hmg.com",
+ // "dataURL": "https://hmg.nav.penguinin.com",
+ // "positionURL": "https://hmg.nav.penguinin.com",
+ // "dataURL": "https://hmg-v33.local.penguinin.com",
+ // "positionURL": "https://hmg-v33.local.penguinin.com",
+ "dataURL": "https://penguinuat.hmg.com",
+ "positionURL": "https://penguinuat.hmg.com",
+ "dataServiceName": "api",
+ "positionServiceName": "pe",
+ "clientID": "HMG",
+ "clientKey": "UGVuZ3VpbklOX1Blbk5hdl9QSUY=",
+ "username": details?.patientId ?? "Haroon",
+ // "username": "Haroon",
+ "isSimulationModeEnabled": false,
+ "isShowUserName": false,
+ "isUpdateUserLocationSmoothly": true,
+ "isEnableReportIssue": true,
+ "languageCode": languageCode,
+ "mapBoxKey": "pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ",
+ "clinicID": details?.clinicId ?? "",
+ // "clinicID": "108", // 46 ,49, 133
+ "patientID": details?.patientId ?? "",
+ "projectID": int.parse(details?.projectId ?? "-1"),
+ // "loaderImage": image,
+ });
+ } on PlatformException catch (e) {
+ print("Failed to launch PenguinIn: '${e.message}'.");
+ }
+ }
+
+ void setMethodCallHandler(){
+ _channel.setMethodCallHandler((MethodCall call) async {
+ try {
+
+ print(call.method);
+
+ switch (call.method) {
+
+ case PenguinMethodNames.onPenNavInitializationError:
+ _handleInitializationError(call.arguments); // Handle onPenNavInitializationError errors.
+ break;
+ case PenguinMethodNames.onPenNavUIDismiss:
+ //todo handle pen dismissable
+ // _handlePenNavUIDismiss(); // Handle UI dismissal event.
+ break;
+ case PenguinMethodNames.onReportIssue:
+ // Handle the report issue event.
+ _handleInitializationError(call.arguments);
+ break;
+ default:
+ _handleUnknownMethod(call.method); // Handle unknown method calls.
+ }
+ } catch (e) {
+ print("Error handling method call '${call.method}': $e");
+ // Optionally, log this error to an external service
+ }
+ });
+ }
+ static void _handleUnknownMethod(String method) {
+ print("Unknown method: $method");
+ // Optionally, handle this unknown method case, such as reporting or ignoring it
+ }
+
+
+ static void _handleInitializationError(Map error) {
+ final type = error['type'] as String?;
+ final description = error['description'] as String?;
+ print("Initialization Error: ${type ?? 'Unknown Type'}, ${description ?? 'No Description'}");
+
+ }
+
+}
+// Define constants for method names
+class PenguinMethodNames {
+ static const String showPenguinUI = 'showPenguinUI';
+ static const String openSharedLocation = 'openSharedLocation';
+
+ // ---- Handler Method
+ static const String onPenNavSuccess = 'onPenNavSuccess'; // Tested Android,iOS
+ static const String onPenNavInitializationError = 'onPenNavInitializationError'; // Tested Android,iOS
+ static const String onPenNavUIDismiss = 'onPenNavUIDismiss'; //Tested Android,iOS
+ static const String onReportIssue = 'onReportIssue'; // Tested Android,iOS
+ static const String onLocationOffCampus = 'onLocationOffCampus'; // Tested iOS,Android
+ static const String navigateToPOI = 'navigateToPOI'; // Tested Android,iOS
+}
+
+class NavigationClinicDetails {
+ String? clinicId;
+ String? patientId;
+ String? projectId;
+}
diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart
index a96b8050..88e8cc84 100644
--- a/lib/core/utils/push_notification_handler.dart
+++ b/lib/core/utils/push_notification_handler.dart
@@ -15,16 +15,11 @@ import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart';
import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart';
-// import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
-
-import 'package:flutter_local_notifications/flutter_local_notifications.dart';
-import 'package:hmg_patient_app_new/core/utils/local_notifications.dart';
+import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
-import '../cache_consts.dart';
-
// |--> Push Notification Background
@pragma('vm:entry-point')
Future backgroundMessageHandler(dynamic message) async {
@@ -36,7 +31,7 @@ Future backgroundMessageHandler(dynamic message) async {
// showCallkitIncoming(message);
_incomingCall(message.data);
return;
- } else {}
+ }
}
callPage(String sessionID, String token) async {}
@@ -323,7 +318,7 @@ class PushNotificationHandler {
if (fcmToken != null) onToken(fcmToken);
// }
} catch (ex) {
- print("Notification Exception: " + ex.toString());
+ print("Notification Exception: $ex");
}
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
}
@@ -331,7 +326,7 @@ class PushNotificationHandler {
if (Platform.isIOS) {
final permission = await FirebaseMessaging.instance.requestPermission();
await FirebaseMessaging.instance.getAPNSToken().then((value) async {
- log("APNS token: " + value.toString());
+ log("APNS token: $value");
await Utils.saveStringFromPrefs(CacheConst.apnsToken, value.toString());
});
await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
@@ -378,14 +373,14 @@ class PushNotificationHandler {
});
FirebaseMessaging.instance.getToken().then((String? token) {
- print("Push Notification getToken: " + token!);
+ print("Push Notification getToken: ${token!}");
onToken(token!);
}).catchError((err) {
print(err);
});
FirebaseMessaging.instance.onTokenRefresh.listen((fcm_token) {
- print("Push Notification onTokenRefresh: " + fcm_token);
+ print("Push Notification onTokenRefresh: $fcm_token");
onToken(fcm_token);
});
@@ -401,7 +396,7 @@ class PushNotificationHandler {
}
newMessage(RemoteMessage remoteMessage) async {
- print("Remote Message: " + remoteMessage.data.toString());
+ print("Remote Message: ${remoteMessage.data}");
if (remoteMessage.data.isEmpty) {
return;
}
@@ -427,7 +422,7 @@ class PushNotificationHandler {
}
onToken(String token) async {
- print("Push Notification Token: " + token);
+ print("Push Notification Token: $token");
await Utils.saveStringFromPrefs(CacheConst.pushToken, token);
}
@@ -441,9 +436,7 @@ class PushNotificationHandler {
Future requestPermissions() async {
try {
if (Platform.isIOS) {
- await flutterLocalNotificationsPlugin
- .resolvePlatformSpecificImplementation()
- ?.requestPermissions(alert: true, badge: true, sound: true);
+ await FirebaseMessaging.instance.requestPermission(alert: true, badge: true, sound: true);
} else if (Platform.isAndroid) {
Map statuses = await [
Permission.notification,
diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart
index 8a0703e6..4fdc09cf 100644
--- a/lib/core/utils/size_utils.dart
+++ b/lib/core/utils/size_utils.dart
@@ -1,4 +1,5 @@
import 'dart:developer';
+import 'dart:math' as math;
import 'package:flutter/material.dart'; // These are the Viewport values of your Figma Design.
@@ -6,6 +7,16 @@ import 'package:flutter/material.dart'; // These are the Viewport values of your
const num figmaDesignWidth = 375; // iPhone X / 12 base width
const num figmaDesignHeight = 812; // iPhone X / 12 base height
+
+extension ConstrainedResponsive on num {
+ /// Width with max cap for tablets
+ double get wCapped => isTablet ? math.min( w, this * 1.3) : w;
+
+ /// Height with max cap for tablets
+ double get hCapped => isTablet ? math.min(h, this * 1.3) : h;
+}
+
+
extension ResponsiveExtension on num {
double get _screenWidth => SizeUtils.width;
diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart
index 38d04b96..03ff6b59 100644
--- a/lib/core/utils/utils.dart
+++ b/lib/core/utils/utils.dart
@@ -39,6 +39,50 @@ class Utils {
static bool get isLoading => _isLoadingVisible;
+ static var navigationProjectsList = [
+ {
+ "Desciption": "Sahafa Hospital",
+ "DesciptionN": "مستشفى الصحافة",
+ "ID": 1,
+ "LegalName": "Sahafa Hospital",
+ "LegalNameN": "مستشفى الصحافة",
+ "Name": "Sahafa Hospital",
+ "NameN": "مستشفى الصحافة",
+ "PhoneNumber": "+966115222222",
+ "SetupID": "013311",
+ "DistanceInKilometers": 0,
+ "HasVida3": false,
+ "IsActive": true,
+ "IsHmg": true,
+ "IsVidaPlus": false,
+ "Latitude": "24.8113774",
+ "Longitude": "46.6239813",
+ "MainProjectID": 130,
+ "ProjectOutSA": false,
+ "UsingInDoctorApp": false
+ },{
+ "Desciption": "Jeddah Hospital",
+ "DesciptionN": "مستشفى الصحافة",
+ "ID": 3,
+ "LegalName": "Jeddah Hospital",
+ "LegalNameN": "مستشفى الصحافة",
+ "Name": "Jeddah Hospital",
+ "NameN": "مستشفى الصحافة",
+ "PhoneNumber": "+966115222222",
+ "SetupID": "013311",
+ "DistanceInKilometers": 0,
+ "HasVida3": false,
+ "IsActive": true,
+ "IsHmg": true,
+ "IsVidaPlus": false,
+ "Latitude": "24.8113774",
+ "Longitude": "46.6239813",
+ "MainProjectID": 130,
+ "ProjectOutSA": false,
+ "UsingInDoctorApp": false
+ }
+ ];
+
static void showToast(String message, {bool longDuration = true}) {
Fluttertoast.showToast(
msg: message,
@@ -326,7 +370,7 @@ class Utils {
children: [
SizedBox(height: isSmallWidget ? 0.h : 48.h),
Lottie.asset(AppAnimations.noData,
- repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill),
+ repeat: false, reverse: false, frameRate: FrameRate(60), width: width.w, height: height.h, fit: BoxFit.fill),
SizedBox(height: 16.h),
(noDataText ?? LocaleKeys.noDataAvailable.tr())
.toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)
@@ -351,10 +395,10 @@ class Utils {
).center;
}
- static Widget getSuccessWidget({String? loadingText}) {
+ static Widget getSuccessWidget({String? loadingText, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center}) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
+ crossAxisAlignment: crossAxisAlignment,
children: [
Lottie.asset(AppAnimations.checkmark, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
@@ -722,7 +766,16 @@ class Utils {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image.asset(AppAssets.mada, width: 25.h, height: 25.h),
- Image.asset(AppAssets.tamaraEng, width: 25.h, height: 25.h),
+ Image.asset(
+ AppAssets.tamaraEng,
+ width: 25.h,
+ height: 25.h,
+ fit: BoxFit.contain,
+ errorBuilder: (context, error, stackTrace) {
+ debugPrint('Failed to load Tamara PNG in payment methods: $error');
+ return Utils.buildSvgWithAssets(icon: AppAssets.tamara, width: 25.h, height: 25.h, fit: BoxFit.contain);
+ },
+ ),
Image.asset(AppAssets.visa, width: 25.h, height: 25.h),
Image.asset(AppAssets.mastercard, width: 25.h, height: 25.h),
Image.asset(AppAssets.applePay, width: 25.h, height: 25.h),
@@ -859,6 +912,17 @@ class Utils {
isHMC: hospital.isHMC);
}
+
+ static HospitalsModel? convertToHospitalsModel(PatientDoctorAppointmentList? item) {
+ if (item == null) return null;
+ return HospitalsModel(
+ name: item.filterName,
+ nameN: item.filterName,
+ distanceInKilometers: item.distanceInKMs,
+ isHMC: item.isHMC,
+ );
+ }
+
static bool havePrivilege(int id) {
bool isHavePrivilege = false;
try {
@@ -876,7 +940,6 @@ class Utils {
launchUrl(uri, mode: LaunchMode.inAppBrowserView);
}
-
static Color getCardBorderColor(int currentQueueStatus) {
switch (currentQueueStatus) {
case 0:
diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart
index 2039fb85..309dde19 100644
--- a/lib/extensions/string_extensions.dart
+++ b/lib/extensions/string_extensions.dart
@@ -23,14 +23,15 @@ extension CapExtension on String {
extension EmailValidator on String {
Widget get toWidget => Text(this);
- Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text(
+ Widget toText8({Color? color, FontWeight? fontWeight, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) =>
+ Text(
this,
maxLines: maxlines,
overflow: textOverflow,
style: TextStyle(
fontSize: 8.f,
fontStyle: fontStyle ?? FontStyle.normal,
- fontWeight: isBold ? FontWeight.bold : FontWeight.normal,
+ fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal),
color: color ?? AppColors.blackColor,
letterSpacing: 0,
),
@@ -41,7 +42,7 @@ extension EmailValidator on String {
FontWeight? weight,
bool isBold = false,
bool isUnderLine = false,
- bool isCenter = false,
+ bool isCenter = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow,
@@ -191,7 +192,8 @@ extension EmailValidator on String {
letterSpacing: letterSpacing,
height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
- decoration: isUnderLine ? TextDecoration.underline : null),
+ decoration: isUnderLine ? TextDecoration.underline : null,
+ decorationColor: color ?? AppColors.blackColor),
);
Widget toText15(
@@ -214,39 +216,38 @@ extension EmailValidator on String {
decoration: isUnderLine ? TextDecoration.underline : null),
);
- Widget toText16({
- Color? color,
- bool isUnderLine = false,
- bool isBold = false,
- bool isCenter = false,
- int? maxlines,
- double? height,
- TextAlign? textAlign,
- FontWeight? weight,
- TextOverflow? textOverflow,
- double? letterSpacing = -0.4,
- Color decorationColor =AppColors.errorColor
- }) =>
+ Widget toText16(
+ {Color? color,
+ bool isUnderLine = false,
+ bool isBold = false,
+ bool isCenter = false,
+ int? maxlines,
+ double? height,
+ TextAlign? textAlign,
+ FontWeight? weight,
+ TextOverflow? textOverflow,
+ double? letterSpacing = -0.4,
+ Color decorationColor = AppColors.errorColor}) =>
Text(
this,
maxLines: maxlines,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
- color: color ?? AppColors.blackColor,
- fontSize: 16.f,
- letterSpacing: letterSpacing,
- height: height,
- overflow: textOverflow,
- fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
- decoration: isUnderLine ? TextDecoration.underline : null,
- decorationColor: decorationColor
- ),
+ color: color ?? AppColors.blackColor,
+ fontSize: 16.f,
+ letterSpacing: letterSpacing,
+ height: height,
+ overflow: textOverflow,
+ fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
+ decoration: isUnderLine ? TextDecoration.underline : null,
+ decorationColor: decorationColor),
);
Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
- style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
+ style: TextStyle(
+ color: color ?? AppColors.blackColor, fontSize: 17.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toText18({Color? color, FontWeight? weight, bool isBold = false, bool isCenter = false, int? maxlines, TextOverflow? textOverflow}) => Text(
@@ -255,39 +256,62 @@ extension EmailValidator on String {
this,
overflow: textOverflow,
style: TextStyle(
- fontSize: 18.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),
+ fontSize: 18.f,
+ fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
+ color: color ?? AppColors.blackColor,
+ letterSpacing: -0.4),
);
Widget toText19({Color? color, bool isBold = false}) => Text(
this,
- style: TextStyle(fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
+ style: TextStyle(
+ fontSize: 19.f, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4),
);
- Widget toText20({Color? color, FontWeight? weight, bool isBold = false, }) => Text(
+ Widget toText20({
+ Color? color,
+ FontWeight? weight,
+ bool isBold = false,
+ }) =>
+ Text(
this,
style: TextStyle(
- fontSize: 20.f, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4),
+ fontSize: 20.f,
+ fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
+ color: color ?? AppColors.blackColor,
+ letterSpacing: -0.4),
);
Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text(
this,
maxLines: maxlines,
style: TextStyle(
- color: color ?? AppColors.blackColor, fontSize: 21.f, letterSpacing: -1, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
+ color: color ?? AppColors.blackColor,
+ fontSize: 21.f,
+ letterSpacing: -1,
+ fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
);
Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
- height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
+ height: 1,
+ color: color ?? AppColors.blackColor,
+ fontSize: 22.f,
+ letterSpacing: -1,
+ fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
- height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing??-1, fontWeight: isBold ? FontWeight.bold : fontWeight??FontWeight.normal),
+ height: 23 / 24,
+ color: color ?? AppColors.blackColor,
+ fontSize: 24.f,
+ letterSpacing: letterSpacing ?? -1,
+ fontWeight: isBold ? FontWeight.bold : fontWeight ?? FontWeight.normal),
);
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text(
@@ -312,17 +336,25 @@ extension EmailValidator on String {
fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
- Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text(
+ Widget toText32({FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(
- height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
+ height: 32 / 32,
+ color: color ?? AppColors.blackColor,
+ fontSize: 32.f,
+ letterSpacing: -1,
+ fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal),
);
Widget toText44({Color? color, bool isBold = false}) => Text(
this,
style: TextStyle(
- height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
+ height: 32 / 32,
+ color: color ?? AppColors.blackColor,
+ fontSize: 44.f,
+ letterSpacing: -1,
+ fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) {
diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart
index 4a1517c3..9b09c43c 100644
--- a/lib/features/authentication/authentication_repo.dart
+++ b/lib/features/authentication/authentication_repo.dart
@@ -260,10 +260,10 @@ class AuthenticationRepoImp implements AuthenticationRepo {
newRequest.forRegisteration = newRequest.isRegister ?? false;
newRequest.isRegister = false;
//silent login case removed token and login token
- // if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true && (newRequest.loginType==1 || newRequest.loginType==4)) {
- // newRequest.logInTokenID = null;
- // newRequest.deviceToken = null;
- // }
+ if(newRequest.logInTokenID.isEmpty && newRequest.isSilentLogin == true && (newRequest.loginType==1 || newRequest.loginType==4)) {
+ newRequest.logInTokenID = null;
+ newRequest.deviceToken = null;
+ }
}
diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart
index 46649fb0..b3937727 100644
--- a/lib/features/authentication/authentication_view_model.dart
+++ b/lib/features/authentication/authentication_view_model.dart
@@ -32,6 +32,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/saved_login_screen.dart';
+import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@@ -39,6 +40,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
+import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'models/request_models/get_user_mobile_device_data.dart';
@@ -565,7 +567,7 @@ class AuthenticationViewModel extends ChangeNotifier {
if (!_appState.getIsChildLoggedIn) {
await medicalVm.getFamilyFiles(status: 0);
await medicalVm.getAllPendingRecordsByResponseId();
- _navigationService.popUntilNamed(AppRoutes.landingScreen);
+ _navigationService.replaceAllRoutesAndNavigateToLanding();
}
} else {
if (activation.list != null && activation.list!.isNotEmpty) {
@@ -675,7 +677,12 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future navigateToHomeScreen() async {
- _navigationService.pushAndReplace(AppRoutes.landingScreen);
+ Navigator.pushAndRemoveUntil(
+ _navigationService.navigatorKey.currentContext!,
+ CustomPageRoute(
+ page: LandingNavigation(),
+ ),
+ (r) => false);
}
Future navigateToOTPScreen(
diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart
index f6e83ff1..cfd473ee 100644
--- a/lib/features/book_appointments/book_appointments_repo.dart
+++ b/lib/features/book_appointments/book_appointments_repo.dart
@@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
+import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
@@ -102,6 +103,8 @@ abstract class BookAppointmentsRepo {
required int userAge,
Function(dynamic)? onSuccess,
Function(String)? onError});
+
+ Future>> getAppointmentNearestGate({required int projectID, required int clinicID});
}
class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@@ -1046,4 +1049,40 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
+
+ @override
+ Future>> getAppointmentNearestGate({required int projectID, required int clinicID}) async {
+ Map mapRequest = {"ProjectID": projectID, "ClinicID": clinicID};
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_APPOINTMENT_NEAREST_GATE,
+ body: mapRequest,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final nearestGateResponse = AppointmentNearestGateResponseModel.fromJson(response['getGateByProjectIDandClinicIDList'][0]);
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: nearestGateResponse,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
}
diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart
index ae74ffb7..cbed940e 100644
--- a/lib/features/book_appointments/book_appointments_view_model.dart
+++ b/lib/features/book_appointments/book_appointments_view_model.dart
@@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/LaserCategoryType.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/free_slot.dart';
+import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/dental_chief_complaints_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
@@ -44,6 +45,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool isDoctorsListLoading = false;
bool isDoctorProfileLoading = false;
bool isDoctorSearchByNameStarted = false;
+ bool isAppointmentNearestGateLoading = false;
bool isLiveCareSchedule = false;
bool isGetDocForHealthCal = false;
@@ -132,6 +134,8 @@ class BookAppointmentsViewModel extends ChangeNotifier {
PatientAppointmentShareResponseModel? patientWalkInAppointmentShareResponseModel;
+ AppointmentNearestGateResponseModel? appointmentNearestGateResponseModel;
+
///variables for laser clinic
List femaleLaserCategory = [
LaserCategoryType(1, 'bodyString'),
@@ -1343,4 +1347,32 @@ class BookAppointmentsViewModel extends ChangeNotifier {
},
);
}
+
+ Future getAppointmentNearestGate({required int projectID, required int clinicID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ isAppointmentNearestGateLoading = true;
+ notifyListeners();
+
+ final result = await bookAppointmentsRepo.getAppointmentNearestGate(projectID: projectID, clinicID: clinicID);
+
+ result.fold(
+ (failure) async {
+ if (onError != null) {
+ onError(failure.message);
+ }
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ onError!(apiResponse.errorMessage!);
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ appointmentNearestGateResponseModel = apiResponse.data!;
+ isAppointmentNearestGateLoading = false;
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
}
diff --git a/lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart b/lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart
new file mode 100644
index 00000000..bdaa4e23
--- /dev/null
+++ b/lib/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart
@@ -0,0 +1,64 @@
+class AppointmentNearestGateResponseModel {
+ String? clinicDescription;
+ String? clinicDescriptionN;
+ int? clinicID;
+ String? clinicLocation;
+ String? clinicLocationN;
+ int? gender;
+ int? iD;
+ String? nearestGateNumber;
+ String? nearestGateNumberN;
+ int? projectID;
+ String? projectName;
+ String? projectNameN;
+ int? rowID;
+
+ AppointmentNearestGateResponseModel(
+ {this.clinicDescription,
+ this.clinicDescriptionN,
+ this.clinicID,
+ this.clinicLocation,
+ this.clinicLocationN,
+ this.gender,
+ this.iD,
+ this.nearestGateNumber,
+ this.nearestGateNumberN,
+ this.projectID,
+ this.projectName,
+ this.projectNameN,
+ this.rowID});
+
+ AppointmentNearestGateResponseModel.fromJson(Map json) {
+ clinicDescription = json['ClinicDescription'];
+ clinicDescriptionN = json['ClinicDescriptionN'];
+ clinicID = json['ClinicID'];
+ clinicLocation = json['ClinicLocation'];
+ clinicLocationN = json['ClinicLocationN'];
+ gender = json['Gender'];
+ iD = json['ID'];
+ nearestGateNumber = json['NearestGateNumber'];
+ nearestGateNumberN = json['NearestGateNumberN'];
+ projectID = json['ProjectID'];
+ projectName = json['ProjectName'];
+ projectNameN = json['ProjectNameN'];
+ rowID = json['RowID'];
+ }
+
+ Map toJson() {
+ final Map data = Map();
+ data['ClinicDescription'] = clinicDescription;
+ data['ClinicDescriptionN'] = clinicDescriptionN;
+ data['ClinicID'] = clinicID;
+ data['ClinicLocation'] = clinicLocation;
+ data['ClinicLocationN'] = clinicLocationN;
+ data['Gender'] = gender;
+ data['ID'] = iD;
+ data['NearestGateNumber'] = nearestGateNumber;
+ data['NearestGateNumberN'] = nearestGateNumberN;
+ data['ProjectID'] = projectID;
+ data['ProjectName'] = projectName;
+ data['ProjectNameN'] = projectNameN;
+ data['RowID'] = rowID;
+ return data;
+ }
+}
diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart
index b2b3709e..85e6018e 100644
--- a/lib/features/hmg_services/hmg_services_repo.dart
+++ b/lib/features/hmg_services/hmg_services_repo.dart
@@ -17,8 +17,11 @@ import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'models/req_models/create_e_referral_model.dart';
import 'models/req_models/send_activation_code_ereferral_req_model.dart';
+import 'models/resq_models/covid_get_test_proceedure_resp.dart';
+import 'models/resq_models/get_covid_payment_info_resp.dart';
import 'models/resq_models/relationship_type_resp_mode.dart';
import 'models/resq_models/search_e_referral_resp_model.dart';
+import 'models/resq_models/vital_sign_respo_model.dart';
abstract class HmgServicesRepo {
Future>>> getAllComprehensiveCheckupOrders();
@@ -60,7 +63,11 @@ abstract class HmgServicesRepo {
Future>>> searchEReferral(SearchEReferralRequestModel requestModel);
+ Future>>> getCovidTestProcedures();
+ Future>> getCovidPaymentInfo(String procedureID, int projectID);
+
+ Future>>> getPatientVitalSign();
}
class HmgServicesRepoImp implements HmgServicesRepo {
@@ -816,4 +823,149 @@ class HmgServicesRepoImp implements HmgServicesRepo {
}
}
+
+ @override
+ Future>>> getCovidTestProcedures() async {
+
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+
+ await apiClient.post(
+ GET_COVID_DRIVETHRU_PROCEDURES_LIST,
+ body: {"TestTypeEnum":2,"TestProcedureEnum":3,},
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ loggerService.logError("Covid Test Procedure : $error, Status: $statusCode");
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ List covidTestProcedure = [];
+
+ if (response['COVID19_TestProceduresList'] != null && response['COVID19_TestProceduresList'] is List) {
+ final servicesList = response['COVID19_TestProceduresList'] as List;
+
+ for (var serviceJson in servicesList) {
+ if (serviceJson is Map) {
+ covidTestProcedure.add(Covid19GetTestProceduresResp.fromJson(serviceJson));
+ }
+ }
+ }
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: covidTestProcedure,
+ );
+ } catch (e) {
+ loggerService.logError("Error parsing E-Referral services: ${e.toString()}");
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ log("Unknown error in Search Referral: ${e.toString()}");
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> getCovidPaymentInfo(String procedureID, int projectID) async {
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+
+ await apiClient.post(
+ GET_COVID_DRIVETHRU_PAYMENT_INFO,
+ body: {"TestTypeEnum":2,"TestProcedureEnum":3, "ProcedureId":procedureID, "ProjectID":projectID,},
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ loggerService.logError("Covid Test Procedure : $error, Status: $statusCode");
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+
+ Covid19GetPaymentInfo covidPaymentInfo = Covid19GetPaymentInfo.fromJson(response["COVID19_PatientShare"]);
+
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: covidPaymentInfo,
+ );
+ } catch (e) {
+ loggerService.logError("Error parsing E-Referral services: ${e.toString()}");
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ log("Unknown error in Search Referral: ${e.toString()}");
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>>> getPatientVitalSign() async {
+ Map requestBody = {};
+
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+
+ await apiClient.post(
+ GET_PATIENT_VITAL_SIGN,
+ body: requestBody,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ loggerService.logError("Patient Vital Sign API Failed: $error, Status: $statusCode");
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ List vitalSignList = [];
+
+ if (response['List_DoctorPatientVitalSign'] != null && response['List_DoctorPatientVitalSign'] is List) {
+ final vitalSignsList = response['List_DoctorPatientVitalSign'] as List;
+
+ for (var vitalSignJson in vitalSignsList) {
+ if (vitalSignJson is Map) {
+ vitalSignList.add(VitalSignResModel.fromJson(vitalSignJson));
+ }
+ }
+ }
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: vitalSignList,
+ );
+ } catch (e) {
+ loggerService.logError("Error parsing Patient Vital Sign: ${e.toString()}");
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ log("Unknown error in getPatientVitalSign: ${e.toString()}");
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+
}
diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart
index 5da08609..c55a11c3 100644
--- a/lib/features/hmg_services/hmg_services_view_model.dart
+++ b/lib/features/hmg_services/hmg_services_view_model.dart
@@ -10,15 +10,18 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/crea
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/search_e_referral_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/search_e_referral_resp_model.dart';
+import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'models/req_models/check_activation_e_referral_req_model.dart';
+import 'models/resq_models/get_covid_payment_info_resp.dart';
import 'models/resq_models/relationship_type_resp_mode.dart';
import 'models/ui_models/covid_questionnare_model.dart';
@@ -35,6 +38,7 @@ class HmgServicesViewModel extends ChangeNotifier {
bool isCmcServicesLoading = false;
bool isUpdatingOrder = false;
bool isHospitalListLoading = false;
+ bool isVitalSignLoading = false;
// HHC specific loading states
bool isHhcOrdersLoading = false;
@@ -45,6 +49,20 @@ class HmgServicesViewModel extends ChangeNotifier {
List hospitalsList = [];
List filteredHospitalsList = [];
HospitalsModel? selectedHospital;
+ List vitalSignList = [];
+
+ // Vital Sign PageView Controller
+ PageController _vitalSignPageController = PageController();
+ PageController get vitalSignPageController => _vitalSignPageController;
+
+ int _vitalSignCurrentPage = 0;
+ int get vitalSignCurrentPage => _vitalSignCurrentPage;
+
+ void setVitalSignCurrentPage(int page) {
+ _vitalSignCurrentPage = page;
+ notifyListeners();
+ }
+
// HHC specific lists
List hhcOrdersList = [];
@@ -60,7 +78,8 @@ class HmgServicesViewModel extends ChangeNotifier {
List relationTypes = [];
List getAllCitiesList = [];
List searchReferralList = [];
-
+ List covidTestProcedureList = [];
+ Covid19GetPaymentInfo? covidPaymentInfo;
Future getOrdersList() async {}
@@ -783,4 +802,117 @@ class HmgServicesViewModel extends ChangeNotifier {
return [];
}
}
+
+
+ Future getCovidProcedureList({
+
+ Function(dynamic)? onSuccess,
+ Function(String)? onError,
+ }) async {
+ notifyListeners();
+
+ final result = await hmgServicesRepo.getCovidTestProcedures();
+
+ result.fold(
+ (failure) async {
+ notifyListeners();
+ await errorHandlerService.handleError(failure: failure);
+ if (onError != null) {
+ onError(failure.toString());
+ }
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 1) {
+ covidTestProcedureList = apiResponse.data ?? [];
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ } else {
+ notifyListeners();
+ if (onError != null) {
+ onError(apiResponse.errorMessage ?? 'Unknown error');
+ }
+ }
+ },
+ );
+ }
+
+
+ Future getPaymentInfo({
+ String? procedureID,
+ int? projectID,
+ Function(dynamic)? onSuccess,
+ Function(String)? onError,
+ }) async {
+ notifyListeners();
+
+ final result = await hmgServicesRepo.getCovidPaymentInfo(procedureID!, projectID!);
+
+ result.fold(
+ (failure) async {
+ notifyListeners();
+ await errorHandlerService.handleError(failure: failure);
+ if (onError != null) {
+ onError(failure.toString());
+ }
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 1) {
+ covidPaymentInfo = apiResponse.data;
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ } else {
+ notifyListeners();
+ if (onError != null) {
+ onError(apiResponse.errorMessage ?? 'Unknown error');
+ }
+ }
+ },
+ );
+ }
+
+ Future getPatientVitalSign({
+ Function(dynamic)? onSuccess,
+ Function(String)? onError,
+ }) async {
+ isVitalSignLoading = true;
+ notifyListeners();
+
+ final result = await hmgServicesRepo.getPatientVitalSign();
+
+ result.fold(
+ (failure) async {
+ isVitalSignLoading = false;
+ notifyListeners();
+ await errorHandlerService.handleError(failure: failure);
+ if (onError != null) {
+ onError(failure.toString());
+ }
+ },
+ (apiResponse) {
+ isVitalSignLoading = false;
+ if (apiResponse.messageStatus == 1) {
+ vitalSignList = apiResponse.data ?? [];
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ } else {
+ notifyListeners();
+ if (onError != null) {
+ onError(apiResponse.errorMessage ?? 'Unknown error');
+ }
+ }
+ },
+ );
+ }
+
+ @override
+ void dispose() {
+ _vitalSignPageController.dispose();
+ super.dispose();
+ }
}
diff --git a/lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart b/lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart
new file mode 100644
index 00000000..c27f1e78
--- /dev/null
+++ b/lib/features/hmg_services/models/resq_models/covid_get_test_proceedure_resp.dart
@@ -0,0 +1,33 @@
+import 'dart:convert';
+
+class Covid19GetTestProceduresResp {
+ String? procedureId;
+ String? procedureName;
+ String? procedureNameN;
+ String? setupId;
+
+ Covid19GetTestProceduresResp({
+ this.procedureId,
+ this.procedureName,
+ this.procedureNameN,
+ this.setupId,
+ });
+
+ factory Covid19GetTestProceduresResp.fromRawJson(String str) => Covid19GetTestProceduresResp.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory Covid19GetTestProceduresResp.fromJson(Map json) => Covid19GetTestProceduresResp(
+ procedureId: json["ProcedureID"],
+ procedureName: json["ProcedureName"],
+ procedureNameN: json["ProcedureNameN"],
+ setupId: json["SetupID"],
+ );
+
+ Map toJson() => {
+ "ProcedureID": procedureId,
+ "ProcedureName": procedureName,
+ "ProcedureNameN": procedureNameN,
+ "SetupID": setupId,
+ };
+}
diff --git a/lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart b/lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart
new file mode 100644
index 00000000..5f95263b
--- /dev/null
+++ b/lib/features/hmg_services/models/resq_models/get_covid_payment_info_resp.dart
@@ -0,0 +1,105 @@
+import 'dart:convert';
+
+class Covid19GetPaymentInfo {
+ dynamic propertyChanged;
+ int? cashPriceField;
+ int? cashPriceTaxField;
+ int? cashPriceWithTaxField;
+ int? companyIdField;
+ String? companyNameField;
+ int? companyShareWithTaxField;
+ dynamic errCodeField;
+ int? groupIdField;
+ dynamic insurancePolicyNoField;
+ String? messageField;
+ dynamic patientCardIdField;
+ int? patientShareField;
+ double? patientShareWithTaxField;
+ double? patientTaxAmountField;
+ int? policyIdField;
+ dynamic policyNameField;
+ dynamic procedureIdField;
+ String? procedureNameField;
+ dynamic setupIdField;
+ int? statusCodeField;
+ dynamic subPolicyNoField;
+
+ Covid19GetPaymentInfo({
+ this.propertyChanged,
+ this.cashPriceField,
+ this.cashPriceTaxField,
+ this.cashPriceWithTaxField,
+ this.companyIdField,
+ this.companyNameField,
+ this.companyShareWithTaxField,
+ this.errCodeField,
+ this.groupIdField,
+ this.insurancePolicyNoField,
+ this.messageField,
+ this.patientCardIdField,
+ this.patientShareField,
+ this.patientShareWithTaxField,
+ this.patientTaxAmountField,
+ this.policyIdField,
+ this.policyNameField,
+ this.procedureIdField,
+ this.procedureNameField,
+ this.setupIdField,
+ this.statusCodeField,
+ this.subPolicyNoField,
+ });
+
+ factory Covid19GetPaymentInfo.fromRawJson(String str) => Covid19GetPaymentInfo.fromJson(json.decode(str));
+
+ String toRawJson() => json.encode(toJson());
+
+ factory Covid19GetPaymentInfo.fromJson(Map json) => Covid19GetPaymentInfo(
+ propertyChanged: json["PropertyChanged"],
+ cashPriceField: json["cashPriceField"],
+ cashPriceTaxField: json["cashPriceTaxField"],
+ cashPriceWithTaxField: json["cashPriceWithTaxField"],
+ companyIdField: json["companyIdField"],
+ companyNameField: json["companyNameField"],
+ companyShareWithTaxField: json["companyShareWithTaxField"],
+ errCodeField: json["errCodeField"],
+ groupIdField: json["groupIDField"],
+ insurancePolicyNoField: json["insurancePolicyNoField"],
+ messageField: json["messageField"],
+ patientCardIdField: json["patientCardIDField"],
+ patientShareField: json["patientShareField"],
+ patientShareWithTaxField: json["patientShareWithTaxField"]?.toDouble(),
+ patientTaxAmountField: json["patientTaxAmountField"]?.toDouble(),
+ policyIdField: json["policyIdField"],
+ policyNameField: json["policyNameField"],
+ procedureIdField: json["procedureIdField"],
+ procedureNameField: json["procedureNameField"],
+ setupIdField: json["setupIDField"],
+ statusCodeField: json["statusCodeField"],
+ subPolicyNoField: json["subPolicyNoField"],
+ );
+
+ Map toJson() => {
+ "PropertyChanged": propertyChanged,
+ "cashPriceField": cashPriceField,
+ "cashPriceTaxField": cashPriceTaxField,
+ "cashPriceWithTaxField": cashPriceWithTaxField,
+ "companyIdField": companyIdField,
+ "companyNameField": companyNameField,
+ "companyShareWithTaxField": companyShareWithTaxField,
+ "errCodeField": errCodeField,
+ "groupIDField": groupIdField,
+ "insurancePolicyNoField": insurancePolicyNoField,
+ "messageField": messageField,
+ "patientCardIDField": patientCardIdField,
+ "patientShareField": patientShareField,
+ "patientShareWithTaxField": patientShareWithTaxField,
+ "patientTaxAmountField": patientTaxAmountField,
+ "policyIdField": policyIdField,
+ "policyNameField": policyNameField,
+ "procedureIdField": procedureIdField,
+ "procedureNameField": procedureNameField,
+ "setupIDField": setupIdField,
+ "statusCodeField": statusCodeField,
+ "subPolicyNoField": subPolicyNoField,
+ };
+}
diff --git a/lib/features/hmg_services/models/resq_models/vital_sign_respo_model.dart b/lib/features/hmg_services/models/resq_models/vital_sign_respo_model.dart
new file mode 100644
index 00000000..bc93f59a
--- /dev/null
+++ b/lib/features/hmg_services/models/resq_models/vital_sign_respo_model.dart
@@ -0,0 +1,259 @@
+import 'package:hmg_patient_app_new/core/utils/date_util.dart';
+
+class VitalSignResModel {
+ var transNo;
+ var projectID;
+ var weightKg;
+ var heightCm;
+ var temperatureCelcius;
+ var pulseBeatPerMinute;
+ var respirationBeatPerMinute;
+ var bloodPressureLower;
+ var bloodPressureHigher;
+ var sAO2;
+ var fIO2;
+ var painScore;
+ var bodyMassIndex;
+ var headCircumCm;
+ var leanBodyWeightLbs;
+ var idealBodyWeightLbs;
+ var temperatureCelciusMethod;
+ var pulseRhythm;
+ var respirationPattern;
+ var bloodPressureCuffLocation;
+ var bloodPressureCuffSize;
+ var bloodPressurePatientPosition;
+ var painLocation;
+ var painDuration;
+ var painCharacter;
+ var painFrequency;
+ bool? isPainManagementDone;
+ var status;
+ bool? isVitalsRequired;
+ var patientID;
+ var createdOn;
+ var doctorID;
+ var clinicID;
+ var triageCategory;
+ var gCScore;
+ var lineItemNo;
+ DateTime? vitalSignDate;
+ var actualTimeTaken;
+ var sugarLevel;
+ var fBS;
+ var rBS;
+ var observationType;
+ var heartRate;
+ var muscleTone;
+ var reflexIrritability;
+ var bodyColor;
+ var isFirstAssessment;
+ var dateofBirth;
+ var timeOfBirth;
+ var bloodPressure;
+ var bloodPressureCuffLocationDesc;
+ var bloodPressureCuffSizeDesc;
+ var bloodPressurePatientPositionDesc;
+ var clinicName;
+ var doctorImageURL;
+ var doctorName;
+ var painScoreDesc;
+ var pulseRhythmDesc;
+ var respirationPatternDesc;
+ var temperatureCelciusMethodDesc;
+ var time;
+
+ VitalSignResModel(
+ {this.transNo,
+ this.projectID,
+ this.weightKg,
+ this.heightCm,
+ this.temperatureCelcius,
+ this.pulseBeatPerMinute,
+ this.respirationBeatPerMinute,
+ this.bloodPressureLower,
+ this.bloodPressureHigher,
+ this.sAO2,
+ this.fIO2,
+ this.painScore,
+ this.bodyMassIndex,
+ this.headCircumCm,
+ this.leanBodyWeightLbs,
+ this.idealBodyWeightLbs,
+ this.temperatureCelciusMethod,
+ this.pulseRhythm,
+ this.respirationPattern,
+ this.bloodPressureCuffLocation,
+ this.bloodPressureCuffSize,
+ this.bloodPressurePatientPosition,
+ this.painLocation,
+ this.painDuration,
+ this.painCharacter,
+ this.painFrequency,
+ this.isPainManagementDone,
+ this.status,
+ this.isVitalsRequired,
+ this.patientID,
+ this.createdOn,
+ this.doctorID,
+ this.clinicID,
+ this.triageCategory,
+ this.gCScore,
+ this.lineItemNo,
+ this.vitalSignDate,
+ this.actualTimeTaken,
+ this.sugarLevel,
+ this.fBS,
+ this.rBS,
+ this.observationType,
+ this.heartRate,
+ this.muscleTone,
+ this.reflexIrritability,
+ this.bodyColor,
+ this.isFirstAssessment,
+ this.dateofBirth,
+ this.timeOfBirth,
+ this.bloodPressure,
+ this.bloodPressureCuffLocationDesc,
+ this.bloodPressureCuffSizeDesc,
+ this.bloodPressurePatientPositionDesc,
+ this.clinicName,
+ this.doctorImageURL,
+ this.doctorName,
+ this.painScoreDesc,
+ this.pulseRhythmDesc,
+ this.respirationPatternDesc,
+ this.temperatureCelciusMethodDesc,
+ this.time});
+
+ VitalSignResModel.fromJson(Map json) {
+ transNo = json['TransNo'];
+ projectID = json['ProjectID'];
+ weightKg = json['WeightKg'];
+ heightCm = json['HeightCm'];
+ temperatureCelcius = json['TemperatureCelcius'];
+ pulseBeatPerMinute = json['PulseBeatPerMinute'];
+ respirationBeatPerMinute = json['RespirationBeatPerMinute'];
+ bloodPressureLower = json['BloodPressureLower'];
+ bloodPressureHigher = json['BloodPressureHigher'];
+ sAO2 = json['SAO2'];
+ fIO2 = json['FIO2'];
+ painScore = json['PainScore'];
+ bodyMassIndex = json['BodyMassIndex'];
+ headCircumCm = json['HeadCircumCm'];
+ leanBodyWeightLbs = json['LeanBodyWeightLbs'];
+ idealBodyWeightLbs = json['IdealBodyWeightLbs'];
+ temperatureCelciusMethod = json['TemperatureCelciusMethod'];
+ pulseRhythm = json['PulseRhythm'];
+ respirationPattern = json['RespirationPattern'];
+ bloodPressureCuffLocation = json['BloodPressureCuffLocation'];
+ bloodPressureCuffSize = json['BloodPressureCuffSize'];
+ bloodPressurePatientPosition = json['BloodPressurePatientPosition'];
+ painLocation = json['PainLocation'];
+ painDuration = json['PainDuration'];
+ painCharacter = json['PainCharacter'];
+ painFrequency = json['PainFrequency'];
+ isPainManagementDone = json['IsPainManagementDone'];
+ status = json['Status'];
+ isVitalsRequired = json['IsVitalsRequired'];
+ patientID = json['PatientID'];
+ createdOn = json['CreatedOn'];
+ doctorID = json['DoctorID'];
+ clinicID = json['ClinicID'];
+ triageCategory = json['TriageCategory'];
+ gCScore = json['GCScore'];
+ lineItemNo = json['LineItemNo'];
+ vitalSignDate = DateUtil.convertStringToDate(json['CreatedOn']);
+ actualTimeTaken = json['ActualTimeTaken'];
+ sugarLevel = json['SugarLevel'];
+ fBS = json['FBS'];
+ rBS = json['RBS'];
+ observationType = json['ObservationType'];
+ heartRate = json['HeartRate'];
+ muscleTone = json['MuscleTone'];
+ reflexIrritability = json['ReflexIrritability'];
+ bodyColor = json['BodyColor'];
+ isFirstAssessment = json['IsFirstAssessment'];
+ dateofBirth = json['DateofBirth'];
+ timeOfBirth = json['TimeOfBirth'];
+ bloodPressure = json['BloodPressure'];
+ bloodPressureCuffLocationDesc = json['BloodPressureCuffLocationDesc'];
+ bloodPressureCuffSizeDesc = json['BloodPressureCuffSizeDesc'];
+ bloodPressurePatientPositionDesc = json['BloodPressurePatientPositionDesc'];
+ clinicName = json['ClinicName'];
+ doctorImageURL = json['DoctorImageURL'];
+ doctorName = json['DoctorName'];
+ painScoreDesc = json['PainScoreDesc'];
+ pulseRhythmDesc = json['PulseRhythmDesc'];
+ respirationPatternDesc = json['RespirationPatternDesc'];
+ temperatureCelciusMethodDesc = json['TemperatureCelciusMethodDesc'];
+ time = json['Time'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['TransNo'] = this.transNo;
+ data['ProjectID'] = this.projectID;
+ data['WeightKg'] = this.weightKg;
+ data['HeightCm'] = this.heightCm;
+ data['TemperatureCelcius'] = this.temperatureCelcius;
+ data['PulseBeatPerMinute'] = this.pulseBeatPerMinute;
+ data['RespirationBeatPerMinute'] = this.respirationBeatPerMinute;
+ data['BloodPressureLower'] = this.bloodPressureLower;
+ data['BloodPressureHigher'] = this.bloodPressureHigher;
+ data['SAO2'] = this.sAO2;
+ data['FIO2'] = this.fIO2;
+ data['PainScore'] = this.painScore;
+ data['BodyMassIndex'] = this.bodyMassIndex;
+ data['HeadCircumCm'] = this.headCircumCm;
+ data['LeanBodyWeightLbs'] = this.leanBodyWeightLbs;
+ data['IdealBodyWeightLbs'] = this.idealBodyWeightLbs;
+ data['TemperatureCelciusMethod'] = this.temperatureCelciusMethod;
+ data['PulseRhythm'] = this.pulseRhythm;
+ data['RespirationPattern'] = this.respirationPattern;
+ data['BloodPressureCuffLocation'] = this.bloodPressureCuffLocation;
+ data['BloodPressureCuffSize'] = this.bloodPressureCuffSize;
+ data['BloodPressurePatientPosition'] = this.bloodPressurePatientPosition;
+ data['PainLocation'] = this.painLocation;
+ data['PainDuration'] = this.painDuration;
+ data['PainCharacter'] = this.painCharacter;
+ data['PainFrequency'] = this.painFrequency;
+ data['IsPainManagementDone'] = this.isPainManagementDone;
+ data['Status'] = this.status;
+ data['IsVitalsRequired'] = this.isVitalsRequired;
+ data['PatientID'] = this.patientID;
+ data['CreatedOn'] = this.createdOn;
+ data['DoctorID'] = this.doctorID;
+ data['ClinicID'] = this.clinicID;
+ data['TriageCategory'] = this.triageCategory;
+ data['GCScore'] = this.gCScore;
+ data['LineItemNo'] = this.lineItemNo;
+ data['VitalSignDate'] = this.vitalSignDate;
+ data['ActualTimeTaken'] = this.actualTimeTaken;
+ data['SugarLevel'] = this.sugarLevel;
+ data['FBS'] = this.fBS;
+ data['RBS'] = this.rBS;
+ data['ObservationType'] = this.observationType;
+ data['HeartRate'] = this.heartRate;
+ data['MuscleTone'] = this.muscleTone;
+ data['ReflexIrritability'] = this.reflexIrritability;
+ data['BodyColor'] = this.bodyColor;
+ data['IsFirstAssessment'] = this.isFirstAssessment;
+ data['DateofBirth'] = this.dateofBirth;
+ data['TimeOfBirth'] = this.timeOfBirth;
+ data['BloodPressure'] = this.bloodPressure;
+ data['BloodPressureCuffLocationDesc'] = this.bloodPressureCuffLocationDesc;
+ data['BloodPressureCuffSizeDesc'] = this.bloodPressureCuffSizeDesc;
+ data['BloodPressurePatientPositionDesc'] =
+ this.bloodPressurePatientPositionDesc;
+ data['ClinicName'] = this.clinicName;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ data['DoctorName'] = this.doctorName;
+ data['PainScoreDesc'] = this.painScoreDesc;
+ data['PulseRhythmDesc'] = this.pulseRhythmDesc;
+ data['RespirationPatternDesc'] = this.respirationPatternDesc;
+ data['TemperatureCelciusMethodDesc'] = this.temperatureCelciusMethodDesc;
+ data['Time'] = this.time;
+ return data;
+ }
+}
diff --git a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart
new file mode 100644
index 00000000..45b0ab65
--- /dev/null
+++ b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart
@@ -0,0 +1,102 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/theme/colors.dart';
+
+/// UI-only helper model for Vital Sign cards.
+///
+/// Keeps presentation logic (chip colors, icon colors, simple status rules)
+/// in one place so it can be reused across multiple pages.
+class VitalSignUiModel {
+ final Color iconBg;
+ final Color iconFg;
+ final Color chipBg;
+ final Color chipFg;
+
+ const VitalSignUiModel({
+ required this.iconBg,
+ required this.iconFg,
+ required this.chipBg,
+ required this.chipFg,
+ });
+
+ /// Returns a color scheme for a card based on its [status] and [label].
+ ///
+ /// Rules (mirrors existing behavior in Medical File page):
+ /// - Height is always blue.
+ /// - High => red scheme.
+ /// - Low => yellow scheme.
+ /// - Otherwise => green scheme (Normal).
+ static VitalSignUiModel scheme({required String? status, required String label}) {
+ final s = (status ?? '').toLowerCase();
+ final l = label.toLowerCase();
+
+ // Height should always be blue.
+ if (l.contains('height')) {
+ return VitalSignUiModel(
+ iconBg: AppColors.infoColor.withValues(alpha: 0.12),
+ iconFg: AppColors.infoColor,
+ chipBg: AppColors.infoColor.withValues(alpha: 0.12),
+ chipFg: AppColors.infoColor,
+ );
+ }
+
+ if (s.contains('high')) {
+ return const VitalSignUiModel(
+ iconBg: AppColors.chipSecondaryLightRedColor,
+ iconFg: AppColors.primaryRedColor,
+ chipBg: AppColors.chipSecondaryLightRedColor,
+ chipFg: AppColors.primaryRedColor,
+ );
+ }
+
+ if (s.contains('low')) {
+ final Color yellowBg = AppColors.warningColor.withValues(alpha: 0.12);
+ return VitalSignUiModel(
+ iconBg: yellowBg,
+ iconFg: AppColors.warningColor,
+ chipBg: yellowBg,
+ chipFg: AppColors.warningColor,
+ );
+ }
+
+ // Normal (green)
+ final Color greenBg = AppColors.lightGreenColor;
+ return VitalSignUiModel(
+ iconBg: greenBg,
+ iconFg: AppColors.bgGreenColor,
+ chipBg: greenBg,
+ chipFg: AppColors.bgGreenColor,
+ );
+ }
+
+ /// Simple, user-friendly classification:
+ /// - Low: systolic < 90 OR diastolic < 60
+ /// - High: systolic >= 140 OR diastolic >= 90
+ /// - Normal: otherwise
+ /// Returns null if values are missing/unparseable.
+ static String? bloodPressureStatus({dynamic systolic, dynamic diastolic}) {
+ final int? s = toIntOrNull(systolic);
+ final int? d = toIntOrNull(diastolic);
+ if (s == null || d == null) return null;
+
+ if (s < 90 || d < 60) return 'Low';
+ if (s >= 140 || d >= 90) return 'High';
+ return 'Normal';
+ }
+
+ static int? toIntOrNull(dynamic v) {
+ if (v == null) return null;
+ if (v is int) return v;
+ if (v is double) return v.round();
+ return int.tryParse(v.toString());
+ }
+
+ static String bmiStatus(dynamic bmi) {
+ if (bmi == null) return 'N/A';
+ final double bmiValue = double.tryParse(bmi.toString()) ?? 0;
+ if (bmiValue < 18.5) return 'Underweight';
+ if (bmiValue < 25) return 'Normal';
+ if (bmiValue < 30) return 'Overweight';
+ return 'High';
+ }
+}
+
diff --git a/lib/features/hospital/AppPermission.dart b/lib/features/hospital/AppPermission.dart
new file mode 100644
index 00000000..008f5719
--- /dev/null
+++ b/lib/features/hospital/AppPermission.dart
@@ -0,0 +1,27 @@
+import 'package:flutter/cupertino.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+
+class AppPermission {
+ static Future askVideoCallPermission(BuildContext context) async {
+ if (!(await Permission.camera.request().isGranted) || !(await Permission.microphone.request().isGranted)) {
+ return false;
+ }
+ // if (Platform.isAndroid && !(await PlatformBridge.shared().isDrawOverAppsPermissionAllowed())) {
+ // await _drawOverAppsMessageDialog(context);
+ // return false;
+ // }
+ return true;
+ }
+
+ static Future askPenguinPermissions() async {
+ if (!(await Permission.location.request().isGranted) ||
+ !(await Permission.bluetooth.request().isGranted) ||
+ !(await Permission.bluetoothScan.request().isGranted) ||
+ !(await Permission.bluetoothConnect.request().isGranted) ||
+ !(await Permission.activityRecognition.request().isGranted)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/lib/features/hospital/hospital_selection_view_model.dart b/lib/features/hospital/hospital_selection_view_model.dart
new file mode 100644
index 00000000..dd9531f2
--- /dev/null
+++ b/lib/features/hospital/hospital_selection_view_model.dart
@@ -0,0 +1,104 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/app_state.dart';
+import 'package:hmg_patient_app_new/core/utils/penguin_method_channel.dart';
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+class HospitalSelectionBottomSheetViewModel extends ChangeNotifier {
+ List displayList = [];
+ List listOfData = [];
+ List hmgHospitalList = [];
+ List hmcHospitalList = [];
+ FacilitySelection selectedFacility = FacilitySelection.ALL;
+ int hmcCount = 0;
+ int hmgCount = 0;
+ TextEditingController searchController = TextEditingController();
+ final AppState appState;
+
+ HospitalSelectionBottomSheetViewModel(this.appState) {
+ Utils.navigationProjectsList.forEach((element) {
+ HospitalsModel model = HospitalsModel.fromJson(element);
+ if (model.isHMC == true) {
+ hmcHospitalList.add(model);
+ } else {
+ hmgHospitalList.add(model);
+ }
+ listOfData.add(model);
+ });
+ hmgCount = hmgHospitalList.length;
+ hmcCount = hmcHospitalList.length;
+ getDisplayList();
+ }
+
+ getDisplayList() {
+ switch (selectedFacility) {
+ case FacilitySelection.ALL:
+ displayList = listOfData;
+ break;
+ case FacilitySelection.HMG:
+ displayList = hmgHospitalList;
+ break;
+ case FacilitySelection.HMC:
+ displayList = hmcHospitalList;
+ break;
+ }
+ notifyListeners();
+ }
+
+ searchHospitals(String query) {
+ if (query.isEmpty) {
+ getDisplayList();
+ return;
+ }
+ List sourceList = [];
+ switch (selectedFacility) {
+ case FacilitySelection.ALL:
+ sourceList = listOfData;
+ break;
+ case FacilitySelection.HMG:
+ sourceList = hmgHospitalList;
+ break;
+ case FacilitySelection.HMC:
+ sourceList = hmcHospitalList;
+ break;
+ }
+ displayList = sourceList.where((hospital) => hospital.name != null && hospital.name!.toLowerCase().contains(query.toLowerCase())).toList();
+ notifyListeners();
+ }
+
+ void clearSearchText() {
+ searchController.clear();
+ }
+
+ void setSelectedFacility(FacilitySelection value) {
+ selectedFacility = value;
+ getDisplayList();
+
+ }
+
+ void openPenguin(HospitalsModel hospital) {
+ initPenguinSDK(hospital.iD);
+ }
+
+ initPenguinSDK(int projectID) async {
+ NavigationClinicDetails data = NavigationClinicDetails();
+ data.projectId = projectID.toString();
+ final bool permited = await AppPermission.askPenguinPermissions();
+ if (!permited) {
+ Map statuses = await [
+ Permission.location,
+ Permission.bluetooth,
+ Permission.bluetoothConnect,
+ Permission.bluetoothScan,
+ Permission.activityRecognition,
+ ].request().whenComplete(() {
+ PenguinMethodChannel().launch("penguin", appState.isArabic() ? "ar" : "en", appState.getAuthenticatedUser()?.patientId?.toString()??"", details: data);
+ });
+ }
+ }
+
+
+}
diff --git a/lib/features/monthly_report/monthly_report_repo.dart b/lib/features/monthly_report/monthly_report_repo.dart
new file mode 100644
index 00000000..429700c4
--- /dev/null
+++ b/lib/features/monthly_report/monthly_report_repo.dart
@@ -0,0 +1,53 @@
+import 'package:dartz/dartz.dart';
+import 'package:hmg_patient_app_new/core/api/api_client.dart';
+import 'package:hmg_patient_app_new/core/api_consts.dart';
+import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
+import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
+import 'package:hmg_patient_app_new/services/logger_service.dart';
+
+abstract class MonthlyReportRepo {
+ Future>> updatePatientHealthSummaryReport({required bool rSummaryReport});
+}
+
+class MonthlyReportRepoImp implements MonthlyReportRepo {
+ final ApiClient apiClient;
+ final LoggerService loggerService;
+
+ MonthlyReportRepoImp({required this.loggerService, required this.apiClient});
+
+ @override
+ Future>> updatePatientHealthSummaryReport({required bool rSummaryReport}) async {
+ Map mapDevice = {
+ "RSummaryReport": rSummaryReport,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ UPDATE_HEALTH_TERMS,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: response,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+}
diff --git a/lib/features/monthly_report/monthly_report_view_model.dart b/lib/features/monthly_report/monthly_report_view_model.dart
new file mode 100644
index 00000000..348ca926
--- /dev/null
+++ b/lib/features/monthly_report/monthly_report_view_model.dart
@@ -0,0 +1,66 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
+import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+
+class MonthlyReportViewModel extends ChangeNotifier {
+ MonthlyReportRepo monthlyReportRepo;
+ ErrorHandlerService errorHandlerService;
+
+ bool isUpdateHealthSummaryLoading = false;
+ bool isHealthSummaryEnabled = false;
+
+ MonthlyReportViewModel({
+ required this.monthlyReportRepo,
+ required this.errorHandlerService,
+ });
+
+ setHealthSummaryEnabled(bool value) {
+ isHealthSummaryEnabled = value;
+ notifyListeners();
+ }
+
+ Future updatePatientHealthSummaryReport({
+ required bool rSummaryReport,
+ Function(dynamic)? onSuccess,
+ Function(String)? onError,
+ }) async {
+ isUpdateHealthSummaryLoading = true;
+ notifyListeners();
+
+ final result = await monthlyReportRepo.updatePatientHealthSummaryReport(
+ rSummaryReport: rSummaryReport,
+ );
+
+ result.fold(
+ (failure) async {
+ isUpdateHealthSummaryLoading = false;
+ notifyListeners();
+ await errorHandlerService.handleError(failure: failure);
+ if (onError != null) {
+ onError(failure.toString());
+ }
+ },
+ (apiResponse) {
+ isUpdateHealthSummaryLoading = false;
+ if (apiResponse.messageStatus == 2) {
+ notifyListeners();
+ if (onError != null) {
+ onError(apiResponse.errorMessage ?? "Unknown error");
+ }
+ } else if (apiResponse.messageStatus == 1) {
+ // Update the local state on success
+ isHealthSummaryEnabled = rSummaryReport;
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ }
+}
diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart
index f51f7017..c5dcaf62 100644
--- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart
+++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart
@@ -1,6 +1,10 @@
import 'package:flutter/foundation.dart' show ChangeNotifier;
+import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart' show AppState;
+import 'package:hmg_patient_app_new/core/utils/utils.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
+import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/laser/laser_appointment.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
@@ -30,7 +34,14 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
AppointmentViaRegionState bottomSheetState =
AppointmentViaRegionState.REGION_SELECTION;
final AppState appState;
-
+ TextEditingController searchController = TextEditingController();
+ List? hospitalList;
+ List? hmgHospitalList;
+ List? hmcHospitalList;
+ List? displayList;
+ FacilitySelection selectedFacility = FacilitySelection.ALL;
+ int hmgCount = 0;
+ int hmcCount = 0;
RegionBottomSheetType regionBottomSheetType = RegionBottomSheetType.FOR_REGION;
AppointmentViaRegionViewmodel({required this.navigationService,required this.appState});
@@ -40,6 +51,35 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
notifyListeners();
}
+ void setDisplayListAndRegionHospitalList(PatientDoctorAppointmentListByRegion? registeredDoctorMap){
+ if(registeredDoctorMap == null) {
+ return;
+ }
+ selectedFacility = FacilitySelection.ALL;
+ hmcHospitalList = [];
+ hmgHospitalList = [];
+ hospitalList = [];
+ displayList = [];
+ for(var data in registeredDoctorMap.hmgDoctorList!){
+ hmgHospitalList?.add(data);
+
+ }
+
+ for(var data in registeredDoctorMap.hmcDoctorList!){
+ hmcHospitalList?.add(data);
+
+ }
+
+ hospitalList!.addAll(hmgHospitalList!);
+ hospitalList!.addAll(hmcHospitalList!);
+
+ hmcCount = registeredDoctorMap.hmcSize;
+ hmgCount = registeredDoctorMap.hmgSize;
+
+ getDisplayList();
+
+ }
+
void setFacility(String? facility) {
selectedFacilityType = facility;
notifyListeners();
@@ -71,7 +111,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
setSelectedRegionId(null);
break;
case AppointmentViaRegionState.HOSPITAL_SELECTION:
- setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
+ setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
break;
default:
}
@@ -129,4 +169,48 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
),
);
}
+
+ searchHospitals(String query) {
+ if (query.isEmpty) {
+ getDisplayList();
+ return;
+ }
+ List? sourceList;
+ switch (selectedFacility) {
+ case FacilitySelection.ALL:
+ sourceList = hospitalList;
+ break;
+ case FacilitySelection.HMG:
+ sourceList = hmgHospitalList;
+ break;
+ case FacilitySelection.HMC:
+ sourceList = hmcHospitalList;
+ break;
+ }
+ displayList = sourceList?.where((hospital) => hospital.filterName != null && hospital.filterName!.toLowerCase().contains(query.toLowerCase())).toList();
+ notifyListeners();
+ }
+
+ getDisplayList() {
+ switch (selectedFacility) {
+ case FacilitySelection.ALL:
+ displayList = hospitalList;
+ break;
+ case FacilitySelection.HMG:
+ displayList = hmgHospitalList;
+ break;
+ case FacilitySelection.HMC:
+ displayList = hmcHospitalList;
+ break;
+ }
+ notifyListeners();
+ }
+
+
+ setSelectedFacility(FacilitySelection selection) {
+ selectedFacility = selection;
+ notifyListeners();
+ }
+
+
}
diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart
index b4a25eec..9bd48ec9 100644
--- a/lib/features/my_appointments/my_appointments_view_model.dart
+++ b/lib/features/my_appointments/my_appointments_view_model.dart
@@ -127,6 +127,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
initAppointmentsViewModel() {
if (isAppointmentDataToBeLoaded) {
+ // Default view is grouped by clinic on first open.
+ isAppointmentsSortByClinic = true;
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
@@ -269,6 +271,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
+ // Build grouped list immediately so the UI has data for the default (By Clinic) view.
+ _groupAppointmentsByClinicAndHospital();
+
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
if (Utils.isDateToday(DateUtil.convertStringToDate(patientArrivedAppointmentsHistoryList.first.appointmentDate))) {
// getPatientAppointmentQueueDetails(appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
@@ -280,6 +285,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
+ notifyListeners();
}
void getFiltersForSelectedAppointmentList(List filteredAppointmentList) {
diff --git a/lib/features/my_invoices/models/get_invoice_details_response_model.dart b/lib/features/my_invoices/models/get_invoice_details_response_model.dart
new file mode 100644
index 00000000..ef88623d
--- /dev/null
+++ b/lib/features/my_invoices/models/get_invoice_details_response_model.dart
@@ -0,0 +1,489 @@
+class GetInvoiceDetailsResponseModel {
+ int? projectID;
+ int? doctorID;
+ num? grandTotal;
+ num? quantity;
+ num? total;
+ num? discount;
+ num? subTotal;
+ int? invoiceNo;
+ String? createdOn;
+ String? procedureID;
+ String? procedureName;
+ String? procedureNameN;
+ num? procedurePrice;
+ num? patientShare;
+ num? companyShare;
+ num? totalPatientShare;
+ num? totalCompanyShare;
+ num? totalShare;
+ num? discountAmount;
+ num? vATPercentage;
+ num? patientVATAmount;
+ num? companyVATAmount;
+ num? totalVATAmount;
+ num? price;
+ int? patientID;
+ String? patientIdentificationNo;
+ String? patientName;
+ String? patientNameN;
+ String? nationalityID;
+ String? doctorName;
+ String? doctorNameN;
+ int? clinicID;
+ String? clinicDescription;
+ String? clinicDescriptionN;
+ String? appointmentDate;
+ int? appointmentNo;
+ String? insuranceID;
+ int? companyID;
+ String? companyName;
+ String? companyNameN;
+ String? companyAddress;
+ String? companyAddressN;
+ String? companyGroupAddress;
+ String? groupName;
+ String? groupNameN;
+ String? patientAddress;
+ String? vATNo;
+ String? paymentDate;
+ String? projectName;
+ num? totalDiscount;
+ num? totalPatientShareWithQuantity;
+ String? legalName;
+ String? legalNameN;
+ num? advanceAdjustment;
+ String? patientCityName;
+ String? patientCityNameN;
+ String? doctorImageURL;
+ List? listConsultation;
+
+ GetInvoiceDetailsResponseModel(
+ {this.projectID,
+ this.doctorID,
+ this.grandTotal,
+ this.quantity,
+ this.total,
+ this.discount,
+ this.subTotal,
+ this.invoiceNo,
+ this.createdOn,
+ this.procedureID,
+ this.procedureName,
+ this.procedureNameN,
+ this.procedurePrice,
+ this.patientShare,
+ this.companyShare,
+ this.totalPatientShare,
+ this.totalCompanyShare,
+ this.totalShare,
+ this.discountAmount,
+ this.vATPercentage,
+ this.patientVATAmount,
+ this.companyVATAmount,
+ this.totalVATAmount,
+ this.price,
+ this.patientID,
+ this.patientIdentificationNo,
+ this.patientName,
+ this.patientNameN,
+ this.nationalityID,
+ this.doctorName,
+ this.doctorNameN,
+ this.clinicID,
+ this.clinicDescription,
+ this.clinicDescriptionN,
+ this.appointmentDate,
+ this.appointmentNo,
+ this.insuranceID,
+ this.companyID,
+ this.companyName,
+ this.companyNameN,
+ this.companyAddress,
+ this.companyAddressN,
+ this.companyGroupAddress,
+ this.groupName,
+ this.groupNameN,
+ this.patientAddress,
+ this.vATNo,
+ this.paymentDate,
+ this.projectName,
+ this.totalDiscount,
+ this.totalPatientShareWithQuantity,
+ this.legalName,
+ this.legalNameN,
+ this.advanceAdjustment,
+ this.patientCityName,
+ this.patientCityNameN,
+ this.doctorImageURL,
+ this.listConsultation});
+
+ GetInvoiceDetailsResponseModel.fromJson(Map json) {
+ projectID = json['ProjectID'];
+ doctorID = json['DoctorID'];
+ grandTotal = json['GrandTotal'];
+ quantity = json['Quantity'];
+ total = json['Total'];
+ discount = json['Discount'];
+ subTotal = json['SubTotal'];
+ invoiceNo = json['InvoiceNo'];
+ createdOn = json['CreatedOn'];
+ procedureID = json['ProcedureID'];
+ procedureName = json['ProcedureName'];
+ procedureNameN = json['ProcedureNameN'];
+ procedurePrice = json['ProcedurePrice'];
+ patientShare = json['PatientShare'];
+ companyShare = json['CompanyShare'];
+ totalPatientShare = json['TotalPatientShare'];
+ totalCompanyShare = json['TotalCompanyShare'];
+ totalShare = json['TotalShare'];
+ discountAmount = json['DiscountAmount'];
+ vATPercentage = json['VATPercentage'];
+ patientVATAmount = json['PatientVATAmount'];
+ companyVATAmount = json['CompanyVATAmount'];
+ totalVATAmount = json['TotalVATAmount'];
+ price = json['Price'];
+ patientID = json['PatientID'];
+ patientIdentificationNo = json['PatientIdentificationNo'];
+ patientName = json['PatientName'];
+ patientNameN = json['PatientNameN'];
+ nationalityID = json['NationalityID'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ clinicID = json['ClinicID'];
+ clinicDescription = json['ClinicDescription'];
+ clinicDescriptionN = json['ClinicDescriptionN'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentNo = json['AppointmentNo'];
+ insuranceID = json['InsuranceID'];
+ companyID = json['CompanyID'];
+ companyName = json['CompanyName'];
+ companyNameN = json['CompanyNameN'];
+ companyAddress = json['CompanyAddress'];
+ companyAddressN = json['CompanyAddressN'];
+ companyGroupAddress = json['CompanyGroupAddress'];
+ groupName = json['GroupName'];
+ groupNameN = json['GroupNameN'];
+ patientAddress = json['PatientAddress'];
+ vATNo = json['VATNo'];
+ paymentDate = json['PaymentDate'];
+ projectName = json['ProjectName'];
+ totalDiscount = json['TotalDiscount'];
+ totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
+ legalName = json['LegalName'];
+ legalNameN = json['LegalNameN'];
+ advanceAdjustment = json['AdvanceAdjustment'];
+ patientCityName = json['PatientCityName'];
+ patientCityNameN = json['PatientCityNameN'];
+ doctorImageURL = json['DoctorImageURL'];
+ if (json['listConsultation'] != null) {
+ listConsultation = [];
+ json['listConsultation'].forEach((v) {
+ listConsultation!.add(new ListConsultation.fromJson(v));
+ });
+ }
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['ProjectID'] = this.projectID;
+ data['DoctorID'] = this.doctorID;
+ data['GrandTotal'] = this.grandTotal;
+ data['Quantity'] = this.quantity;
+ data['Total'] = this.total;
+ data['Discount'] = this.discount;
+ data['SubTotal'] = this.subTotal;
+ data['InvoiceNo'] = this.invoiceNo;
+ data['CreatedOn'] = this.createdOn;
+ data['ProcedureID'] = this.procedureID;
+ data['ProcedureName'] = this.procedureName;
+ data['ProcedureNameN'] = this.procedureNameN;
+ data['ProcedurePrice'] = this.procedurePrice;
+ data['PatientShare'] = this.patientShare;
+ data['CompanyShare'] = this.companyShare;
+ data['TotalPatientShare'] = this.totalPatientShare;
+ data['TotalCompanyShare'] = this.totalCompanyShare;
+ data['TotalShare'] = this.totalShare;
+ data['DiscountAmount'] = this.discountAmount;
+ data['VATPercentage'] = this.vATPercentage;
+ data['PatientVATAmount'] = this.patientVATAmount;
+ data['CompanyVATAmount'] = this.companyVATAmount;
+ data['TotalVATAmount'] = this.totalVATAmount;
+ data['Price'] = this.price;
+ data['PatientID'] = this.patientID;
+ data['PatientIdentificationNo'] = this.patientIdentificationNo;
+ data['PatientName'] = this.patientName;
+ data['PatientNameN'] = this.patientNameN;
+ data['NationalityID'] = this.nationalityID;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['ClinicID'] = this.clinicID;
+ data['ClinicDescription'] = this.clinicDescription;
+ data['ClinicDescriptionN'] = this.clinicDescriptionN;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['InsuranceID'] = this.insuranceID;
+ data['CompanyID'] = this.companyID;
+ data['CompanyName'] = this.companyName;
+ data['CompanyNameN'] = this.companyNameN;
+ data['CompanyAddress'] = this.companyAddress;
+ data['CompanyAddressN'] = this.companyAddressN;
+ data['CompanyGroupAddress'] = this.companyGroupAddress;
+ data['GroupName'] = this.groupName;
+ data['GroupNameN'] = this.groupNameN;
+ data['PatientAddress'] = this.patientAddress;
+ data['VATNo'] = this.vATNo;
+ data['PaymentDate'] = this.paymentDate;
+ data['ProjectName'] = this.projectName;
+ data['TotalDiscount'] = this.totalDiscount;
+ data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
+ data['LegalName'] = this.legalName;
+ data['LegalNameN'] = this.legalNameN;
+ data['AdvanceAdjustment'] = this.advanceAdjustment;
+ data['PatientCityName'] = this.patientCityName;
+ data['PatientCityNameN'] = this.patientCityNameN;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ if (this.listConsultation != null) {
+ data['listConsultation'] =
+ this.listConsultation!.map((v) => v.toJson()).toList();
+ }
+ return data;
+ }
+}
+
+class ListConsultation {
+ int? projectID;
+ int? doctorID;
+ num? grandTotal;
+ int? quantity;
+ num? total;
+ num? discount;
+ num? subTotal;
+ int? invoiceNo;
+ String? createdOn;
+ String? procedureID;
+ String? procedureName;
+ String? procedureNameN;
+ num? procedurePrice;
+ num? patientShare;
+ num? companyShare;
+ num? totalPatientShare;
+ num? totalCompanyShare;
+ num? totalShare;
+ num? discountAmount;
+ num? vATPercentage;
+ num? patientVATAmount;
+ num? companyVATAmount;
+ num? totalVATAmount;
+ num? price;
+ int? patientID;
+ int? patientIdentificationNo;
+ String? patientName;
+ String? patientNameN;
+ String? nationalityID;
+ String? doctorName;
+ String? doctorNameN;
+ int? clinicID;
+ String? clinicDescription;
+ String? clinicDescriptionN;
+ String? appointmentDate;
+ dynamic appointmentNo;
+ dynamic insuranceID;
+ dynamic companyID;
+ String? companyName;
+ String? companyNameN;
+ String? companyAddress;
+ String? companyAddressN;
+ String? companyGroupAddress;
+ String? groupName;
+ String? groupNameN;
+ String? patientAddress;
+ String? vATNo;
+ String? paymentDate;
+ String? projectName;
+ num? totalDiscount;
+ num? totalPatientShareWithQuantity;
+ String? legalName;
+ String? legalNameN;
+ num? advanceAdjustment;
+ String? patientCityName;
+ String? patientCityNameN;
+
+ ListConsultation(
+ {this.projectID,
+ this.doctorID,
+ this.grandTotal,
+ this.quantity,
+ this.total,
+ this.discount,
+ this.subTotal,
+ this.invoiceNo,
+ this.createdOn,
+ this.procedureID,
+ this.procedureName,
+ this.procedureNameN,
+ this.procedurePrice,
+ this.patientShare,
+ this.companyShare,
+ this.totalPatientShare,
+ this.totalCompanyShare,
+ this.totalShare,
+ this.discountAmount,
+ this.vATPercentage,
+ this.patientVATAmount,
+ this.companyVATAmount,
+ this.totalVATAmount,
+ this.price,
+ this.patientID,
+ this.patientIdentificationNo,
+ this.patientName,
+ this.patientNameN,
+ this.nationalityID,
+ this.doctorName,
+ this.doctorNameN,
+ this.clinicID,
+ this.clinicDescription,
+ this.clinicDescriptionN,
+ this.appointmentDate,
+ this.appointmentNo,
+ this.insuranceID,
+ this.companyID,
+ this.companyName,
+ this.companyNameN,
+ this.companyAddress,
+ this.companyAddressN,
+ this.companyGroupAddress,
+ this.groupName,
+ this.groupNameN,
+ this.patientAddress,
+ this.vATNo,
+ this.paymentDate,
+ this.projectName,
+ this.totalDiscount,
+ this.totalPatientShareWithQuantity,
+ this.legalName,
+ this.legalNameN,
+ this.advanceAdjustment,
+ this.patientCityName,
+ this.patientCityNameN});
+
+ ListConsultation.fromJson(Map json) {
+ projectID = json['ProjectID'];
+ doctorID = json['DoctorID'];
+ grandTotal = json['GrandTotal'];
+ quantity = json['Quantity'];
+ total = json['Total'];
+ discount = json['Discount'];
+ subTotal = json['SubTotal'];
+ invoiceNo = json['InvoiceNo'];
+ createdOn = json['CreatedOn'];
+ procedureID = json['ProcedureID'];
+ procedureName = json['ProcedureName'];
+ procedureNameN = json['ProcedureNameN'];
+ procedurePrice = json['ProcedurePrice'];
+ patientShare = json['PatientShare'];
+ companyShare = json['CompanyShare'];
+ totalPatientShare = json['TotalPatientShare'];
+ totalCompanyShare = json['TotalCompanyShare'];
+ totalShare = json['TotalShare'];
+ discountAmount = json['DiscountAmount'];
+ vATPercentage = json['VATPercentage'];
+ patientVATAmount = json['PatientVATAmount'];
+ companyVATAmount = json['CompanyVATAmount'];
+ totalVATAmount = json['TotalVATAmount'];
+ price = json['Price'];
+ patientID = json['PatientID'];
+ patientIdentificationNo = json['PatientIdentificationNo'];
+ patientName = json['PatientName'];
+ patientNameN = json['PatientNameN'];
+ nationalityID = json['NationalityID'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ clinicID = json['ClinicID'];
+ clinicDescription = json['ClinicDescription'];
+ clinicDescriptionN = json['ClinicDescriptionN'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentNo = json['AppointmentNo'];
+ insuranceID = json['InsuranceID'];
+ companyID = json['CompanyID'];
+ companyName = json['CompanyName'];
+ companyNameN = json['CompanyNameN'];
+ companyAddress = json['CompanyAddress'];
+ companyAddressN = json['CompanyAddressN'];
+ companyGroupAddress = json['CompanyGroupAddress'];
+ groupName = json['GroupName'];
+ groupNameN = json['GroupNameN'];
+ patientAddress = json['PatientAddress'];
+ vATNo = json['VATNo'];
+ paymentDate = json['PaymentDate'];
+ projectName = json['ProjectName'];
+ totalDiscount = json['TotalDiscount'];
+ totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
+ legalName = json['LegalName'];
+ legalNameN = json['LegalNameN'];
+ advanceAdjustment = json['AdvanceAdjustment'];
+ patientCityName = json['PatientCityName'];
+ patientCityNameN = json['PatientCityNameN'];
+ }
+
+ Map toJson() {
+ final Map data = new Map();
+ data['ProjectID'] = this.projectID;
+ data['DoctorID'] = this.doctorID;
+ data['GrandTotal'] = this.grandTotal;
+ data['Quantity'] = this.quantity;
+ data['Total'] = this.total;
+ data['Discount'] = this.discount;
+ data['SubTotal'] = this.subTotal;
+ data['InvoiceNo'] = this.invoiceNo;
+ data['CreatedOn'] = this.createdOn;
+ data['ProcedureID'] = this.procedureID;
+ data['ProcedureName'] = this.procedureName;
+ data['ProcedureNameN'] = this.procedureNameN;
+ data['ProcedurePrice'] = this.procedurePrice;
+ data['PatientShare'] = this.patientShare;
+ data['CompanyShare'] = this.companyShare;
+ data['TotalPatientShare'] = this.totalPatientShare;
+ data['TotalCompanyShare'] = this.totalCompanyShare;
+ data['TotalShare'] = this.totalShare;
+ data['DiscountAmount'] = this.discountAmount;
+ data['VATPercentage'] = this.vATPercentage;
+ data['PatientVATAmount'] = this.patientVATAmount;
+ data['CompanyVATAmount'] = this.companyVATAmount;
+ data['TotalVATAmount'] = this.totalVATAmount;
+ data['Price'] = this.price;
+ data['PatientID'] = this.patientID;
+ data['PatientIdentificationNo'] = this.patientIdentificationNo;
+ data['PatientName'] = this.patientName;
+ data['PatientNameN'] = this.patientNameN;
+ data['NationalityID'] = this.nationalityID;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['ClinicID'] = this.clinicID;
+ data['ClinicDescription'] = this.clinicDescription;
+ data['ClinicDescriptionN'] = this.clinicDescriptionN;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['InsuranceID'] = this.insuranceID;
+ data['CompanyID'] = this.companyID;
+ data['CompanyName'] = this.companyName;
+ data['CompanyNameN'] = this.companyNameN;
+ data['CompanyAddress'] = this.companyAddress;
+ data['CompanyAddressN'] = this.companyAddressN;
+ data['CompanyGroupAddress'] = this.companyGroupAddress;
+ data['GroupName'] = this.groupName;
+ data['GroupNameN'] = this.groupNameN;
+ data['PatientAddress'] = this.patientAddress;
+ data['VATNo'] = this.vATNo;
+ data['PaymentDate'] = this.paymentDate;
+ data['ProjectName'] = this.projectName;
+ data['TotalDiscount'] = this.totalDiscount;
+ data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
+ data['LegalName'] = this.legalName;
+ data['LegalNameN'] = this.legalNameN;
+ data['AdvanceAdjustment'] = this.advanceAdjustment;
+ data['PatientCityName'] = this.patientCityName;
+ data['PatientCityNameN'] = this.patientCityNameN;
+ return data;
+ }
+}
diff --git a/lib/features/my_invoices/models/get_invoices_list_response_model.dart b/lib/features/my_invoices/models/get_invoices_list_response_model.dart
new file mode 100644
index 00000000..e8056d91
--- /dev/null
+++ b/lib/features/my_invoices/models/get_invoices_list_response_model.dart
@@ -0,0 +1,88 @@
+class GetInvoicesListResponseModel {
+ String? setupId;
+ int? projectID;
+ int? patientID;
+ int? appointmentNo;
+ String? appointmentDate;
+ String? appointmentDateN;
+ int? clinicID;
+ int? doctorID;
+ int? invoiceNo;
+ int? status;
+ String? arrivedOn;
+ String? doctorName;
+ String? doctorNameN;
+ String? clinicName;
+ double? decimalDoctorRate;
+ String? doctorImageURL;
+ int? doctorRate;
+ int? patientNumber;
+ String? projectName;
+
+ GetInvoicesListResponseModel(
+ {this.setupId,
+ this.projectID,
+ this.patientID,
+ this.appointmentNo,
+ this.appointmentDate,
+ this.appointmentDateN,
+ this.clinicID,
+ this.doctorID,
+ this.invoiceNo,
+ this.status,
+ this.arrivedOn,
+ this.doctorName,
+ this.doctorNameN,
+ this.clinicName,
+ this.decimalDoctorRate,
+ this.doctorImageURL,
+ this.doctorRate,
+ this.patientNumber,
+ this.projectName});
+
+ GetInvoicesListResponseModel.fromJson(Map json) {
+ setupId = json['SetupId'];
+ projectID = json['ProjectID'];
+ patientID = json['PatientID'];
+ appointmentNo = json['AppointmentNo'];
+ appointmentDate = json['AppointmentDate'];
+ appointmentDateN = json['AppointmentDateN'];
+ clinicID = json['ClinicID'];
+ doctorID = json['DoctorID'];
+ invoiceNo = json['InvoiceNo'];
+ status = json['Status'];
+ arrivedOn = json['ArrivedOn'];
+ doctorName = json['DoctorName'];
+ doctorNameN = json['DoctorNameN'];
+ clinicName = json['ClinicName'];
+ decimalDoctorRate = json['DecimalDoctorRate'];
+ doctorImageURL = json['DoctorImageURL'];
+ doctorRate = json['DoctorRate'];
+ patientNumber = json['PatientNumber'];
+ projectName = json['ProjectName'];
+ }
+
+ Map toJson() {
+ final Map data = {};
+ data['SetupId'] = this.setupId;
+ data['ProjectID'] = this.projectID;
+ data['PatientID'] = this.patientID;
+ data['AppointmentNo'] = this.appointmentNo;
+ data['AppointmentDate'] = this.appointmentDate;
+ data['AppointmentDateN'] = this.appointmentDateN;
+ data['ClinicID'] = this.clinicID;
+ data['DoctorID'] = this.doctorID;
+ data['InvoiceNo'] = this.invoiceNo;
+ data['Status'] = this.status;
+ data['ArrivedOn'] = this.arrivedOn;
+ data['DoctorName'] = this.doctorName;
+ data['DoctorNameN'] = this.doctorNameN;
+ data['ClinicName'] = this.clinicName;
+ data['DecimalDoctorRate'] = this.decimalDoctorRate;
+ data['DoctorImageURL'] = this.doctorImageURL;
+ data['DoctorRate'] = this.doctorRate;
+ data['PatientNumber'] = this.patientNumber;
+ data['ProjectName'] = this.projectName;
+ return data;
+ }
+}
diff --git a/lib/features/my_invoices/my_invoices_repo.dart b/lib/features/my_invoices/my_invoices_repo.dart
new file mode 100644
index 00000000..68eee6e5
--- /dev/null
+++ b/lib/features/my_invoices/my_invoices_repo.dart
@@ -0,0 +1,141 @@
+import 'package:dartz/dartz.dart';
+import 'package:hmg_patient_app_new/core/api/api_client.dart';
+import 'package:hmg_patient_app_new/core/api_consts.dart';
+import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
+import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
+import 'package:hmg_patient_app_new/services/logger_service.dart';
+
+abstract class MyInvoicesRepo {
+ Future>>> getAllInvoicesList();
+
+ Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID});
+
+ Future>> sendInvoiceEmail({required num appointmentNo, required int projectID});
+}
+
+class MyInvoicesRepoImp implements MyInvoicesRepo {
+ final ApiClient apiClient;
+ final LoggerService loggerService;
+
+ MyInvoicesRepoImp({required this.loggerService, required this.apiClient});
+
+ @override
+ Future>>> getAllInvoicesList() async {
+ Map mapDevice = {};
+
+ try {
+ GenericApiModel>? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final list = response['List_DentalAppointments'];
+
+ final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map)).toList().cast();
+
+ apiResponse = GenericApiModel>(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: invoicesList,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async {
+ Map mapDevice = {
+ "AppointmentNo": appointmentNo,
+ "InvoiceNo": invoiceNo,
+ "IsRegistered": true,
+ "ProjectID": projectID,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ GET_DENTAL_APPOINTMENT_INVOICE,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ final list = response['List_eInvoiceForDental'];
+ final invoicesList = GetInvoiceDetailsResponseModel.fromJson(list[0]);
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: invoicesList,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future> sendInvoiceEmail({required num appointmentNo, required int projectID}) async {
+ Map mapDevice = {
+ "AppointmentNo": appointmentNo,
+ "IsRegistered": true,
+ "ProjectID": projectID,
+ };
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+ await apiClient.post(
+ SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL,
+ body: mapDevice,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ failure = failureType;
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus,
+ statusCode: statusCode,
+ errorMessage: null,
+ data: response,
+ );
+ } catch (e) {
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e) {
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+}
diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart
new file mode 100644
index 00000000..a02d741d
--- /dev/null
+++ b/lib/features/my_invoices/my_invoices_view_model.dart
@@ -0,0 +1,99 @@
+import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
+import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
+import 'package:hmg_patient_app_new/services/error_handler_service.dart';
+import 'package:hmg_patient_app_new/services/navigation_service.dart';
+
+class MyInvoicesViewModel extends ChangeNotifier {
+ bool isInvoicesListLoading = false;
+ bool isInvoiceDetailsLoading = false;
+
+ MyInvoicesRepo myInvoicesRepo;
+ ErrorHandlerService errorHandlerService;
+ NavigationService navServices;
+
+ List allInvoicesList = [];
+ late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel;
+
+ MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices});
+
+ setInvoicesListLoading() {
+ isInvoicesListLoading = true;
+ allInvoicesList.clear();
+ notifyListeners();
+ }
+
+ setInvoiceDetailLoading() {
+ isInvoiceDetailsLoading = true;
+ notifyListeners();
+ }
+
+ Future getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myInvoicesRepo.getAllInvoicesList();
+
+ result.fold(
+ (failure) async {
+ isInvoicesListLoading = false;
+ notifyListeners();
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ allInvoicesList = apiResponse.data!;
+ isInvoicesListLoading = false;
+
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ Future getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID);
+
+ result.fold(
+ (failure) async {
+ isInvoiceDetailsLoading = false;
+ notifyListeners();
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ invoiceDetailsResponseModel = apiResponse.data!;
+ isInvoiceDetailsLoading = false;
+
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+
+ Future sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
+ final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID);
+
+ result.fold(
+ (failure) async {
+ notifyListeners();
+ },
+ (apiResponse) {
+ if (apiResponse.messageStatus == 2) {
+ // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
+ } else if (apiResponse.messageStatus == 1) {
+ notifyListeners();
+ if (onSuccess != null) {
+ onSuccess(apiResponse);
+ }
+ }
+ },
+ );
+ }
+}
diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart
index e7a4f078..c6e7150b 100644
--- a/lib/features/prescriptions/prescriptions_repo.dart
+++ b/lib/features/prescriptions/prescriptions_repo.dart
@@ -182,6 +182,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo {
"To": Utils.appState.getAuthenticatedUser()!.emailAddress,
"SetupID": prescriptionsResponseModel.setupID,
"IsDownload": true,
+ "isDentalAllowedBackend": false,
};
try {
diff --git a/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart b/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart
new file mode 100644
index 00000000..b2be4a27
--- /dev/null
+++ b/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart
@@ -0,0 +1,59 @@
+class RiskAndSuggestionsResponseModel {
+ final List? dataDetails;
+
+ RiskAndSuggestionsResponseModel({this.dataDetails});
+
+ factory RiskAndSuggestionsResponseModel.fromJson(Map json) {
+ return RiskAndSuggestionsResponseModel(
+ dataDetails:
+ json['dataDetails'] != null ? (json['dataDetails'] as List).map((item) => RiskAndSuggestionsItemModel.fromJson(item)).toList() : null,
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'dataDetails': dataDetails?.map((item) => item.toJson()).toList(),
+ };
+ }
+}
+
+class RiskAndSuggestionsItemModel {
+ final String? id;
+ final String? type;
+ final String? name;
+ final String? commonName;
+ final String? language;
+
+ RiskAndSuggestionsItemModel({
+ this.id,
+ this.type,
+ this.name,
+ this.commonName,
+ this.language,
+ });
+
+ factory RiskAndSuggestionsItemModel.fromJson(Map json) {
+ return RiskAndSuggestionsItemModel(
+ id: json['id'],
+ type: json['type'],
+ name: json['name'],
+ commonName: json['common_name'],
+ language: json['language'],
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'id': id,
+ 'type': type,
+ 'name': name,
+ 'common_name': commonName,
+ 'language': language,
+ };
+ }
+
+ // Helper method to get display name
+ String getDisplayName() {
+ return commonName ?? name ?? '';
+ }
+}
diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart
index 53792071..954d414a 100644
--- a/lib/features/symptoms_checker/symptoms_checker_repo.dart
+++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart
@@ -7,77 +7,214 @@ import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
+import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
-import 'package:http/http.dart' as http;
abstract class SymptomsCheckerRepo {
Future>> getBodySymptomsByName({
required List organNames,
});
+
+ Future>> getRiskFactors({
+ required int age,
+ required String sex,
+ required List evidenceIds,
+ required String language,
+ });
+
+ Future>> getSuggestions({
+ required int age,
+ required String sex,
+ required List evidenceIds,
+ required String language,
+ });
}
class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
final ApiClient apiClient;
final LoggerService loggerService;
- SymptomsCheckerRepoImp({
- required this.apiClient,
- required this.loggerService,
- });
+ SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService});
@override
- Future>> getBodySymptomsByName({
- required List organNames,
+ Future>> getBodySymptomsByName({required List organNames}) async {
+ log("GetBodySymptomsByName Request URL: ${ApiConsts.getBodySymptomsByName}");
+ log("GetBodySymptomsByName Request Body: ${jsonEncode(organNames)}");
+
+ Map headers = {'Content-Type': 'application/json', 'Accept': 'text/plain'};
+
+ try {
+ GenericApiModel? apiResponse;
+ Failure? failure;
+
+ await apiClient.post(
+ ApiConsts.getBodySymptomsByName,
+ apiHeaders: headers,
+ body: jsonEncode(organNames),
+ isExternal: true,
+ isAllowAny: true,
+ isBodyPlainText: true,
+ onFailure: (error, statusCode, {messageStatus, failureType}) {
+ loggerService.logError("GetBodySymptomsByName API Failed: $error");
+ log("GetBodySymptomsByName Failed: $error, Status: $statusCode");
+ failure = failureType ?? ServerFailure(error);
+ },
+ onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
+ try {
+ log("GetBodySymptomsByName Response Status: $statusCode");
+ loggerService.logInfo("GetBodySymptomsByName API Success: $response");
+ log("GetBodySymptomsByName Response: $response");
+
+ BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response);
+
+ apiResponse = GenericApiModel(
+ messageStatus: messageStatus ?? 1,
+ statusCode: statusCode,
+ errorMessage: errorMessage,
+ data: bodySymptomResponse,
+ );
+ } catch (e, stackTrace) {
+ loggerService.logError("Error parsing GetBodySymptomsByName response: $e");
+ loggerService.logError("StackTrace: $stackTrace");
+ log("Parse Error: $e");
+ failure = DataParsingFailure(e.toString());
+ }
+ },
+ );
+
+ if (failure != null) return Left(failure!);
+ if (apiResponse == null) return Left(ServerFailure("Unknown error"));
+ return Right(apiResponse!);
+ } catch (e, stackTrace) {
+ loggerService.logError("Exception in getBodySymptomsByName: $e");
+ loggerService.logError("StackTrace: $stackTrace");
+ log("Exception: $e");
+ return Left(UnknownFailure(e.toString()));
+ }
+ }
+
+ @override
+ Future>> getRiskFactors({
+ required int age,
+ required String sex,
+ required List evidenceIds,
+ required String language,
}) async {
+ final Map body = {
+ "age": {
+ "value": age,
+ },
+ "sex": sex,
+ "evidence": evidenceIds.map((id) => {"id": id}).toList(),
+ "language": language,
+ };
+
try {
- // API expects a direct JSON array: ["mid_abdomen", "chest"]
- // Not an object like: {"organNames": [...]}
- // Since ApiClient.post expects Map