Merge branch 'master' into faiz_dev

# Conflicts:
#	lib/core/api_consts.dart
#	lib/core/dependencies.dart
#	lib/main.dart
faiz_dev
faizatflutter 3 days ago
commit ff2bcbae3f

@ -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"))
}

Binary file not shown.

Binary file not shown.

@ -49,7 +49,7 @@
<uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
tools:node="remove" /> <!-- <uses-permission android:name="android.permission.INTERNET" /> -->
<!-- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@ -58,6 +58,13 @@
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-feature
android:name="android.hardware.sensor.stepcounter"
android:required="false"

@ -1,6 +1,51 @@
package com.ejada.hmg
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.view.WindowManager
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi
import com.ejada.hmg.penguin.PenguinInPlatformBridge
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.embedding.android.FlutterFragmentActivity
class MainActivity : FlutterFragmentActivity()
class MainActivity: FlutterFragmentActivity() {
@RequiresApi(Build.VERSION_CODES.O)
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
// Create Flutter Platform Bridge
this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
PenguinInPlatformBridge(flutterEngine, this).create()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
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()
}
}

@ -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<String, Any>?
Log.d("TAG", "configureFlutterEngine: $args")
println("args")
args?.let {
PenguinView(
mainActivity,
100,
args,
flutterEngine.dartExecutor.binaryMessenger,
activity = mainActivity,
channel
)
}
}
else -> {
result.notImplemented()
}
}
}
}
}

@ -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<Integer> callable = () -> {
SharedPreferences preferences = getPreferences(context);
return preferences.getInt(key, -1);
};
Future<Integer> 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<String> callable = () -> {
SharedPreferences preferences = getPreferences(context);
return preferences.getString(key, defValue);
};
Future<String> 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<Boolean> callable = () -> {
SharedPreferences preferences = getPreferences(context);
return preferences.getBoolean(key, defValue);
};
Future<Boolean> 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
}
}

@ -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);
}
}
}

@ -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
);
}
}

@ -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;
}
}
}

@ -0,0 +1,28 @@
package com.ejada.hmg.PermissionManager
import android.Manifest
import android.os.Build
object PermissionHelper {
fun getRequiredPermissions(): Array<String> {
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()
}
}

@ -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<out String>, grantResults: IntArray) {
if (this.requestCode == requestCode) {
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
if (allGranted) {
listener.onPermissionGranted()
} else {
listener.onPermissionDenied()
}
}
}
}

@ -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)
}
}

@ -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");
}

@ -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<ResponseBody> = ApiController.getInstance(mContext)
.apiMethods
.getToken(postToken)
// Enqueue the call for asynchronous execution
purposesCall.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>,
response: Response<ResponseBody?>
) {
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<ResponseBody?>, t: Throwable) {
apiTokenCallBack.onGetByRefIDError(t.message)
}
})
} catch (error: Exception) {
apiTokenCallBack.onGetByRefIDError("Exception during API call: $error")
}
}
}

@ -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<String, Any>,
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<String, Any?> = 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<Double>?) {
TODO("Not yet implemented")
}
override fun onLocationMessage(locationMessage: LocationMessage?) {
TODO("Not yet implemented")
}
}

@ -1,3 +1,3 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>
<!-- <string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>-->
</resources>

@ -19,5 +19,5 @@
<string name="GEOFENCE_REQUEST_TOO_FREQUENT">
Geofence requests happened too frequently.
</string>
<!-- <string name="mapbox_access_token" translatable="false">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>-->
<string name="mapbox_access_token" translatable="false">pk.eyJ1IjoicndhaWQiLCJhIjoiY2x5cGo4aHNjMGNsbTJyc2djempobGQxaSJ9.RCaC6WrUt4A4YnZNfxnONQ</string>
</resources>

File diff suppressed because one or more lines are too long

@ -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
}

@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path d="M11.9998 21.25C11.5858 21.2503 11.2498 21.5859 11.2498 22C11.2498 22.4141 11.5858 22.7497 11.9998 22.75H14.3953C14.577 22.75 14.7553 22.7915 14.9158 22.8691L16.9578 23.8564C17.3233 24.0333 17.7251 24.125 18.1316 24.125H19.1746C19.601 24.1251 19.9516 24.3534 20.1277 24.6719L18.1931 25.207C17.9121 25.2847 17.6116 25.2563 17.3523 25.1309L15.1687 24.0752C14.7959 23.895 14.3471 24.051 14.1667 24.4238C13.9866 24.7966 14.1427 25.2454 14.5154 25.4258L16.699 26.4814C17.2874 26.7661 17.9622 26.8265 18.5925 26.6523L21.1335 25.9502C21.4836 25.8534 21.7498 25.5327 21.7498 25.1416C21.7498 25.1125 21.7468 25.0836 21.7458 25.0547L25.8132 23.8057L25.8162 23.8047C26.3135 23.6499 26.8624 23.8288 27.1892 24.2812C27.3052 24.4419 27.2446 24.6626 27.1033 24.7441L19.5876 29.0811C19.2867 29.2546 18.9351 29.2953 18.6082 29.1982L12.2136 27.3008C11.8165 27.1829 11.3988 27.4095 11.281 27.8066C11.1632 28.2037 11.3898 28.6214 11.7869 28.7393L18.1814 30.6367C18.9059 30.8517 19.6827 30.7578 20.3376 30.3799L27.8533 26.0439C28.7864 25.5055 29.0265 24.2639 28.405 23.4033C27.7121 22.4438 26.5013 22.0206 25.3708 22.3721L25.3699 22.373L21.241 23.6416C20.7675 23.0215 20.0117 22.6251 19.1746 22.625H18.1316C17.95 22.625 17.7716 22.5844 17.6111 22.5068L15.5691 21.5186C15.2035 21.3417 14.8017 21.25 14.3953 21.25H11.9998ZM22.6511 9.91992C21.7188 9.02657 20.2817 9.02657 19.3494 9.91992C18.7347 10.5091 17.9734 11.3294 17.3611 12.2773C16.7542 13.2169 16.2498 14.3489 16.2498 15.5469C16.2498 17.8542 18.0478 20.25 21.0007 20.25C23.9534 20.2497 25.7507 17.854 25.7507 15.5469C25.7507 14.3489 25.2463 13.2169 24.6394 12.2773C24.0271 11.3294 23.2657 10.5091 22.6511 9.91992ZM20.3875 11.0029C20.7396 10.6657 21.2609 10.6657 21.613 11.0029C22.181 11.5474 22.8538 12.2767 23.3796 13.0908C23.9109 13.9134 24.2507 14.7575 24.2507 15.5469C24.2507 17.1367 23.0181 18.7497 21.0007 18.75C18.9831 18.75 17.7498 17.1369 17.7498 15.5469C17.7498 14.7575 18.0895 13.9134 18.6208 13.0908C19.1467 12.2767 19.8195 11.5474 20.3875 11.0029Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -1,3 +1,3 @@
add_<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1905 5.77665C20.59 6.15799 20.6047 6.79098 20.2234 7.19048L9.72336 18.1905C9.53745 18.3852 9.28086 18.4968 9.01163 18.4999C8.7424 18.5031 8.48328 18.3975 8.29289 18.2071L4.79289 14.7071C4.40237 14.3166 4.40237 13.6834 4.79289 13.2929C5.18342 12.9024 5.81658 12.9024 6.20711 13.2929L8.98336 16.0692L18.7766 5.80953C19.158 5.41003 19.791 5.39531 20.1905 5.77665Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 537 B

After

Width:  |  Height:  |  Size: 533 B

