Merge pull request 'haroon_dev' (#144) from haroon_dev into master
Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/144pull/157/head
commit
7c82295e2c
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +1,51 @@
|
|||||||
package com.ejada.hmg
|
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
|
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">
|
<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>
|
</resources>
|
||||||
File diff suppressed because one or more lines are too long
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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>
|
||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue