diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 0ffb97d..2987d3b 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 d423bc1..7832df8 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 5c789c6..a769c7a 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 b657ac6..2926e9a 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 4f7ef74..8c1388b 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 0000000..4df25bc
--- /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 0000000..d012799
--- /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 0000000..5bc332d
--- /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 0000000..adde120
--- /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 0000000..5b9f19e
--- /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 0000000..9856a49
--- /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 0000000..d8aea7b
--- /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 0000000..c07d1de
--- /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 0000000..18463d2
--- /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 0000000..b822d67
--- /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 0000000..6c7306d
--- /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 f1daf69..65bc4b3 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 328e8fc..2d10333 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 866b270..9b679bc 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 3e6502f..6d0842d 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/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 0000000..fa0b357
--- /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 0000000..4f91d05
--- /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 0000000..b487f03
--- /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 0000000..de67f9b
--- /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 0000000..f1b3f09
--- /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 0000000..e703b64
--- /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 0000000..3768780
--- /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 0000000..c4a4424
--- /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 0000000..fd9fb40
--- /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 0000000..47454d3
--- /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 0000000..8853064
--- /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 0000000..4da39dc
--- /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 0000000..e41979d
--- /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 0000000..e7ce55b
--- /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 0000000..029bec3
--- /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 0000000..b5161eb
--- /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 0000000..a88bb5d
--- /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 2eab03a..7a41ae2 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 6a5d34f..64d7428 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 0000000..4f91d05
--- /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 0000000..b487f03
--- /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 0000000..de67f9b
--- /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 0000000..f1b3f09
--- /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 0000000..e703b64
--- /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 0000000..3768780
--- /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 0000000..db02e8f
--- /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 0000000..fd9fb40
--- /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 0000000..47454d3
--- /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 0000000..8853064
--- /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 0000000..4da39dc
--- /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 0000000..7b6ab2d
--- /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 0000000..31cf626
--- /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 0000000..029bec3
--- /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 0000000..d5303e2
--- /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 0000000..a88bb5d
--- /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 0000000..319178a
--- /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/utils/penguin_method_channel.dart b/lib/core/utils/penguin_method_channel.dart
new file mode 100644
index 0000000..1f19037
--- /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/utils.dart b/lib/core/utils/utils.dart
index 9edc9b2..03ff6b5 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,
diff --git a/lib/features/hospital/AppPermission.dart b/lib/features/hospital/AppPermission.dart
new file mode 100644
index 0000000..008f571
--- /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 0000000..dd9531f
--- /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/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart
index 76ed3f7..6216880 100644
--- a/lib/presentation/home/widgets/small_service_card.dart
+++ b/lib/presentation/home/widgets/small_service_card.dart
@@ -1,19 +1,25 @@
+import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
+import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
+import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart';
+import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
+import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import '../../../core/utils/utils.dart';
import '../../../theme/colors.dart';
+import '../../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
import '../../radiology/radiology_orders_page.dart' show RadiologyOrdersPage;
class SmallServiceCard extends StatelessWidget {
@@ -117,10 +123,50 @@ class SmallServiceCard extends StatelessWidget {
),
);
break;
+
+ case "indoor_navigation":
+ openIndoorNavigationBottomSheet(context);
default:
// Handle unknown service
break;
}
});
}
+
+ void openIndoorNavigationBottomSheet(BuildContext context) {
+ showCommonBottomSheetWithoutHeight(
+ title: LocaleKeys.selectHospital.tr(),
+ context,
+ child: ChangeNotifierProvider(
+ create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
+ child: Consumer(
+ builder: (_, vm, __) => HospitalBottomSheetBody(
+ searchText: vm.searchController,
+ displayList: vm.displayList,
+ onFacilityClicked: (value) {
+ vm.setSelectedFacility(value);
+ vm.getDisplayList();
+ },
+ onHospitalClicked: (hospital) {
+ Navigator.pop(context);
+ vm.openPenguin(hospital);
+ },
+ onHospitalSearch: (value) {
+ vm.searchHospitals(value ?? "");
+ },
+ selectedFacility: vm.selectedFacility,
+ hmcCount: vm.hmcCount,
+ hmgCount: vm.hmgCount,
+ ),
+ ),
+ ),
+ isFullScreen: false,
+ isCloseButtonVisible: true,
+ hasBottomPadding: false,
+ backgroundColor: AppColors.bottomSheetBgColor,
+ callBackFunc: () {
+ context.read().clearSearchText();
+ },
+ );
+ }
}
diff --git a/pubspec.lock b/pubspec.lock
new file mode 100644
index 0000000..42b828d
--- /dev/null
+++ b/pubspec.lock
@@ -0,0 +1,1985 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ _flutterfire_internals:
+ dependency: transitive
+ description:
+ name: _flutterfire_internals
+ sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.59"
+ amazon_payfort:
+ dependency: "direct main"
+ description:
+ name: amazon_payfort
+ sha256: "7732df0764aecbb814f910db36d0dca2f696e7e5ea380b49aa3ec62965768b33"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.4"
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.7"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.7.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.13.0"
+ audio_session:
+ dependency: transitive
+ description:
+ name: audio_session
+ sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.2"
+ auto_size_text:
+ dependency: "direct main"
+ description:
+ name: auto_size_text
+ sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ barcode_scan2:
+ dependency: "direct main"
+ description:
+ name: barcode_scan2
+ sha256: "0f3eb7c0a0c80a0f65d3fa88737544fdb6d27127a4fad566e980e626f3fb76e1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.1"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ cached_network_image:
+ dependency: "direct main"
+ description:
+ name: cached_network_image
+ sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.1"
+ cached_network_image_platform_interface:
+ dependency: transitive
+ description:
+ name: cached_network_image_platform_interface
+ sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.1"
+ cached_network_image_web:
+ dependency: transitive
+ description:
+ name: cached_network_image_web
+ sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ carp_serializable:
+ dependency: transitive
+ description:
+ name: carp_serializable
+ sha256: f039f8ea22e9437aef13fe7e9743c3761c76d401288dcb702eadd273c3e4dcef
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ chewie:
+ dependency: transitive
+ description:
+ name: chewie
+ sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.13.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.19.1"
+ connectivity_plus:
+ dependency: "direct main"
+ description:
+ name: connectivity_plus
+ sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.5"
+ connectivity_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: connectivity_plus_platform_interface
+ sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.4+2"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.6"
+ csslib:
+ dependency: transitive
+ description:
+ name: csslib
+ sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.2"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.8"
+ dartz:
+ dependency: "direct main"
+ description:
+ name: dartz
+ sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.1"
+ dbus:
+ dependency: transitive
+ description:
+ name: dbus
+ sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.11"
+ device_calendar:
+ dependency: "direct main"
+ description:
+ path: "."
+ ref: HEAD
+ resolved-ref: "5ea5ed9e2bb499c0633383b53103f2920b634755"
+ url: "https://github.com/bardram/device_calendar"
+ source: git
+ version: "4.3.1"
+ device_info_plus:
+ dependency: "direct main"
+ description:
+ name: device_info_plus
+ sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.5.0"
+ device_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: device_info_plus_platform_interface
+ sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.3"
+ dropdown_search:
+ dependency: "direct main"
+ description:
+ name: dropdown_search
+ sha256: c29b3e5147a82a06a4a08b3b574c51cb48cc17ad89893d53ee72a6f86643622e
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.2"
+ easy_localization:
+ dependency: "direct main"
+ description:
+ name: easy_localization
+ sha256: "2ccdf9db8fe4d9c5a75c122e6275674508fd0f0d49c827354967b8afcc56bbed"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.8"
+ easy_logger:
+ dependency: transitive
+ description:
+ name: easy_logger
+ sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.0.2"
+ equatable:
+ dependency: "direct main"
+ description:
+ name: equatable
+ sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.7"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.3"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
+ file_picker:
+ dependency: "direct main"
+ description:
+ name: file_picker
+ sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.3.3"
+ file_selector_linux:
+ dependency: transitive
+ description:
+ name: file_selector_linux
+ sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.3+2"
+ file_selector_macos:
+ dependency: transitive
+ description:
+ name: file_selector_macos
+ sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.4+4"
+ file_selector_platform_interface:
+ dependency: transitive
+ description:
+ name: file_selector_platform_interface
+ sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.6.2"
+ file_selector_windows:
+ dependency: transitive
+ description:
+ name: file_selector_windows
+ sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.3+4"
+ firebase_analytics:
+ dependency: "direct main"
+ description:
+ name: firebase_analytics
+ sha256: "4f85b161772e1d54a66893ef131c0a44bd9e552efa78b33d5f4f60d2caa5c8a3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.6.0"
+ firebase_analytics_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_analytics_platform_interface
+ sha256: a44b6d1155ed5cae7641e3de7163111cfd9f6f6c954ca916dc6a3bdfa86bf845
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.4.3"
+ firebase_analytics_web:
+ dependency: transitive
+ description:
+ name: firebase_analytics_web
+ sha256: c7d1ed1f86ae64215757518af5576ff88341c8ce5741988c05cc3b2e07b0b273
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.10+16"
+ firebase_core:
+ dependency: "direct main"
+ description:
+ name: firebase_core
+ sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.15.2"
+ firebase_core_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_core_platform_interface
+ sha256: "5873a370f0d232918e23a5a6137dbe4c2c47cf017301f4ea02d9d636e52f60f0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.1"
+ firebase_core_web:
+ dependency: transitive
+ description:
+ name: firebase_core_web
+ sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.24.1"
+ firebase_messaging:
+ dependency: "direct main"
+ description:
+ name: firebase_messaging
+ sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "15.2.10"
+ firebase_messaging_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_messaging_platform_interface
+ sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.6.10"
+ firebase_messaging_web:
+ dependency: transitive
+ description:
+ name: firebase_messaging_web
+ sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.10.10"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ fl_chart:
+ dependency: "direct main"
+ description:
+ name: fl_chart
+ sha256: "577aeac8ca414c25333334d7c4bb246775234c0e44b38b10a82b559dd4d764e7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_cache_manager:
+ dependency: transitive
+ description:
+ name: flutter_cache_manager
+ sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.1"
+ flutter_hooks:
+ dependency: transitive
+ description:
+ name: flutter_hooks
+ sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.20.5"
+ flutter_inappwebview:
+ dependency: "direct main"
+ description:
+ name: flutter_inappwebview
+ sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.5"
+ flutter_inappwebview_android:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_android
+ sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
+ flutter_inappwebview_internal_annotations:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_internal_annotations
+ sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ flutter_inappwebview_ios:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_ios
+ sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_inappwebview_macos:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_macos
+ sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_inappwebview_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_platform_interface
+ sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0+1"
+ flutter_inappwebview_web:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_web
+ sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_inappwebview_windows:
+ dependency: transitive
+ description:
+ name: flutter_inappwebview_windows
+ sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.0"
+ flutter_ios_voip_kit_karmm:
+ dependency: "direct main"
+ description:
+ name: flutter_ios_voip_kit_karmm
+ sha256: "31a445d78aacacdf128a0354efb9f4e424285dfe4c0af3ea872e64f03e6f6bfc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.0"
+ flutter_local_notifications:
+ dependency: "direct main"
+ description:
+ name: flutter_local_notifications
+ sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "19.4.2"
+ flutter_local_notifications_linux:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_linux
+ sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.0"
+ flutter_local_notifications_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_platform_interface
+ sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.1.0"
+ flutter_local_notifications_windows:
+ dependency: transitive
+ description:
+ name: flutter_local_notifications_windows
+ sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.3"
+ flutter_localizations:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_nfc_kit:
+ dependency: "direct main"
+ description:
+ name: flutter_nfc_kit
+ sha256: "3cc4059626fa672031261512299458dd274de4ccb57a7f0ee0951ddd70a048e5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.6.0"
+ flutter_plugin_android_lifecycle:
+ dependency: transitive
+ description:
+ name: flutter_plugin_android_lifecycle
+ sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.30"
+ flutter_rating_bar:
+ dependency: "direct main"
+ description:
+ name: flutter_rating_bar
+ sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.1"
+ flutter_staggered_animations:
+ dependency: "direct main"
+ description:
+ name: flutter_staggered_animations
+ sha256: "81d3c816c9bb0dca9e8a5d5454610e21ffb068aedb2bde49d2f8d04f75538351"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ flutter_svg:
+ dependency: "direct main"
+ description:
+ name: flutter_svg
+ sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ flutter_swiper_view:
+ dependency: "direct main"
+ description:
+ name: flutter_swiper_view
+ sha256: "2a165b259e8a4c49d4da5626b967ed42a73dac2d075bd9e266ad8d23b9f01879"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.8"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_widget_from_html:
+ dependency: "direct main"
+ description:
+ name: flutter_widget_from_html
+ sha256: "7f1daefcd3009c43c7e7fb37501e6bb752d79aa7bfad0085fb0444da14e89bd0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.17.1"
+ flutter_widget_from_html_core:
+ dependency: transitive
+ description:
+ name: flutter_widget_from_html_core
+ sha256: "1120ee6ed3509ceff2d55aa6c6cbc7b6b1291434422de2411b5a59364dd6ff03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.17.0"
+ flutter_zoom_videosdk:
+ dependency: "direct main"
+ description:
+ name: flutter_zoom_videosdk
+ sha256: "22731485fe48472a34ff0c7e787a382f5e1ec662fd89186e58e760974fc2a0cb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
+ fluttertoast:
+ dependency: "direct main"
+ description:
+ name: fluttertoast
+ sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.2.14"
+ fwfh_cached_network_image:
+ dependency: transitive
+ description:
+ name: fwfh_cached_network_image
+ sha256: "484cb5f8047f02cfac0654fca5832bfa91bb715fd7fc651c04eb7454187c4af8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.16.1"
+ fwfh_chewie:
+ dependency: transitive
+ description:
+ name: fwfh_chewie
+ sha256: ae74fc26798b0e74f3983f7b851e74c63b9eeb2d3015ecd4b829096b2c3f8818
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.16.1"
+ fwfh_just_audio:
+ dependency: transitive
+ description:
+ name: fwfh_just_audio
+ sha256: dfd622a0dfe049ac647423a2a8afa7f057d9b2b93d92710b624e3d370b1ac69a
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.17.0"
+ fwfh_svg:
+ dependency: transitive
+ description:
+ name: fwfh_svg
+ sha256: "2e6bb241179eeeb1a7941e05c8c923b05d332d36a9085233e7bf110ea7deb915"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.16.1"
+ fwfh_url_launcher:
+ dependency: transitive
+ description:
+ name: fwfh_url_launcher
+ sha256: c38aa8fb373fda3a89b951fa260b539f623f6edb45eee7874cb8b492471af881
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.16.1"
+ fwfh_webview:
+ dependency: transitive
+ description:
+ name: fwfh_webview
+ sha256: f71b0aa16e15d82f3c017f33560201ff5ae04e91e970cab5d12d3bcf970b870c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.15.6"
+ geoclue:
+ dependency: transitive
+ description:
+ name: geoclue
+ sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.1"
+ geolocator:
+ dependency: "direct main"
+ description:
+ name: geolocator
+ sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.0.2"
+ geolocator_android:
+ dependency: transitive
+ description:
+ name: geolocator_android
+ sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.2"
+ geolocator_apple:
+ dependency: transitive
+ description:
+ name: geolocator_apple
+ sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.13"
+ geolocator_linux:
+ dependency: transitive
+ description:
+ name: geolocator_linux
+ sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.3"
+ geolocator_platform_interface:
+ dependency: transitive
+ description:
+ name: geolocator_platform_interface
+ sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.6"
+ geolocator_web:
+ dependency: transitive
+ description:
+ name: geolocator_web
+ sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.3"
+ geolocator_windows:
+ dependency: transitive
+ description:
+ name: geolocator_windows
+ sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.5"
+ get_it:
+ dependency: "direct main"
+ description:
+ name: get_it
+ sha256: a4292e7cf67193f8e7c1258203104eb2a51ec8b3a04baa14695f4064c144297b
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.2.0"
+ gms_check:
+ dependency: "direct main"
+ description:
+ name: gms_check
+ sha256: b3fc08fd41da233f9761f9981303346aa9778b4802e90ce9bd8122674fcca6f0
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.4"
+ google_api_availability:
+ dependency: "direct main"
+ description:
+ name: google_api_availability
+ sha256: "2ffdc91e1e0cf4e7974fef6c2988a24cefa81f03526ff04b694df6dc0fcbca03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.0.1"
+ google_api_availability_android:
+ dependency: transitive
+ description:
+ name: google_api_availability_android
+ sha256: "4794147f43a8f3eee6b514d3ae30dbe6f7b9048cae8cd2a74cb4055cd28d74a8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ google_api_availability_platform_interface:
+ dependency: transitive
+ description:
+ name: google_api_availability_platform_interface
+ sha256: "65b7da62fe5b582bb3d508628ad827d36d890710ea274766a992a56fa5420da6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ google_maps:
+ dependency: transitive
+ description:
+ name: google_maps
+ sha256: "5d410c32112d7c6eb7858d359275b2aa04778eed3e36c745aeae905fb2fa6468"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.2.0"
+ google_maps_flutter:
+ dependency: "direct main"
+ description:
+ name: google_maps_flutter
+ sha256: c389e16fafc04b37a4105e0757ecb9d59806026cee72f408f1ba68811d01bfe6
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.13.1"
+ google_maps_flutter_android:
+ dependency: transitive
+ description:
+ name: google_maps_flutter_android
+ sha256: a6c9d43f6a944ff4bae5c3deb34817970ac3d591dcd7f5bd2ea450ab9e9c514a
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.18.2"
+ google_maps_flutter_ios:
+ dependency: transitive
+ description:
+ name: google_maps_flutter_ios
+ sha256: ca02463b19a9abc7d31fcaf22631d021d647107467f741b917a69fa26659fd75
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.15.5"
+ google_maps_flutter_platform_interface:
+ dependency: transitive
+ description:
+ name: google_maps_flutter_platform_interface
+ sha256: f4b9b44f7b12a1f6707ffc79d082738e0b7e194bf728ee61d2b3cdf5fdf16081
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.14.0"
+ google_maps_flutter_web:
+ dependency: transitive
+ description:
+ name: google_maps_flutter_web
+ sha256: "53e5dbf73ff04153acc55a038248706967c21d5b6ef6657a57fce2be73c2895a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.14+2"
+ gsettings:
+ dependency: transitive
+ description:
+ name: gsettings
+ sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.8"
+ health:
+ dependency: "direct main"
+ description:
+ name: health
+ sha256: "320633022fb2423178baa66508001c4ca5aee5806ffa2c913e66488081e9fd47"
+ url: "https://pub.dev"
+ source: hosted
+ version: "13.1.4"
+ hijri_gregorian_calendar:
+ dependency: "direct main"
+ description:
+ name: hijri_gregorian_calendar
+ sha256: aecdbe3c9365fac55f17b5e1f24086a81999b1e5c9372cb08888bfbe61e07fa1
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.1"
+ html:
+ dependency: transitive
+ description:
+ name: html
+ sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.15.6"
+ http:
+ dependency: "direct main"
+ description:
+ name: http
+ sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.2"
+ huawei_location:
+ dependency: "direct main"
+ description:
+ name: huawei_location
+ sha256: "3100d6b2b11df56481b8deade71baa84970e0bae0ade6ec56407be2b036af355"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.14.2+301"
+ huawei_map:
+ dependency: "direct main"
+ description:
+ path: flutter-hms-map
+ ref: HEAD
+ resolved-ref: "9a16541e4016e3bf58a2571e6aa658a4751af399"
+ url: "https://github.com/fleoparra/hms-flutter-plugin.git"
+ source: git
+ version: "6.11.2+303"
+ image_picker:
+ dependency: "direct main"
+ description:
+ name: image_picker
+ sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ image_picker_android:
+ dependency: transitive
+ description:
+ name: image_picker_android
+ sha256: "8dfe08ea7fcf7467dbaf6889e72eebd5e0d6711caae201fdac780eb45232cd02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.13+3"
+ image_picker_for_web:
+ dependency: transitive
+ description:
+ name: image_picker_for_web
+ sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ image_picker_ios:
+ dependency: transitive
+ description:
+ name: image_picker_ios
+ sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.13"
+ image_picker_linux:
+ dependency: transitive
+ description:
+ name: image_picker_linux
+ sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.2"
+ image_picker_macos:
+ dependency: transitive
+ description:
+ name: image_picker_macos
+ sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.2"
+ image_picker_platform_interface:
+ dependency: transitive
+ description:
+ name: image_picker_platform_interface
+ sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ image_picker_windows:
+ dependency: transitive
+ description:
+ name: image_picker_windows
+ sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.2"
+ intl:
+ dependency: "direct main"
+ description:
+ name: intl
+ sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.20.2"
+ jiffy:
+ dependency: "direct main"
+ description:
+ name: jiffy
+ sha256: "9bafbfe6d97587048bf449165e050029e716a12438f54a3d39e7e3a256decdac"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.4.3"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.9.0"
+ just_audio:
+ dependency: "direct main"
+ description:
+ name: just_audio
+ sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.5"
+ just_audio_platform_interface:
+ dependency: transitive
+ description:
+ name: just_audio_platform_interface
+ sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.6.0"
+ just_audio_web:
+ dependency: transitive
+ description:
+ name: just_audio_web
+ sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.16"
+ keyboard_actions:
+ dependency: "direct main"
+ description:
+ name: keyboard_actions
+ sha256: "31e0ab2a706ac8f58887efa60efc1f19aecdf37d8ab0f665a0f156d1fbeab650"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.0.2"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.10"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.1.1"
+ local_auth:
+ dependency: "direct main"
+ description:
+ name: local_auth
+ sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
+ local_auth_android:
+ dependency: transitive
+ description:
+ name: local_auth_android
+ sha256: "1ee0e63fb8b5c6fa286796b5fb1570d256857c2f4a262127e728b36b80a570cf"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.53"
+ local_auth_darwin:
+ dependency: transitive
+ description:
+ name: local_auth_darwin
+ sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.6.1"
+ local_auth_platform_interface:
+ dependency: transitive
+ description:
+ name: local_auth_platform_interface
+ sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.10"
+ local_auth_windows:
+ dependency: transitive
+ description:
+ name: local_auth_windows
+ sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.11"
+ location:
+ dependency: "direct main"
+ description:
+ name: location
+ sha256: b080053c181c7d152c43dd576eec6436c40e25f326933051c330da563ddd5333
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.0.1"
+ location_platform_interface:
+ dependency: transitive
+ description:
+ name: location_platform_interface
+ sha256: ca8700bb3f6b1e8b2afbd86bd78b2280d116c613ca7bfa1d4d7b64eba357d749
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.1"
+ location_web:
+ dependency: transitive
+ description:
+ name: location_web
+ sha256: b8e3add5efe0d65c5e692b7a135d80a4015c580d3ea646fa71973e97668dd868
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.1"
+ logger:
+ dependency: "direct main"
+ description:
+ name: logger
+ sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.6.1"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ lottie:
+ dependency: "direct main"
+ description:
+ name: lottie
+ sha256: "8ae0be46dbd9e19641791dc12ee480d34e1fd3f84c749adc05f3ad9342b71b95"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.2"
+ manage_calendar_events:
+ dependency: "direct main"
+ description:
+ name: manage_calendar_events
+ sha256: f17600fcb7dc7047120c185993045e493d686930237b4e3c2689c26a64513d66
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.3"
+ maps_launcher:
+ dependency: "direct main"
+ description:
+ name: maps_launcher
+ sha256: dac4c609720211fa6336b5903d917fe45e545c6b5665978efc3db2a3f436b1ae
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0+1"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.17"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.11.1"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.16.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ ndef:
+ dependency: transitive
+ description:
+ name: ndef
+ sha256: "5083507cff4bb823b2a198a27ea2c70c4d6bc27a97b66097d966a250e1615d54"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.4"
+ nested:
+ dependency: transitive
+ description:
+ name: nested
+ sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+ network_info_plus:
+ dependency: "direct main"
+ description:
+ name: network_info_plus
+ sha256: f926b2ba86aa0086a0dfbb9e5072089bc213d854135c1712f1d29fc89ba3c877
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.4"
+ network_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: network_info_plus_platform_interface
+ sha256: "7e7496a8a9d8136859b8881affc613c4a21304afeb6c324bcefc4bd0aff6b94b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ nm:
+ dependency: transitive
+ description:
+ name: nm
+ sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.5.0"
+ octo_image:
+ dependency: transitive
+ description:
+ name: octo_image
+ sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ open_filex:
+ dependency: "direct main"
+ description:
+ name: open_filex
+ sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.7.0"
+ package_info_plus:
+ dependency: transitive
+ description:
+ name: package_info_plus
+ sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.3.1"
+ package_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: package_info_plus_platform_interface
+ sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.1"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ path_parsing:
+ dependency: transitive
+ description:
+ name: path_parsing
+ sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ path_provider:
+ dependency: "direct main"
+ description:
+ name: path_provider
+ sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.5"
+ path_provider_android:
+ dependency: transitive
+ description:
+ name: path_provider_android
+ sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.18"
+ path_provider_foundation:
+ dependency: transitive
+ description:
+ name: path_provider_foundation
+ sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
+ permission_handler:
+ dependency: "direct main"
+ description:
+ name: permission_handler
+ sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
+ url: "https://pub.dev"
+ source: hosted
+ version: "12.0.1"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "13.0.1"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.4.7"
+ permission_handler_html:
+ dependency: transitive
+ description:
+ name: permission_handler_html
+ sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.1.3+5"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.3.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.1"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.6"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.8"
+ posix:
+ dependency: transitive
+ description:
+ name: posix
+ sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.3"
+ protobuf:
+ dependency: transitive
+ description:
+ name: protobuf
+ sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ provider:
+ dependency: "direct main"
+ description:
+ name: provider
+ sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.5+1"
+ quiver:
+ dependency: transitive
+ description:
+ name: quiver
+ sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
+ rrule:
+ dependency: transitive
+ description:
+ name: rrule
+ sha256: b7425410c594d4b6717c9f17ec8ef83c9d1ff2e513c428a135b5924fc2e8e045
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.17"
+ rxdart:
+ dependency: transitive
+ description:
+ name: rxdart
+ sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.28.0"
+ sanitize_html:
+ dependency: transitive
+ description:
+ name: sanitize_html
+ sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ share_plus:
+ dependency: "direct main"
+ description:
+ name: share_plus
+ sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.1.0"
+ share_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: share_plus_platform_interface
+ sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.0"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.3"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.13"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.4"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.3"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ shimmer:
+ dependency: "direct main"
+ description:
+ name: shimmer
+ sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ sizer:
+ dependency: "direct main"
+ description:
+ name: sizer
+ sha256: "9963c89e4d30d7c2108de3eafc0a7e6a4a8009799376ea6be5ef0a9ad87cfbad"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ smooth_corner:
+ dependency: "direct main"
+ description:
+ name: smooth_corner
+ sha256: "112d7331f82ead81ec870c5d1eb0624f2e7e367eccd166c2fffe4c11d4f87c4f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ sms_otp_auto_verify:
+ dependency: "direct main"
+ description:
+ name: sms_otp_auto_verify
+ sha256: ee02af0d6b81d386ef70d7d0317a1929bc0b4a3a30a451284450bbcf6901ba1a
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.1"
+ sprintf:
+ dependency: transitive
+ description:
+ name: sprintf
+ sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.0"
+ sqflite:
+ dependency: transitive
+ description:
+ name: sqflite
+ sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ sqflite_android:
+ dependency: transitive
+ description:
+ name: sqflite_android
+ sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2+2"
+ sqflite_common:
+ dependency: transitive
+ description:
+ name: sqflite_common
+ sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.6"
+ sqflite_darwin:
+ dependency: transitive
+ description:
+ name: sqflite_darwin
+ sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.2"
+ sqflite_platform_interface:
+ dependency: transitive
+ description:
+ name: sqflite_platform_interface
+ sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ stream_transform:
+ dependency: transitive
+ description:
+ name: stream_transform
+ sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.1"
+ syncfusion_flutter_calendar:
+ dependency: "direct main"
+ description:
+ name: syncfusion_flutter_calendar
+ sha256: "8e8a4eef01d6a82ae2c17e76d497ff289ded274de014c9f471ffabc12d1e2e71"
+ url: "https://pub.dev"
+ source: hosted
+ version: "30.2.7"
+ syncfusion_flutter_core:
+ dependency: transitive
+ description:
+ name: syncfusion_flutter_core
+ sha256: bfd026c0f9822b49ff26fed11cd3334519acb6a6ad4b0c81d9cd18df6af1c4c0
+ url: "https://pub.dev"
+ source: hosted
+ version: "30.2.7"
+ syncfusion_flutter_datepicker:
+ dependency: transitive
+ description:
+ name: syncfusion_flutter_datepicker
+ sha256: b5f35cc808e91b229d41613efe71dadab1549a35bfd493f922fc06ccc2fe908c
+ url: "https://pub.dev"
+ source: hosted
+ version: "30.2.7"
+ syncfusion_localizations:
+ dependency: transitive
+ description:
+ name: syncfusion_localizations
+ sha256: bb32b07879b4c1dee5d4c8ad1c57343a4fdae55d65a87f492727c11b68f23164
+ url: "https://pub.dev"
+ source: hosted
+ version: "30.2.7"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.4.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.6"
+ time:
+ dependency: transitive
+ description:
+ name: time
+ sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.5"
+ timezone:
+ dependency: transitive
+ description:
+ name: timezone
+ sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.1"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ url_launcher:
+ dependency: "direct main"
+ description:
+ name: url_launcher
+ sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.2"
+ url_launcher_android:
+ dependency: transitive
+ description:
+ name: url_launcher_android
+ sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.22"
+ url_launcher_ios:
+ dependency: transitive
+ description:
+ name: url_launcher_ios
+ sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.3.4"
+ url_launcher_linux:
+ dependency: transitive
+ description:
+ name: url_launcher_linux
+ sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.1"
+ url_launcher_macos:
+ dependency: transitive
+ description:
+ name: url_launcher_macos
+ sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.3"
+ url_launcher_platform_interface:
+ dependency: transitive
+ description:
+ name: url_launcher_platform_interface
+ sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.2"
+ url_launcher_web:
+ dependency: transitive
+ description:
+ name: url_launcher_web
+ sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.1"
+ url_launcher_windows:
+ dependency: transitive
+ description:
+ name: url_launcher_windows
+ sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.4"
+ uuid:
+ dependency: "direct main"
+ description:
+ name: uuid
+ sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.5.1"
+ vector_graphics:
+ dependency: transitive
+ description:
+ name: vector_graphics
+ sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.19"
+ vector_graphics_codec:
+ dependency: transitive
+ description:
+ name: vector_graphics_codec
+ sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.13"
+ vector_graphics_compiler:
+ dependency: transitive
+ description:
+ name: vector_graphics_compiler
+ sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.19"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ video_player:
+ dependency: transitive
+ description:
+ name: video_player
+ sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.10.0"
+ video_player_android:
+ dependency: transitive
+ description:
+ name: video_player_android
+ sha256: "6cfe0b1e102522eda1e139b82bf00602181c5844fd2885340f595fb213d74842"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.8.14"
+ video_player_avfoundation:
+ dependency: transitive
+ description:
+ name: video_player_avfoundation
+ sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.8.4"
+ video_player_platform_interface:
+ dependency: transitive
+ description:
+ name: video_player_platform_interface
+ sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.4.0"
+ video_player_web:
+ dependency: transitive
+ description:
+ name: video_player_web
+ sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.4.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
+ url: "https://pub.dev"
+ source: hosted
+ version: "15.0.2"
+ wakelock_plus:
+ dependency: transitive
+ description:
+ name: wakelock_plus
+ sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.3"
+ wakelock_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: wakelock_plus_platform_interface
+ sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ web:
+ dependency: "direct main"
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ webview_flutter:
+ dependency: transitive
+ description:
+ name: webview_flutter
+ sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.13.0"
+ webview_flutter_android:
+ dependency: transitive
+ description:
+ name: webview_flutter_android
+ sha256: "21507ea5a326ceeba4d29dea19e37d92d53d9959cfc746317b9f9f7a57418d87"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.10.3"
+ webview_flutter_platform_interface:
+ dependency: transitive
+ description:
+ name: webview_flutter_platform_interface
+ sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.14.0"
+ webview_flutter_wkwebview:
+ dependency: transitive
+ description:
+ name: webview_flutter_wkwebview
+ sha256: fea63576b3b7e02b2df8b78ba92b48ed66caec2bb041e9a0b1cbd586d5d80bfd
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.23.1"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.14.0"
+ win32_registry:
+ dependency: transitive
+ description:
+ name: win32_registry
+ sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.6.1"
+sdks:
+ dart: ">=3.9.0 <4.0.0"
+ flutter: ">=3.35.0"