@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.9999 9.25C17.9288 9.25 16.2499 10.9289 16.2499 13C16.2499 14.0736 16.805 15.1188 17.4741 15.8701C17.8161 16.2541 18.2118 16.5896 18.6284 16.8334C19.038 17.0732 19.5126 17.25 19.9999 17.25C20.4872 17.25 20.9617 17.0732 21.3714 16.8334C21.7879 16.5896 22.1837 16.2541 22.5257 15.8701C23.1947 15.1188 23.7499 14.0736 23.7499 13C23.7499 10.9289 22.0709 9.25 19.9999 9.25ZM17.7499 13C17.7499 11.7574 18.7572 10.75 19.9999 10.75C21.2425 10.75 22.2499 11.7574 22.2499 13C22.2499 13.5832 21.9259 14.2881 21.4054 14.8725C21.1526 15.1564 20.876 15.3853 20.6137 15.5388C20.3444 15.6964 20.1334 15.75 19.9999 15.75C19.8663 15.75 19.6554 15.6964 19.3861 15.5388C19.1238 15.3853 18.8471 15.1564 18.5943 14.8725C18.0738 14.2881 17.7499 13.5832 17.7499 13Z" fill="#8F9AA3"/>
<path d="M24.536 16.4361C24.2245 16.163 23.7507 16.1941 23.4776 16.5056C23.2045 16.817 23.2356 17.2909 23.5471 17.5639C24.916 18.7642 25.6946 20.6997 24.9738 22.4145C24.8973 22.5965 24.7277 22.7039 24.5514 22.7039C24.4876 22.7039 24.4316 22.7013 24.3593 22.6978C24.3058 22.6953 24.2432 22.6923 24.1618 22.6897C24.0028 22.6845 23.7976 22.6828 23.5892 22.7127C23.3824 22.7424 23.1208 22.8099 22.8813 22.9807C22.6237 23.1644 22.4437 23.4309 22.3633 23.7588L21.266 28.2337C21.1173 28.8402 20.5889 29.25 20.0001 29.25C19.4112 29.25 18.8828 28.8402 18.7341 28.2337L17.6368 23.7588C17.5564 23.4309 17.3764 23.1644 17.1188 22.9807C16.8793 22.8099 16.6177 22.7424 16.4109 22.7127C16.2025 22.6828 15.9973 22.6845 15.8383 22.6897C15.7569 22.6923 15.6944 22.6953 15.6408 22.6978C15.5685 22.7013 15.5125 22.7039 15.4487 22.7039C15.2724 22.7039 15.1028 22.5965 15.0263 22.4145C14.3055 20.6997 15.0841 18.7642 16.4531 17.5639C16.7645 17.2909 16.7956 16.817 16.5226 16.5056C16.2495 16.1941 15.7756 16.163 15.4642 16.4361C13.7871 17.9065 12.5919 20.4941 13.6435 22.9958C13.9489 23.7223 14.6555 24.2039 15.4487 24.2039C15.5413 24.2039 15.6638 24.1985 15.7652 24.1939C15.8118 24.1918 15.8541 24.1899 15.8869 24.1889C16.0232 24.1845 16.1226 24.1867 16.1979 24.1975L16.2 24.1978L17.2772 28.591C17.5861 29.8506 18.7022 30.75 20.0001 30.75C21.2979 30.75 22.414 29.8506 22.7229 28.591L23.8001 24.1978L23.8022 24.1975C23.8775 24.1867 23.9769 24.1845 24.1132 24.1889C24.146 24.1899 24.1881 24.1918 24.2346 24.1939C24.336 24.1984 24.4588 24.2039 24.5514 24.2039C25.3446 24.2039 26.0512 23.7223 26.3566 22.9958C27.4082 20.4941 26.213 17.9065 24.536 16.4361Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.46313 15.0645C2.87679 15.044 3.22877 15.3627 3.24927 15.7764C3.35231 17.8551 3.66017 18.906 4.3772 19.623C5.09424 20.34 6.14517 20.6479 8.22388 20.751C8.63755 20.7715 8.95629 21.1234 8.93579 21.5371C8.91528 21.9508 8.56333 22.2695 8.14966 22.249C6.02926 22.1439 4.4621 21.829 3.31665 20.6836C2.17121 19.5382 1.85633 17.971 1.75122 15.8506C1.73072 15.4369 2.04943 15.085 2.46313 15.0645ZM21.5374 15.0645C21.951 15.085 22.2698 15.4369 22.2493 15.8506C22.1442 17.971 21.8292 19.5382 20.6838 20.6836C19.5384 21.829 17.9712 22.1439 15.8508 22.249C15.4371 22.2695 15.0852 21.9508 15.0647 21.5371C15.0442 21.1234 15.3629 20.7715 15.7766 20.751C17.8553 20.6479 18.9063 20.3401 19.6233 19.623C20.3403 18.906 20.6482 17.855 20.7512 15.7764C20.7717 15.3627 21.1236 15.044 21.5374 15.0645ZM12.0002 3.90039C13.1582 3.90046 14.0999 4.84198 14.0999 6C14.0998 6.84086 13.6022 7.56538 12.887 7.90039H13.136C14.762 7.90045 16.0999 9.11395 16.0999 10.624L16.0999 13.9297C16.0998 14.2828 15.7901 14.5546 15.427 14.5547H14.8362L14.4954 19.5156C14.4719 19.8513 14.1707 20.0996 13.8235 20.0996H10.1956C9.84928 20.0994 9.54928 19.8523 9.52466 19.5176V19.5166L9.16626 14.5547H8.57349C8.21034 14.5547 7.90065 14.2828 7.90063 13.9297L7.90063 10.624C7.90063 9.11391 9.23851 7.90039 10.8645 7.90039H11.1135C10.3982 7.56541 9.90065 6.84092 9.90063 6C9.90063 4.84194 10.8422 3.90039 12.0002 3.90039ZM14.3948 19.5078C14.3902 19.5747 14.3712 19.6378 14.342 19.6953C14.3561 19.6675 14.3685 19.6387 14.3772 19.6084L14.3958 19.5078L14.7434 14.4551L14.3948 19.5078ZM10.8645 9.14941C9.96391 9.14941 9.24536 9.81932 9.24536 10.624L9.24536 13.3057H9.79517C10.1415 13.3058 10.4415 13.5529 10.4661 13.8877V13.8887L10.8245 18.8506H13.1926L13.5334 13.8896C13.5569 13.5537 13.858 13.3047 14.2053 13.3047H14.7542L14.7542 10.624C14.7542 9.81935 14.0367 9.14947 13.136 9.14941H10.8645ZM8.00903 14.0225C8.00955 14.0251 8.01043 14.0277 8.01099 14.0303C8.00412 13.9978 8.00024 13.964 8.00024 13.9297L8.00903 14.0225ZM8.14966 1.75098C8.56334 1.73051 8.91529 2.04921 8.93579 2.46289C8.95628 2.87656 8.63754 3.22849 8.22388 3.24902C6.14515 3.35206 5.09424 3.65995 4.3772 4.37695C3.66016 5.09399 3.35231 6.14489 3.24927 8.22363C3.22877 8.6373 2.87679 8.95598 2.46313 8.93555C2.04943 8.91504 1.73072 8.56312 1.75122 8.14941C1.85633 6.02899 2.1712 4.46186 3.31665 3.31641C4.4621 2.17099 6.02924 1.85608 8.14966 1.75098ZM15.8508 1.75098C17.9712 1.85609 19.5384 2.17098 20.6838 3.31641C21.8292 4.46185 22.1442 6.02903 22.2493 8.14941C22.2698 8.56308 21.951 8.91498 21.5374 8.93555C21.1236 8.95605 20.7717 8.63734 20.7512 8.22363C20.6482 6.14495 20.3403 5.09399 19.6233 4.37695C18.9063 3.65993 17.8553 3.35207 15.7766 3.24902C15.3629 3.22852 15.0442 2.87659 15.0647 2.46289C15.0852 2.04919 15.4371 1.73047 15.8508 1.75098ZM12.0002 5.09961C11.504 5.09961 11.0999 5.50374 11.0999 6C11.0999 6.49625 11.504 6.90039 12.0002 6.90039C12.4964 6.90032 12.9006 6.49622 12.9006 6C12.9006 5.50378 12.4965 5.09968 12.0002 5.09961ZM10.0364 6.37793C10.0371 6.38156 10.0386 6.38505 10.0393 6.38867C10.0273 6.32802 10.0173 6.26657 10.011 6.2041L10.0364 6.37793Z" fill="#2B353E"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -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:

@ -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{
}
}
}
}

@ -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
//}

@ -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) {
}
}
}

@ -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
}
}
}
}
}

@ -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
}
}

@ -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()
}

@ -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)
}
}

@ -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)
}
}

@ -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<CLRegion>{
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")
}
}
}
}
}

@ -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)
}
})
}
}

@ -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("")
}
}
}

@ -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
}
}

@ -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)
}
}
}
}

@ -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")
// }
//}

@ -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)")
}
}

@ -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()
}
}

@ -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 = "<group>"; };
478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
478CFA952E6E20A60064F3D7 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMGPenguinInPlatformBridge.swift; sourceTree = "<group>"; };
61243B422EC5FA3700D46FA0 /* PenguinModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinModel.swift; sourceTree = "<group>"; };
61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinNavigator.swift; sourceTree = "<group>"; };
61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinPlugin.swift; sourceTree = "<group>"; };
61243B452EC5FA3700D46FA0 /* PenguinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinView.swift; sourceTree = "<group>"; };
61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenguinViewFactory.swift; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
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 = "<group>"; };
766D8CB32EC60BE600D05E07 /* Penguin.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Penguin.xcframework; path = Frameworks/Penguin.xcframework; sourceTree = "<group>"; };
766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenNavUI.xcframework; path = Frameworks/PenNavUI.xcframework; sourceTree = "<group>"; };
766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = PenguinINRenderer.xcframework; path = Frameworks/PenguinINRenderer.xcframework; sourceTree = "<group>"; };
769C9BF82E6F106D009F68A9 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
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 = "<group>"; };
@ -62,7 +86,7 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; };
/* 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 = "<group>";
};
61243B412EC5FA3700D46FA0 /* Helper */ = {
isa = PBXGroup;
children = (
61243B3D2EC5FA3700D46FA0 /* HMGPenguinInPlatformBridge.swift */,
);
path = Helper;
sourceTree = "<group>";
};
61243B472EC5FA3700D46FA0 /* Penguin */ = {
isa = PBXGroup;
children = (
61243B422EC5FA3700D46FA0 /* PenguinModel.swift */,
61243B432EC5FA3700D46FA0 /* PenguinNavigator.swift */,
61243B442EC5FA3700D46FA0 /* PenguinPlugin.swift */,
61243B452EC5FA3700D46FA0 /* PenguinView.swift */,
61243B462EC5FA3700D46FA0 /* PenguinViewFactory.swift */,
);
path = Penguin;
sourceTree = "<group>";
};
766D8CB22EC60BE600D05E07 /* Frameworks */ = {
isa = PBXGroup;
children = (
766D8CB32EC60BE600D05E07 /* Penguin.xcframework */,
766D8CB52EC60BE600D05E07 /* PenguinINRenderer.xcframework */,
766D8CB42EC60BE600D05E07 /* PenNavUI.xcframework */,
D562310E31D1DDEFA02A6C12 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
79DD2093A1D9674C94359FC8 /* Pods */ = {
isa = PBXGroup;
children = (
@ -115,7 +173,7 @@
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
79DD2093A1D9674C94359FC8 /* Pods */,
A07D637C76A0ABB38659D189 /* Frameworks */,
766D8CB22EC60BE600D05E07 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -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 = "<group>";
};
A07D637C76A0ABB38659D189 /* Frameworks */ = {
isa = PBXGroup;
children = (
ACE60DF9393168FD748550B3 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* 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;

@ -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)

@ -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{
}
}
}
}

@ -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
//}

@ -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) {
}
}
}

@ -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
}
}
}
}
}

@ -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
}
}

@ -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()
}

@ -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)
}
}

@ -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)
}
}

@ -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<CLRegion>{
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")
}
}
}
}
}

@ -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)
}
})
}
}

@ -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("")
}
}
}

@ -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
}
}

@ -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)
}
}
}
}

@ -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")
// }
//}

@ -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)")
}
}

@ -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()
}
}

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.in-app-payments</key>
<array>
<string>merchant.com.hmgwebservices</string>
<string>merchant.com.hmgwebservices.uat</string>
</array>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>
</array>
</dict>
</plist>

@ -661,7 +661,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -860,6 +860,12 @@ class ApiConsts {
static String deactivateWeightMeasurementStatus = 'Services/Patients.svc/REST/Patient_DeactivateWeightMeasurementStatus';
static String sendAverageBodyWeightReport = 'Services/Notifications.svc/REST/SendAverageBodyWeightReport';
//Blood Donation
static String bloodGroupUpdate = "Services/PatientVarification.svc/REST/BloodDonation_RegisterBloodType";
static String userAgreementForBloodGroupUpdate = "Services/PatientVarification.svc/REST/AddUserAgreementForBloodDonation";
static String getProjectsHaveBDClinics = "Services/OUTPs.svc/REST/BD_getProjectsHaveBDClinics";
static String getClinicsBDFreeSlots = "Services/OUTPs.svc/REST/BD_GetFreeSlots";
// ************ static values for Api ****************
static final double appVersionID = 50.3;
static final int appChannelId = 3;

@ -218,6 +218,8 @@ class AppAssets {
static const String activity = '$svgBasePath/activity.svg';
static const String age = '$svgBasePath/age_icon.svg';
static const String gender = '$svgBasePath/gender_icon.svg';
static const String genderInputIcon = '$svgBasePath/genderInputIcon.svg';
static const String bloodType = '$svgBasePath/blood_type.svg';
static const String trade_down_yellow = '$svgBasePath/trade_down_yellow.svg';
static const String trade_down_red = '$svgBasePath/trade_down_red.svg';
@ -247,6 +249,7 @@ class AppAssets {
static const String refreshIcon = '$svgBasePath/refresh.svg';
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
static const String symptomCheckerIcon = '$svgBasePath/symptom_checker_icon.svg';
static const String symptomCheckerBottomIcon = '$svgBasePath/symptom_bottom_icon.svg';
// Water Monitor
static const String waterBottle = '$svgBasePath/water_bottle.svg';

@ -75,6 +75,7 @@ class CacheConst {
static const String patientOccupationList = 'patient-occupation-list';
static const String hasEnabledQuickLogin = 'has-enabled-quick-login';
static const String quickLoginEnabled = 'quick-login-enabled';
static const String isMonthlyReportEnabled = 'is-monthly-report-enabled';
static const String zoomRoomID = 'zoom-room-id';
static String isAppOpenedFromCall = "is_app_opened_from_call";

@ -31,6 +31,8 @@ import 'package:hmg_patient_app_new/features/location/location_repo.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
@ -144,6 +146,7 @@ class AppDependencies {
getIt.registerLazySingleton<WaterMonitorRepo>(() => WaterMonitorRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MyInvoicesRepo>(() => MyInvoicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HealthTrackersRepo>(() => HealthTrackersRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MonthlyReportRepo>(() => MonthlyReportRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -154,16 +157,13 @@ class AppDependencies {
() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
);
getIt.registerLazySingleton<PrescriptionsViewModel>(
() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<PrescriptionsViewModel>(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<InsuranceViewModel>(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<MyAppointmentsViewModel>(
() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<MyAppointmentsViewModel>(() => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<AppointmentRatingViewModel>(
() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<AppointmentRatingViewModel>(() => AppointmentRatingViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()));
getIt.registerLazySingleton<PayfortViewModel>(
() => PayfortViewModel(payfortRepo: getIt(), errorHandlerService: getIt()),
@ -182,12 +182,7 @@ class AppDependencies {
getIt.registerLazySingleton<BookAppointmentsViewModel>(
() => BookAppointmentsViewModel(
bookAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt(),
myAppointmentsViewModel: getIt(),
locationUtils: getIt(),
dialogService: getIt()),
bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()),
);
getIt.registerLazySingleton<ImmediateLiveCareViewModel>(
@ -201,13 +196,7 @@ class AppDependencies {
getIt.registerLazySingleton<AuthenticationViewModel>(
() => AuthenticationViewModel(
authenticationRepo: getIt(),
cacheService: getIt(),
navigationService: getIt(),
dialogService: getIt(),
appState: getIt(),
errorHandlerService: getIt(),
localAuthService: getIt()),
authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()),
);
getIt.registerLazySingleton<ProfileSettingsViewModel>(() => ProfileSettingsViewModel());
@ -268,6 +257,7 @@ class AppDependencies {
navigationService: getIt(),
dialogService: getIt(),
appState: getIt(),
navServices: getIt(),
),
);
@ -275,6 +265,9 @@ class AppDependencies {
getIt.registerLazySingleton<WaterMonitorViewModel>(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<MonthlyReportViewModel>(() => MonthlyReportViewModel(errorHandlerService: getIt(), monthlyReportRepo: getIt()));
getIt.registerLazySingleton<MyInvoicesViewModel>(() => MyInvoicesViewModel(
myInvoicesRepo: getIt(),
errorHandlerService: getIt(),

@ -0,0 +1,105 @@
import 'package:flutter/services.dart';
class PenguinMethodChannel {
static const MethodChannel _channel = MethodChannel('launch_penguin_ui');
Future<Uint8List> loadGif() async {
return await rootBundle.load("assets/images/progress-loading-red-crop-1.gif").then((data) => data.buffer.asUint8List());
}
Future<void> 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<dynamic, dynamic> 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;
}

@ -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,
@ -304,7 +348,7 @@ class Utils {
children: [
SizedBox(height: isSmallWidget ? 0.h : 48.h),
Lottie.asset(AppAnimations.noData,
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill),
repeat: false, reverse: false, frameRate: FrameRate(60), width: width.w, height: height.h, fit: BoxFit.fill),
SizedBox(height: 16.h),
(noDataText ?? LocaleKeys.noDataAvailable.tr())
.toText14(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true)

@ -192,7 +192,8 @@ extension EmailValidator on String {
letterSpacing: letterSpacing,
height: height,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null),
decoration: isUnderLine ? TextDecoration.underline : null,
decorationColor: color ?? AppColors.blackColor),
);
Widget toText15(

@ -3,14 +3,26 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class BloodDonationRepo {
Future<Either<Failure, GenericApiModel<List<CitiesModel>>>> getAllCities();
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getProjectList();
Future<Either<Failure, GenericApiModel<List<BdGetProjectsHaveBdClinic>>>> getBloodDonationProjectsList();
Future<Either<Failure, GenericApiModel<List_BloodGroupDetailsModel>>> getPatientBloodGroupDetails();
Future<Either<Failure, GenericApiModel<dynamic>>> updateBloodGroup({required Map<String, dynamic> request});
Future<Either<Failure, GenericApiModel<dynamic>>> getFreeBloodDonationSlots({required Map<String, dynamic> request});
Future<Either<Failure, GenericApiModel<dynamic>>> addUserAgreementForBloodDonation({required Map<String, dynamic> request});
}
class BloodDonationRepoImp implements BloodDonationRepo {
@ -93,4 +105,186 @@ class BloodDonationRepoImp implements BloodDonationRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getProjectList() async {
Map<String, dynamic> request = {};
try {
GenericApiModel<List<HospitalsModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PROJECT_LIST,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListProject'];
final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<HospitalsModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: appointmentsList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<BdGetProjectsHaveBdClinic>>>> getBloodDonationProjectsList() async {
Map<String, dynamic> request = {};
try {
GenericApiModel<List<BdGetProjectsHaveBdClinic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getProjectsHaveBDClinics,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final listData = (response['BD_getProjectsHaveBDClinics'] as List<dynamic>);
final list = listData.map((item) => BdGetProjectsHaveBdClinic.fromJson(item as Map<String, dynamic>)).toList();
apiResponse = GenericApiModel<List<BdGetProjectsHaveBdClinic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: list,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<dynamic>>>> updateBloodGroup({required Map<String, dynamic> request}) async {
try {
GenericApiModel<List<dynamic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.bloodGroupUpdate,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['ListProject'];
// final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<dynamic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<dynamic>>>> getFreeBloodDonationSlots({required Map<String, dynamic> request}) async {
try {
GenericApiModel<List<dynamic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getClinicsBDFreeSlots,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['ListProject'];
// final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<dynamic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<dynamic>>>> addUserAgreementForBloodDonation({required Map<String, dynamic> request}) async {
try {
GenericApiModel<List<dynamic>>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.userAgreementForBloodGroupUpdate,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['ListProject'];
// final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map<String, dynamic>)).toList().cast<HospitalsModel>();
apiResponse = GenericApiModel<List<dynamic>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -1,15 +1,25 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_repo.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_list_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_response_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/cities_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class BloodDonationViewModel extends ChangeNotifier {
final DialogService dialogService;
@ -17,8 +27,21 @@ class BloodDonationViewModel extends ChangeNotifier {
ErrorHandlerService errorHandlerService;
final NavigationService navigationService;
final AppState appState;
bool isTermsAccepted = false;
BdGetProjectsHaveBdClinic? selectedHospital;
CitiesModel? selectedCity;
BloodGroupListModel? selectedBloodGroup;
int _selectedHospitalIndex = 0;
int _selectedBloodTypeIndex = 0;
GenderTypeEnum? selectedGender;
String? selectedBloodType;
final NavigationService navServices;
List<BdGetProjectsHaveBdClinic> hospitalList = [];
List<CitiesModel> citiesList = [];
List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel();
List<BloodGroupListModel> bloodGroupList = [
BloodGroupListModel("O+", 0),
BloodGroupListModel("O-", 1),
@ -30,35 +53,34 @@ class BloodDonationViewModel extends ChangeNotifier {
BloodGroupListModel("B-", 7),
];
List<BloodGroupListModel> genderList = [
BloodGroupListModel(LocaleKeys.malE.tr(), 1),
BloodGroupListModel("Female".needTranslation.tr(), 2),
];
late CitiesModel selectedCity;
late BloodGroupListModel selectedBloodGroup;
int _selectedHospitalIndex = 0;
int _selectedBloodTypeIndex = 0;
String selectedBloodType = '';
List_BloodGroupDetailsModel patientBloodGroupDetailsModel = List_BloodGroupDetailsModel();
BloodDonationViewModel({required this.bloodDonationRepo, required this.errorHandlerService, required this.navigationService, required this.dialogService, required this.appState});
BloodDonationViewModel({
required this.bloodDonationRepo,
required this.errorHandlerService,
required this.navigationService,
required this.dialogService,
required this.appState,
required this.navServices,
});
setSelectedCity(CitiesModel city) {
selectedCity = city;
notifyListeners();
}
void onGenderChange(String? status) {
selectedGender = GenderTypeExtension.fromType(status)!;
notifyListeners();
}
setSelectedBloodGroup(BloodGroupListModel bloodGroup) {
selectedBloodGroup = bloodGroup;
selectedBloodType = selectedBloodGroup.name;
selectedBloodType = selectedBloodGroup!.name;
notifyListeners();
}
Future<void> getRegionSelectedClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async {
citiesList.clear();
selectedCity = CitiesModel();
selectedCity = null;
notifyListeners();
final result = await bloodDonationRepo.getAllCities();
@ -71,6 +93,7 @@ class BloodDonationViewModel extends ChangeNotifier {
onError!(apiResponse.errorMessage ?? 'An unexpected error occurred');
} else if (apiResponse.messageStatus == 1) {
citiesList = apiResponse.data!;
citiesList.sort((a, b) => a.description!.compareTo(b.description!));
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -100,7 +123,7 @@ class BloodDonationViewModel extends ChangeNotifier {
citiesModel.descriptionN = citiesList[_selectedHospitalIndex].descriptionN;
selectedCity = citiesModel;
selectedBloodType = patientBloodGroupDetailsModel.bloodGroup!;
_selectedBloodTypeIndex = getBloodIndex(selectedBloodType);
_selectedBloodTypeIndex = getBloodIndex(selectedBloodType ?? '');
notifyListeners();
if (onSuccess != null) {
@ -113,11 +136,11 @@ class BloodDonationViewModel extends ChangeNotifier {
int getSelectedCityID() {
int cityID = 1;
citiesList.forEach((element) {
for (var element in citiesList) {
if (element.description == patientBloodGroupDetailsModel.city) {
cityID = element.iD!;
}
});
}
return cityID;
}
@ -144,4 +167,120 @@ class BloodDonationViewModel extends ChangeNotifier {
return 0;
}
}
void onTermAccepted() {
isTermsAccepted = !isTermsAccepted;
notifyListeners();
}
bool isUserAuthanticated() {
print("the app state is ${appState.isAuthenticated}");
if (!appState.isAuthenticated) {
return false;
} else {
return true;
}
}
Future<void> fetchHospitalsList() async {
// hospitalList.clear();
notifyListeners();
final result = await bloodDonationRepo.getBloodDonationProjectsList();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
hospitalList = apiResponse.data!;
hospitalList.sort((a, b) => a.projectName!.compareTo(b.projectName!));
notifyListeners();
}
},
);
}
Future<void> getFreeBloodDonationSlots({required Map<String, dynamic> request}) async {
final result = await bloodDonationRepo.getFreeBloodDonationSlots(request: request);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
} else if (apiResponse.messageStatus == 1) {
// TODO: Handle free slots data
print(apiResponse.data['BD_FreeSlots']);
notifyListeners();
}
},
);
}
bool isLocationEnabled() {
return appState.userLong != 0.0 && appState.userLong != 0.0;
}
setSelectedHospital(BdGetProjectsHaveBdClinic hospital) {
selectedHospital = hospital;
notifyListeners();
}
Future<bool> validateSelections() async {
if (selectedCity == null) {
await dialogService.showErrorBottomSheet(
message: "Please choose city",
);
return false;
}
if (selectedBloodGroup == null) {
await dialogService.showErrorBottomSheet(
message: "Please choose Gender",
);
return false;
}
if (selectedBloodType == null) {
await dialogService.showErrorBottomSheet(
message: "Please choose Blood Group",
);
return false;
}
if (!isTermsAccepted) {
await dialogService.showErrorBottomSheet(
message: "Please accept Terms and Conditions to continue",
);
return false;
}
return true;
}
Future<void> updateBloodGroup() async {
LoaderBottomSheet.showLoader();
// body['City'] = detailsModel.city;
// body['cityCode'] = detailsModel.cityCode;
// body['Gender'] = detailsModel.gender;
// body['BloodGroup'] = detailsModel.bloodGroup;
// body['CellNumber'] = user.mobileNumber;
// body['LanguageID'] = languageID;
// body['NationalID'] = user.nationalityID;
// body['ZipCode'] = user.zipCode ?? "+966";
// body['isDentalAllowedBackend'] = false;
Map<String, dynamic> payload = {
"City": selectedCity?.description,
"cityCode": selectedCity?.iD,
"Gender": selectedGender?.value,
"isDentalAllowedBackend": false
// "Gender": selectedGender?.value,
};
await bloodDonationRepo.updateBloodGroup(request: payload);
await addUserAgreementForBloodDonation();
LoaderBottomSheet.hideLoader();
}
Future<void> addUserAgreementForBloodDonation() async {
Map<String, dynamic> payload = {"IsAgreed": true};
await bloodDonationRepo.addUserAgreementForBloodDonation(request: payload);
}
}

@ -0,0 +1,81 @@
import 'dart:convert';
class BdProjectsHaveBdClinicsModel {
List<BdGetProjectsHaveBdClinic>? bdGetProjectsHaveBdClinics;
BdProjectsHaveBdClinicsModel({
this.bdGetProjectsHaveBdClinics,
});
factory BdProjectsHaveBdClinicsModel.fromRawJson(String str) => BdProjectsHaveBdClinicsModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory BdProjectsHaveBdClinicsModel.fromJson(Map<String, dynamic> json) => BdProjectsHaveBdClinicsModel(
bdGetProjectsHaveBdClinics: json["BD_getProjectsHaveBDClinics"] == null ? [] : List<BdGetProjectsHaveBdClinic>.from(json["BD_getProjectsHaveBDClinics"]!.map((x) => BdGetProjectsHaveBdClinic.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"BD_getProjectsHaveBDClinics": bdGetProjectsHaveBdClinics == null ? [] : List<dynamic>.from(bdGetProjectsHaveBdClinics!.map((x) => x.toJson())),
};
}
class BdGetProjectsHaveBdClinic {
int? rowId;
int? id;
int? projectId;
int? numberOfRooms;
bool? isActive;
int? createdBy;
String? createdOn;
dynamic editedBy;
dynamic editedOn;
String? projectName;
dynamic projectNameN;
BdGetProjectsHaveBdClinic({
this.rowId,
this.id,
this.projectId,
this.numberOfRooms,
this.isActive,
this.createdBy,
this.createdOn,
this.editedBy,
this.editedOn,
this.projectName,
this.projectNameN,
});
factory BdGetProjectsHaveBdClinic.fromRawJson(String str) => BdGetProjectsHaveBdClinic.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory BdGetProjectsHaveBdClinic.fromJson(Map<String, dynamic> json) => BdGetProjectsHaveBdClinic(
rowId: json["RowID"],
id: json["ID"],
projectId: json["ProjectID"],
numberOfRooms: json["NumberOfRooms"],
isActive: json["IsActive"],
createdBy: json["CreatedBy"],
createdOn: json["CreatedOn"],
editedBy: json["EditedBy"],
editedOn: json["EditedON"],
projectName: json["ProjectName"],
projectNameN: json["ProjectNameN"],
);
Map<String, dynamic> toJson() => {
"RowID": rowId,
"ID": id,
"ProjectID": projectId,
"NumberOfRooms": numberOfRooms,
"IsActive": isActive,
"CreatedBy": createdBy,
"CreatedOn": createdOn,
"EditedBy": editedBy,
"EditedON": editedOn,
"ProjectName": projectName,
"ProjectNameN": projectNameN,
};
}

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.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/core/utils/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/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:provider/provider.dart';
class HospitalBottomSheetBodySelection extends StatelessWidget {
final Function(BdGetProjectsHaveBdClinic userSelection) onUserHospitalSelection;
const HospitalBottomSheetBodySelection({super.key, required this.onUserHospitalSelection(BdGetProjectsHaveBdClinic userSelection)});
@override
Widget build(BuildContext context) {
final bloodDonationVm = Provider.of<BloodDonationViewModel>(context, listen: false);
AppState appState = getIt.get<AppState>();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Please select the hospital you want to make an appointment.".needTranslation,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
SizedBox(height: 16.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [
hospitalName(bloodDonationVm.hospitalList[index]).onPress(() {
onUserHospitalSelection(bloodDonationVm.hospitalList[index]);
Navigator.of(context).pop();
})
],
),
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.blackColor, width: 40.h, height: 40.h, fit: BoxFit.contain),
),
],
).paddingSymmetrical(16.h, 16.h),
).onPress(() {
bloodDonationVm.setSelectedHospital(bloodDonationVm.hospitalList[index]);
Navigator.of(context).pop();
});
},
separatorBuilder: (_, __) => SizedBox(height: 16.h),
itemCount: bloodDonationVm.hospitalList.length),
)
],
);
}
Widget hospitalName(dynamic hospital) => Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.hmg).paddingOnly(right: 10),
Expanded(
child: Text(hospital.projectName ?? "", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: AppColors.blackColor)),
)
],
);
}

@ -49,6 +49,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
bool isLiveCareSchedule = false;
bool isGetDocForHealthCal = false;
bool showSortFilterButtons = false;
int? calculationID = 0;
bool isSortByClinic = true;
@ -200,8 +201,10 @@ class BookAppointmentsViewModel extends ChangeNotifier {
void filterClinics(String? query) {
if (query!.isEmpty) {
_filteredClinicsList = List.from(clinicsList);
showSortFilterButtons = false;
} else {
_filteredClinicsList = clinicsList.where((clinic) => clinic.clinicDescription?.toLowerCase().contains(query!.toLowerCase()) ?? false).toList();
showSortFilterButtons = query.length >= 3;
}
notifyListeners();
}

@ -0,0 +1,27 @@
import 'package:flutter/cupertino.dart';
import 'package:permission_handler/permission_handler.dart';
class AppPermission {
static Future<bool> 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<bool> 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;
}
}

@ -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<HospitalsModel> displayList = [];
List<HospitalsModel> listOfData = [];
List<HospitalsModel> hmgHospitalList = [];
List<HospitalsModel> 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<HospitalsModel> 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<Permission, PermissionStatus> 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);
});
}
}
}

@ -293,14 +293,13 @@ class MedicalFileRepoImp implements MedicalFileRepo {
Failure? failure;
await apiClient.post(
ApiConsts.getAllSharedRecordsByStatus,
body: {if (status != null) "Status": status, "PatientID": patientID},
body: {if (status != null) "Status": status, "PatientID": patientID.toString()},
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['GetAllSharedRecordsByStatusList'];
final list = response['GetAllSharedRecordsByStatusList'] ?? [];
final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map<String, dynamic>)).toList().cast<FamilyFileResponseModelLists>();
@ -336,7 +335,7 @@ class MedicalFileRepoImp implements MedicalFileRepo {
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['GetAllPendingRecordsList'];
final list = response['GetAllPendingRecordsList'] ?? [];
final familyLists = list.map((item) => FamilyFileResponseModelLists.fromJson(item as Map<String, dynamic>)).toList().cast<FamilyFileResponseModelLists>();
apiResponse = GenericApiModel<List<FamilyFileResponseModelLists>>(

@ -0,0 +1,53 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class MonthlyReportRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> updatePatientHealthSummaryReport({required bool rSummaryReport});
}
class MonthlyReportRepoImp implements MonthlyReportRepo {
final ApiClient apiClient;
final LoggerService loggerService;
MonthlyReportRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<dynamic>>> updatePatientHealthSummaryReport({required bool rSummaryReport}) async {
Map<String, dynamic> mapDevice = {
"RSummaryReport": rSummaryReport,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
UPDATE_HEALTH_TERMS,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class MonthlyReportViewModel extends ChangeNotifier {
MonthlyReportRepo monthlyReportRepo;
ErrorHandlerService errorHandlerService;
bool isUpdateHealthSummaryLoading = false;
bool isHealthSummaryEnabled = false;
MonthlyReportViewModel({
required this.monthlyReportRepo,
required this.errorHandlerService,
});
setHealthSummaryEnabled(bool value) {
isHealthSummaryEnabled = value;
notifyListeners();
}
Future<void> updatePatientHealthSummaryReport({
required bool rSummaryReport,
Function(dynamic)? onSuccess,
Function(String)? onError,
}) async {
isUpdateHealthSummaryLoading = true;
notifyListeners();
final result = await monthlyReportRepo.updatePatientHealthSummaryReport(
rSummaryReport: rSummaryReport,
);
result.fold(
(failure) async {
isUpdateHealthSummaryLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isUpdateHealthSummaryLoading = false;
if (apiResponse.messageStatus == 2) {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? "Unknown error");
}
} else if (apiResponse.messageStatus == 1) {
// Update the local state on success
isHealthSummaryEnabled = rSummaryReport;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
@override
void dispose() {
super.dispose();
}
}

@ -22,6 +22,7 @@ import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.d
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -177,6 +178,9 @@ void main() async {
),
ChangeNotifierProvider<HealthTrackersViewModel>(
create: (_) => getIt.get<HealthTrackersViewModel>(),
),
ChangeNotifierProvider<MonthlyReportViewModel>(
create: (_) => getIt.get<MonthlyReportViewModel>(),
)
], child: MyApp()),
),

@ -401,7 +401,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: tamaraOrderID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,

@ -358,14 +358,14 @@ class AppointmentCard extends StatelessWidget {
backgroundColor: AppColors.greyColor,
borderColor: AppColors.greyColor,
textColor: AppColors.blackColor,
fontSize: 12.f,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
icon: AppAssets.rebook_appointment_icon,
iconColor: AppColors.blackColor,
iconSize: 14.h,
iconSize: 16.h,
);
}

@ -7,16 +7,26 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/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/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
import 'package:hmg_patient_app_new/features/blood_donation/widgets/hospital_selection.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_blood_group_widget.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_gender_widget.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
class BloodDonationPage extends StatelessWidget {
BloodDonationPage({super.key});
@ -25,7 +35,7 @@ class BloodDonationPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
appState = getIt<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<BloodDonationViewModel>(builder: (context, bloodDonationVM, child) {
@ -34,15 +44,83 @@ class BloodDonationPage extends StatelessWidget {
Expanded(
child: CollapsingListView(
title: LocaleKeys.bloodDonation.tr(),
trailing: CustomButton(
text: "Book",
onPressed: () {
// if (bloodDonationVM.isUserAuthanticated()) {
bloodDonationVM.fetchHospitalsList().then((value) {
showCommonBottomSheetWithoutHeight(context, title: "Select Hospital", isDismissible: false, child: Consumer<BloodDonationViewModel>(builder: (_, data, __) {
return HospitalBottomSheetBodySelection(
onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) {
print("============User Choice===============");
bloodDonationVM.getFreeBloodDonationSlots(request: {"ClinicID": 134, "ProjectID": userChoice.projectId});
},
);
}), callBackFunc: () {});
});
// } else {
// return showCommonBottomSheetWithoutHeight(
// context,
// title: LocaleKeys.notice.tr(context: context),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: [
// Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
// SizedBox(height: 8.h),
// (LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor),
// SizedBox(height: 16.h),
// Row(
// children: [
// Expanded(
// child: CustomButton(
// text: LocaleKeys.cancel.tr(),
// onPressed: () {
// Navigator.of(context).pop();
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.primaryRedColor,
// ),
// ),
// SizedBox(width: 8.h),
// Expanded(
// child: CustomButton(
// text: LocaleKeys.confirm.tr(),
// onPressed: () async {
// Navigator.of(context).pop();
// // Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
// await getIt<AuthenticationViewModel>().onLoginPressed();
// },
// backgroundColor: AppColors.bgGreenColor,
// borderColor: AppColors.bgGreenColor,
// textColor: Colors.white,
// icon: AppAssets.confirm,
// ),
// ),
// ],
// ),
// SizedBox(height: 16.h),
// ],
// ).center,
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// }
},
backgroundColor: AppColors.bgRedLightColor,
borderColor: AppColors.bgRedLightColor,
textColor: AppColors.primaryRedColor,
padding: EdgeInsetsGeometry.symmetric(vertical: 0.h, horizontal: 20.h)),
child: Padding(
padding: EdgeInsets.all(24.w),
child: SingleChildScrollView(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: false,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
@ -60,8 +138,8 @@ class BloodDonationPage extends StatelessWidget {
children: [
LocaleKeys.city.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedCity.descriptionN ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedCity.description ?? LocaleKeys.select.tr(context: context))
? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
@ -71,12 +149,7 @@ class BloodDonationPage extends StatelessWidget {
],
).onPress(() async {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.selectCity.tr(context: context),
isDismissible: true,
child: SelectCityWidget(
bloodDonationViewModel: bloodDonationVM,
),
callBackFunc: () {});
title: LocaleKeys.selectCity.tr(context: context), isDismissible: true, child: SelectCityWidget(bloodDonationViewModel: bloodDonationVM), callBackFunc: () {});
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
@ -86,13 +159,16 @@ class BloodDonationPage extends StatelessWidget {
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h),
Utils.buildSvgWithAssets(icon: AppAssets.genderInputIcon, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.gender.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
"Male".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
],
@ -103,10 +179,7 @@ class BloodDonationPage extends StatelessWidget {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.selectGender.tr(context: context),
isDismissible: true,
child: SelectGenderWidget(
isArabic: appState.isArabic(),
bloodDonationViewModel: bloodDonationVM,
),
child: SelectGenderWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});
}),
SizedBox(height: 16.h),
@ -117,13 +190,17 @@ class BloodDonationPage extends StatelessWidget {
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h),
Utils.buildSvgWithAssets(icon: AppAssets.bloodType, width: 40.h, height: 40.h),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
bloodDonationVM.selectedBloodType.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
// bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
(appState.isArabic()
? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr())
: bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context))
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500)
],
),
],
@ -134,10 +211,7 @@ class BloodDonationPage extends StatelessWidget {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.select.tr(context: context),
isDismissible: true,
child: SelectBloodGroupWidget(
isArabic: appState.isArabic(),
bloodDonationViewModel: bloodDonationVM,
),
child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});
}),
],
@ -149,19 +223,73 @@ class BloodDonationPage extends StatelessWidget {
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: SizedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: bloodDonationVM.onTermAccepted,
child: Row(
children: [
Selector<BloodDonationViewModel, bool>(
selector: (_, viewModel) => viewModel.isTermsAccepted,
shouldRebuild: (previous, next) => previous != next,
builder: (context, isTermsAccepted, child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 24.h,
width: 24.h,
decoration: BoxDecoration(
color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h),
),
child: isTermsAccepted ? Icon(Icons.check, size: 16.f, color: Colors.white) : null,
);
},
),
SizedBox(width: 12.h),
Row(
children: [
Text(
LocaleKeys.iAcceptThe.tr(),
style: context.dynamicTextStyle(fontSize: 14.f, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
),
GestureDetector(
onTap: () {
// Navigate to terms and conditions page
Navigator.of(context).pushNamed('/terms');
},
child: Text(
LocaleKeys.termsConditoins.tr(),
style: context.dynamicTextStyle(
fontSize: 14.f,
fontWeight: FontWeight.w500,
color: AppColors.primaryRedColor,
decoration: TextDecoration.underline,
decorationColor: AppColors.primaryRedBorderColor,
),
),
),
],
),
// Expanded(
// child: Text(
// LocaleKeys.iAcceptTermsConditions.tr().split("the").first,
// style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
// ),
// ),
],
),
).paddingOnly(left: 16.h, right: 16.h, top: 24.h),
CustomButton(
text: LocaleKeys.save.tr(),
onPressed: () {
// openDoctorScheduleCalendar();
onPressed: () async {
DialogService dialogService = getIt.get<DialogService>();
if (await bloodDonationVM.validateSelections()) {
bloodDonationVM.updateBloodGroup();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,

@ -26,9 +26,7 @@ class SelectCityWidget extends StatelessWidget {
Navigator.of(context).pop();
});
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
separatorBuilder: (_, __) => SizedBox(height: 8.h),
itemCount: bloodDonationViewModel.citiesList.length),
)
],

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
@ -8,10 +9,10 @@ import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_
import 'package:hmg_patient_app_new/theme/colors.dart';
class SelectGenderWidget extends StatelessWidget {
SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic});
const SelectGenderWidget({super.key, required this.bloodDonationViewModel, required this.isArabic});
BloodDonationViewModel bloodDonationViewModel;
bool isArabic;
final BloodDonationViewModel bloodDonationViewModel;
final bool isArabic;
@override
Widget build(BuildContext context) {
@ -20,46 +21,45 @@ class SelectGenderWidget extends StatelessWidget {
children: [
SizedBox(height: 8.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [bloodDonationViewModel.genderList[index].name.toText16(color: AppColors.textColor, isBold: true)],
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [GenderTypeEnum.values[index].name.toCamelCase.toText16(color: AppColors.textColor, isBold: true)],
),
),
),
Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 40.h,
height: 40.h,
fit: BoxFit.contain,
),
),
),
],
).paddingSymmetrical(16.h, 16.h).onPress(() {
// bloodDonationViewModel.setSelectedCity(bloodDonationViewModel.citiesList[index]);
Navigator.of(context).pop();
}));
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
itemCount: bloodDonationViewModel.genderList.length),
)
],
).paddingSymmetrical(16.h, 16.h).onPress(() {
bloodDonationViewModel.onGenderChange(GenderTypeEnum.values[index].name.toCamelCase);
Navigator.of(context).pop();
}));
},
separatorBuilder: (_, __) => SizedBox(
height: 8.h,
),
itemCount: GenderTypeEnum.values.length))
],
);
}

@ -112,10 +112,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true, radius: 50.r),
SizedBox(height: 8.h),
("Dr. John Smith Smith Smith")
.toString()
.toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2)
.toShimmer2(isShow: true),
("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true),
],
)
: myAppointmentsVM.patientMyDoctorsList.isEmpty
@ -241,10 +238,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
@ -276,10 +270,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false);
@ -309,10 +300,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setProjectID(null);
@ -332,124 +320,115 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Column(
children: [
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() async {
//TODO Implement API to check for existing LiveCare Requests
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() async {
//TODO Implement API to check for existing LiveCare Requests
LoaderBottomSheet.showLoader();
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
LoaderBottomSheet.showLoader();
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
Navigator.of(context).push(
CustomPageRoute(
page: SelectImmediateLiveCareClinicPage(),
),
);
}
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setIsLiveCareSchedule(true);
Navigator.of(context).push(
CustomPageRoute(
page: SelectClinicPage(),
),
);
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),
],
),
),
),
],
).paddingSymmetrical(24.h, 0.h)
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
Navigator.of(context).push(
CustomPageRoute(
page: SelectImmediateLiveCareClinicPage(),
),
);
}
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setIsLiveCareSchedule(true);
Navigator.of(context).push(
CustomPageRoute(
page: SelectClinicPage(),
),
);
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h),
SizedBox(width: 12.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500),
"".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500),
],
),
],
),
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),
],
),
),
),
],
).paddingSymmetrical(24.h, 0.h)
// : getLiveCareNotLoggedInUI()
;
default:
@ -493,10 +472,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
regionalViewModel.flush();
regionalViewModel.setBottomSheetType(type);
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context,
title: "",
titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)),
isDismissible: false, child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) => getTitle(data)), isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
}), callBackFunc: () {});
}
@ -582,9 +559,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true),
"No need to wait, you will get medical consultation immediately via video call"
.needTranslation
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
"No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),
@ -616,9 +591,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true),
"A specialised doctor will contact you and will be able to view your medical history"
.needTranslation
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
"A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),
@ -634,9 +607,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true),
"Offers free medicine delivery for the LiveCare appointment"
.needTranslation
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
"Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
),

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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/string_extensions.dart' show CapExtension;
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';

@ -182,8 +182,9 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
children: [
bookAppointmentsViewModel.selectedDoctor.projectName!.toText16(isBold: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
bookAppointmentsViewModel.appointmentNearestGateResponseModel != null
? Wrap(
direction: Axis.horizontal,
spacing: 8.w,
runSpacing: 8.h,
children: [
@ -197,7 +198,8 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
"Nearest Gate: ${getIt.get<AppState>().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}")
.toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading),
],
),
)
: SizedBox.shrink(),
],
),
),

@ -1211,9 +1211,7 @@ class _SelectClinicPageState extends State<SelectClinicPage> {
bookAppointmentsViewModel.setIsContinueDentalPlan(true);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: SelectDoctorPage(),
),
CustomPageRoute(page: SelectDoctorPage()),
);
},
backgroundColor: AppColors.bgGreenColor,

@ -40,9 +40,7 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
late AppState appState;
late BookAppointmentsViewModel bookAppointmentsViewModel;
// Scroll controller to control page scrolling when a group expands
late ScrollController _scrollController;
// Map of keys for each item to allow scrolling to them
final Map<int, GlobalKey> _itemKeys = {};
@override
@ -79,6 +77,20 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: "Choose Doctor".needTranslation,
// bottomChild: Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
// padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
// child: CustomButton(
// text: LocaleKeys.search.tr(),
// onPressed: () {
// },
// icon: null,
// fontSize: 16.f,
// backgroundColor: AppColors.primaryRedColor,
// borderColor: AppColors.primaryRedColor,
// borderRadius: 12.r,
// fontWeight: FontWeight.w500),
// ),
child: SingleChildScrollView(
controller: _scrollController,
child: Padding(
@ -124,40 +136,42 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
],
),
SizedBox(height: 16.h),
Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(true);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(false);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor,
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(0.h, 0.h),
SizedBox(height: 16.h),
if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons)
Row(
children: [
CustomButton(
text: LocaleKeys.byClinic.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(true);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.byHospital.tr(context: context),
onPressed: () {
bookAppointmentsVM.setIsSortByClinic(false);
},
backgroundColor: bookAppointmentsVM.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor,
borderColor: bookAppointmentsVM.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor,
textColor: bookAppointmentsVM.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor,
fontSize: 12,
fontWeight: FontWeight.w500,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(0.h, 0.h),
if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons)
SizedBox(height: 16.h),
Row(
mainAxisSize: MainAxisSize.max,
children: [

@ -48,7 +48,7 @@ class SelectLivecareClinicPage extends StatelessWidget {
SizedBox(height: 40.h),
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h),
Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.w, height: 58.h),
SizedBox(width: 18.h),
Expanded(
child: Column(

@ -84,6 +84,16 @@ class ServicesPage extends StatelessWidget {
true,
route: AppRoutes.comprehensiveCheckupPage,
),
HmgServicesComponentModel(
11,
"Indoor Navigation".needTranslation,
"".needTranslation,
AppAssets.indoor_nav_icon,
bgColor: Color(0xff45A2F8),
true,
route: null,
onTap: () {},
),
HmgServicesComponentModel(
11,
"E-Referral Services".needTranslation,

@ -192,7 +192,7 @@ class _LandingPageState extends State<LandingPage> {
),
);
}),
Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {
Utils.buildSvgWithAssets(icon: AppAssets.indoor_nav_icon, height: 18.h, width: 18.h).onPress(() {
// Navigator.of(context).push(
// CustomPageRoute(
// page: MedicalFilePage(),

@ -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<HospitalSelectionBottomSheetViewModel>(
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<EmergencyServicesViewModel>().clearSearchText();
},
);
}
}

@ -3,9 +3,11 @@ import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
@ -24,6 +26,7 @@ import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart';
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
@ -50,12 +53,14 @@ import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_ca
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart';
import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart';
import 'package:hmg_patient_app_new/presentation/monthly_report/monthly_report.dart';
import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -90,6 +95,9 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
late MyInvoicesViewModel myInvoicesViewModel;
late HmgServicesViewModel hmgServicesViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
late MonthlyReportViewModel monthlyReportViewModel;
final CacheService cacheService = GetIt.instance<CacheService>();
int currentIndex = 0;
@ -141,6 +149,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
hmgServicesViewModel = Provider.of<HmgServicesViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
monthlyReportViewModel = Provider.of<MonthlyReportViewModel>(context, listen: false);
NavigationService navigationService = getIt.get<NavigationService>();
return CollapsingListView(
// title: "Medical File".needTranslation,
@ -1236,7 +1245,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
svgIcon: AppAssets.monthly_reports_icon,
isLargeText: true,
iconSize: 36.h,
),
).onPress(() {
monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false);
Navigator.of(context).push(
CustomPageRoute(
page: MonthlyReport(),
),
);
}),
MedicalFileCard(
label: "Medical Reports".needTranslation,
textColor: AppColors.blackColor,

@ -0,0 +1,189 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.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/core/utils/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/monthly_report/monthly_report_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class MonthlyReport extends StatelessWidget {
MonthlyReport({super.key});
late AppState appState;
final CacheService _cacheService = GetIt.instance<CacheService>();
bool isTermsAccepted = true;
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MonthlyReportViewModel>(builder: (context, monthlyReportVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.monthlyReports.tr(),
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 24.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: false,
),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
LocaleKeys.patientHealthSummaryReport.tr(context: context).toText14(isBold: true),
const Spacer(),
Switch(
activeTrackColor: AppColors.successColor,
value: monthlyReportVM.isHealthSummaryEnabled,
onChanged: (newValue) async {
monthlyReportVM.setHealthSummaryEnabled(newValue);
},
),
],
).paddingSymmetrical(16.h, 16.h),
),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h),
SizedBox(width: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500),
"${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500),
],
),
],
),
],
).paddingSymmetrical(16.h, 16.h),
),
SizedBox(height: 16.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.w, height: 18.h),
SizedBox(width: 9.h),
Expanded(
child:
"This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and its not considered as a official report so no medical decision should be taken based on it"
.needTranslation
.toText10(weight: FontWeight.w500, color: AppColors.greyTextColorLight),
),
],
),
],
).paddingSymmetrical(24.w, 0.h),
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 32.h, left: 24.w),
child: Row(
children: [
SizedBox(
height: 24.0,
width: 24.0,
child: Checkbox(
value: isTermsAccepted,
onChanged: (v) {
isTermsAccepted = v ?? true;
},
activeColor: AppColors.primaryRedColor,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity.compact,
),
),
SizedBox(width: 10.w),
"I agree to the ".toText14(isBold: true, letterSpacing: -1.0),
"terms and conditions".toText14(isBold: true, letterSpacing: -1.0, color: AppColors.primaryRedColor, isUnderLine: true).onPress(() {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
);
})
],
),
),
CustomButton(
text: LocaleKeys.save.tr(),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: "Updating Monthly Report Status...".needTranslation);
await monthlyReportVM.updatePatientHealthSummaryReport(
rSummaryReport: monthlyReportVM.isHealthSummaryEnabled,
onSuccess: (response) async {
LoaderBottomSheet.hideLoader();
await _cacheService.saveBool(
key: CacheConst.isMonthlyReportEnabled,
value: monthlyReportVM.isHealthSummaryEnabled,
);
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: "Monthly Report Status Updated Successfully".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onError: (error) {
// Error is already handled by errorHandlerService in view model
},
);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 46.h,
iconColor: AppColors.whiteColor,
iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h),
],
),
),
],
);
}),
);
}
}

@ -169,9 +169,9 @@ class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
backgroundColor: AppColors.successColor.withValues(alpha: 0.15),
borderColor: AppColors.successColor.withValues(alpha: 0.01),
textColor: AppColors.successColor,
fontSize: 14,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
icon: AppAssets.download,

@ -220,9 +220,9 @@ class PrescriptionItemView extends StatelessWidget {
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
textColor: AppColors.primaryRedColor,
fontSize: 13,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
).toShimmer2(isShow: isLoading),

@ -259,14 +259,14 @@ class _PrescriptionsListPageState extends State<PrescriptionsListPage> {
prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color,
borderColor: AppColors.successColor.withOpacity(0.01),
textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35),
fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12,
fontSize: prescription.isHomeMedicineDeliverySupported! ? 14.f : 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
icon: AppAssets.prescription_refill_icon,
iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35),
iconSize: 14.h,
iconSize: 16.h,
),
),
SizedBox(width: 8.h),

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_swiper_view/flutter_swiper_view.dart';
@ -28,6 +30,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.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 'package:url_launcher/url_launcher.dart';
class ProfileSettings extends StatefulWidget {
const ProfileSettings({super.key});
@ -195,8 +198,6 @@ class ProfileSettingsState extends State<ProfileSettings> {
title: "Application Language".needTranslation, child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false);
}, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation),
1.divider,
actionItem(AppAssets.accessibility, "Symptoms Checker".needTranslation, () {}),
1.divider,
actionItem(AppAssets.accessibility, "Accessibility".needTranslation, () {}),
1.divider,
actionItem(AppAssets.bell, "Notifications Settings".needTranslation, () {}),
@ -235,15 +236,35 @@ class ProfileSettingsState extends State<ProfileSettings> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Column(
children: [
actionItem(AppAssets.call_fill, "Contact Us".needTranslation, () {}, trailingLabel: "9200666666"),
actionItem(AppAssets.call_fill, "Contact Us".needTranslation, () {
launchUrl(Uri.parse("tel://" + "+966 11 525 9999"));
}, trailingLabel: "011 525 9999"),
1.divider,
actionItem(AppAssets.permission, "Permissions".needTranslation, () {}, trailingLabel: "Location, Camera"),
1.divider,
actionItem(AppAssets.rate, "Rate Our App".needTranslation, () {}, isExternalLink: true),
actionItem(AppAssets.rate, "Rate Our App".needTranslation, () {
if (Platform.isAndroid) {
Utils.openWebView(
url: 'https://play.google.com/store/apps/details?id=com.ejada.hmg',
);
} else {
Utils.openWebView(
url: 'https://itunes.apple.com/app/id733503978',
);
}
}, isExternalLink: true),
1.divider,
actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () {}, isExternalLink: true),
actionItem(AppAssets.privacy_terms, "Privacy Policy".needTranslation, () {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Privacy.aspx',
);
}, isExternalLink: true),
1.divider,
actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () {}, isExternalLink: true),
actionItem(AppAssets.privacy_terms, "Terms & Conditions".needTranslation, () {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
);
}, isExternalLink: true),
],
),
),

@ -177,11 +177,11 @@ class AncillaryOrderCard extends StatelessWidget {
labelText: order.projectName ?? '-',
).toShimmer2(isShow: isLoading),
// orderNo
if (order.orderNo != null || isLoading)
AppCustomChipWidget(
// icon: AppAssets.calendar,
labelText: "${"Order# :".needTranslation}${order.orderNo ?? '-'}",
).toShimmer2(isShow: isLoading),
// if (order.orderNo != null || isLoading)
// AppCustomChipWidget(
// // icon: AppAssets.calendar,
// labelText: "${"Order# :".needTranslation}${order.orderNo ?? '-'}",
// ).toShimmer2(isShow: isLoading),
// Appointment Date
if (order.appointmentDate != null || isLoading)

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

Loading…
Cancel
Save