Merge branch 'master' into faiz_dev
# Conflicts: # lib/core/api_consts.dart # lib/core/dependencies.dart # lib/main.dartfaiz_dev
commit
ff2bcbae3f
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,6 +1,51 @@
|
||||
package com.ejada.hmg
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.WindowManager
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ejada.hmg.penguin.PenguinInPlatformBridge
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugins.GeneratedPluginRegistrant
|
||||
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
|
||||
|
||||
class MainActivity : FlutterFragmentActivity()
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
GeneratedPluginRegistrant.registerWith(flutterEngine);
|
||||
// Create Flutter Platform Bridge
|
||||
this.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON)
|
||||
|
||||
PenguinInPlatformBridge(flutterEngine, this).create()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
val granted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
||||
val intent = Intent("PERMISSION_RESULT_ACTION").apply {
|
||||
putExtra("PERMISSION_GRANTED", granted)
|
||||
}
|
||||
sendBroadcast(intent)
|
||||
|
||||
// Log the request code and permission results
|
||||
Log.d("PermissionsResult", "Request Code: $requestCode")
|
||||
Log.d("PermissionsResult", "Permissions: ${permissions.joinToString()}")
|
||||
Log.d("PermissionsResult", "Grant Results: ${grantResults.joinToString()}")
|
||||
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
package com.ejada.hmg.penguin
|
||||
|
||||
import com.ejada.hmg.MainActivity
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ejada.hmg.penguin.PenguinView
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import com.ejada.hmg.PermissionManager.HostNotificationPermissionManager
|
||||
import com.ejada.hmg.PermissionManager.HostBgLocationManager
|
||||
import com.ejada.hmg.PermissionManager.HostGpsStateManager
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class PenguinInPlatformBridge(
|
||||
private var flutterEngine: FlutterEngine,
|
||||
private var mainActivity: MainActivity
|
||||
) {
|
||||
|
||||
private lateinit var channel: MethodChannel
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL = "launch_penguin_ui"
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
fun create() {
|
||||
// openTok = OpenTok(mainActivity, flutterEngine)
|
||||
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
|
||||
channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
|
||||
when (call.method) {
|
||||
"launchPenguin" -> {
|
||||
print("the platform channel is being called")
|
||||
|
||||
if (HostNotificationPermissionManager.isNotificationPermissionGranted(mainActivity))
|
||||
else HostNotificationPermissionManager.requestNotificationPermission(mainActivity)
|
||||
HostBgLocationManager.requestLocationBackgroundPermission(mainActivity)
|
||||
HostGpsStateManager.requestLocationPermission(mainActivity)
|
||||
val args = call.arguments as Map<String, Any>?
|
||||
Log.d("TAG", "configureFlutterEngine: $args")
|
||||
println("args")
|
||||
args?.let {
|
||||
PenguinView(
|
||||
mainActivity,
|
||||
100,
|
||||
args,
|
||||
flutterEngine.dartExecutor.binaryMessenger,
|
||||
activity = mainActivity,
|
||||
channel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,139 @@
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
|
||||
/**
|
||||
* This preferences for app level
|
||||
*/
|
||||
|
||||
public class AppPreferences {
|
||||
|
||||
public static final String PREF_NAME = "PenguinINUI_AppPreferences";
|
||||
public static final int MODE = Context.MODE_PRIVATE;
|
||||
|
||||
public static final String campusIdKey = "campusId";
|
||||
|
||||
public static final String LANG = "Lang";
|
||||
|
||||
public static final String settingINFO = "SETTING-INFO";
|
||||
|
||||
public static final String userName = "userName";
|
||||
public static final String passWord = "passWord";
|
||||
|
||||
private static HandlerThread handlerThread;
|
||||
private static Handler handler;
|
||||
|
||||
static {
|
||||
handlerThread = new HandlerThread("PreferencesHandlerThread");
|
||||
handlerThread.start();
|
||||
handler = new Handler(handlerThread.getLooper());
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static SharedPreferences getPreferences(final Context context) {
|
||||
return context.getSharedPreferences(AppPreferences.PREF_NAME, AppPreferences.MODE);
|
||||
}
|
||||
|
||||
public static SharedPreferences.Editor getEditor(final Context context) {
|
||||
return getPreferences(context).edit();
|
||||
}
|
||||
|
||||
|
||||
public static void writeInt(final Context context, final String key, final int value) {
|
||||
handler.post(() -> {
|
||||
SharedPreferences.Editor editor = getEditor(context);
|
||||
editor.putInt(key, value);
|
||||
editor.apply();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static int readInt(final Context context, final String key, final int defValue) {
|
||||
Callable<Integer> callable = () -> {
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
return preferences.getInt(key, -1);
|
||||
};
|
||||
|
||||
Future<Integer> future = new FutureTask<>(callable);
|
||||
handler.post((Runnable) future);
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace(); // Handle the exception appropriately
|
||||
}
|
||||
|
||||
return -1; // Return the default value in case of an error
|
||||
}
|
||||
|
||||
public static int getCampusId(final Context context) {
|
||||
return readInt(context,campusIdKey,-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void writeString(final Context context, final String key, final String value) {
|
||||
handler.post(() -> {
|
||||
SharedPreferences.Editor editor = getEditor(context);
|
||||
editor.putString(key, value);
|
||||
editor.apply();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static String readString(final Context context, final String key, final String defValue) {
|
||||
Callable<String> callable = () -> {
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
return preferences.getString(key, defValue);
|
||||
};
|
||||
|
||||
Future<String> future = new FutureTask<>(callable);
|
||||
handler.post((Runnable) future);
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace(); // Handle the exception appropriately
|
||||
}
|
||||
|
||||
return defValue; // Return the default value in case of an error
|
||||
}
|
||||
|
||||
|
||||
public static void writeBoolean(final Context context, final String key, final boolean value) {
|
||||
handler.post(() -> {
|
||||
SharedPreferences.Editor editor = getEditor(context);
|
||||
editor.putBoolean(key, value);
|
||||
editor.apply();
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean readBoolean(final Context context, final String key, final boolean defValue) {
|
||||
Callable<Boolean> callable = () -> {
|
||||
SharedPreferences preferences = getPreferences(context);
|
||||
return preferences.getBoolean(key, defValue);
|
||||
};
|
||||
|
||||
Future<Boolean> future = new FutureTask<>(callable);
|
||||
handler.post((Runnable) future);
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace(); // Handle the exception appropriately
|
||||
}
|
||||
|
||||
return defValue; // Return the default value in case of an error
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.peng.pennavmap.PlugAndPlaySDK;
|
||||
import com.peng.pennavmap.R;
|
||||
import com.peng.pennavmap.enums.InitializationErrorType;
|
||||
|
||||
/**
|
||||
* Manages background location permission requests and handling for the application.
|
||||
*/
|
||||
public class HostBgLocationManager {
|
||||
/**
|
||||
* Request code for background location permission
|
||||
*/
|
||||
public static final int REQUEST_ACCESS_BACKGROUND_LOCATION_CODE = 301;
|
||||
|
||||
/**
|
||||
* Request code for navigating to app settings
|
||||
*/
|
||||
private static final int REQUEST_CODE_SETTINGS = 11234;
|
||||
|
||||
/**
|
||||
* Alert dialog for denied permissions
|
||||
*/
|
||||
private static AlertDialog deniedAlertDialog;
|
||||
|
||||
/**
|
||||
* Checks if the background location permission has been granted.
|
||||
*
|
||||
* @param context the context of the application or activity
|
||||
* @return true if the permission is granted, false otherwise
|
||||
*/
|
||||
|
||||
public static boolean isLocationBackgroundGranted(Context context) {
|
||||
return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the background location permission from the user.
|
||||
*
|
||||
* @param activity the activity from which the request is made
|
||||
*/
|
||||
public static void requestLocationBackgroundPermission(Activity activity) {
|
||||
// Check if the ACCESS_BACKGROUND_LOCATION permission is already granted
|
||||
if (!isLocationBackgroundGranted(activity)) {
|
||||
// Permission is not granted, so request it
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION},
|
||||
REQUEST_ACCESS_BACKGROUND_LOCATION_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog prompting the user to grant the background location permission.
|
||||
*
|
||||
* @param activity the activity where the dialog is displayed
|
||||
*/
|
||||
public static void showLocationBackgroundPermission(Activity activity) {
|
||||
AlertDialog alertDialog = new AlertDialog.Builder(activity)
|
||||
.setCancelable(false)
|
||||
.setMessage(activity.getString(R.string.com_penguin_nav_ui_geofence_alert_msg))
|
||||
.setPositiveButton(activity.getString(R.string.com_penguin_nav_ui_go_to_settings), (dialog, which) -> {
|
||||
if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
|
||||
HostBgLocationManager.requestLocationBackgroundPermission(activity);
|
||||
} else {
|
||||
openAppSettings(activity);
|
||||
}
|
||||
if (dialog != null) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.setNegativeButton(activity.getString(R.string.com_penguin_nav_ui_later), (dialog, which) -> {
|
||||
dialog.cancel();
|
||||
})
|
||||
.create();
|
||||
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the scenario where permissions are denied by the user.
|
||||
* Displays a dialog to guide the user to app settings or exit the activity.
|
||||
*
|
||||
* @param activity the activity where the dialog is displayed
|
||||
*/
|
||||
public static synchronized void handlePermissionsDenied(Activity activity) {
|
||||
if (deniedAlertDialog != null && deniedAlertDialog.isShowing()) {
|
||||
deniedAlertDialog.dismiss();
|
||||
}
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setCancelable(false)
|
||||
.setMessage(activity.getString(R.string.com_penguin_nav_ui_permission_denied_dialog_msg))
|
||||
.setNegativeButton(activity.getString(R.string.com_penguin_nav_ui_cancel), (dialogInterface, i) -> {
|
||||
if (PlugAndPlaySDK.externalPenNavUIDelegate != null) {
|
||||
PlugAndPlaySDK.externalPenNavUIDelegate.onPenNavInitializationError(
|
||||
InitializationErrorType.permissions.getTypeKey(),
|
||||
InitializationErrorType.permissions);
|
||||
}
|
||||
activity.finish();
|
||||
})
|
||||
.setPositiveButton(activity.getString(R.string.com_penguin_nav_ui_go_settings), (dialogInterface, i) -> {
|
||||
dialogInterface.dismiss();
|
||||
openAppSettings(activity);
|
||||
});
|
||||
deniedAlertDialog = builder.create();
|
||||
deniedAlertDialog.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the application's settings screen to allow the user to modify permissions.
|
||||
*
|
||||
* @param activity the activity from which the settings screen is launched
|
||||
*/
|
||||
private static void openAppSettings(Activity activity) {
|
||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
|
||||
if (intent.resolveActivity(activity.getPackageManager()) != null) {
|
||||
activity.startActivityForResult(intent, REQUEST_CODE_SETTINGS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.LocationManager;
|
||||
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.peng.pennavmap.managers.permissions.managers.BgLocationManager;
|
||||
|
||||
public class HostGpsStateManager {
|
||||
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1;
|
||||
|
||||
|
||||
public boolean checkGPSEnabled(Activity activity) {
|
||||
LocationManager gpsStateManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE);
|
||||
return gpsStateManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
|
||||
}
|
||||
|
||||
public static boolean isGpsGranted(Activity activity) {
|
||||
return BgLocationManager.isLocationBackgroundGranted(activity)
|
||||
|| ContextCompat.checkSelfPermission(
|
||||
activity,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
&& ContextCompat.checkSelfPermission(
|
||||
activity,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the location permission is granted.
|
||||
*
|
||||
* @param activity the Activity context
|
||||
* @return true if permission is granted, false otherwise
|
||||
*/
|
||||
public static boolean isLocationPermissionGranted(Activity activity) {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
activity,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED &&
|
||||
ContextCompat.checkSelfPermission(
|
||||
activity,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the location permission.
|
||||
*
|
||||
* @param activity the Activity context
|
||||
*/
|
||||
public static void requestLocationPermission(Activity activity) {
|
||||
ActivityCompat.requestPermissions(
|
||||
activity,
|
||||
new String[]{
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
},
|
||||
LOCATION_PERMISSION_REQUEST_CODE
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
package com.ejada.hmg.PermissionManager;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
public class HostNotificationPermissionManager {
|
||||
private static final int REQUEST_NOTIFICATION_PERMISSION = 100;
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the notification permission is granted.
|
||||
*
|
||||
* @return true if the notification permission is granted, false otherwise.
|
||||
*/
|
||||
public static boolean isNotificationPermissionGranted(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
return ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.POST_NOTIFICATIONS)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
} catch (Exception e) {
|
||||
// Handle cases where the API is unavailable
|
||||
e.printStackTrace();
|
||||
return NotificationManagerCompat.from(activity).areNotificationsEnabled();
|
||||
}
|
||||
} else {
|
||||
// Permissions were not required below Android 13 for notifications
|
||||
return NotificationManagerCompat.from(activity).areNotificationsEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the notification permission.
|
||||
*/
|
||||
public static void requestNotificationPermission(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (!isNotificationPermissionGranted(activity)) {
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{android.Manifest.permission.POST_NOTIFICATIONS},
|
||||
REQUEST_NOTIFICATION_PERMISSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the result of the permission request.
|
||||
*
|
||||
* @param requestCode The request code passed in requestPermissions().
|
||||
* @param permissions The requested permissions.
|
||||
* @param grantResults The grant results for the corresponding permissions.
|
||||
*/
|
||||
public static boolean handlePermissionResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (permissions.length > 0 &&
|
||||
permissions[0].equals(android.Manifest.permission.POST_NOTIFICATIONS) &&
|
||||
grantResults.length > 0 &&
|
||||
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Permission granted
|
||||
System.out.println("Notification permission granted.");
|
||||
return true;
|
||||
} else {
|
||||
// Permission denied
|
||||
System.out.println("Notification permission denied.");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.ejada.hmg.PermissionManager
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
|
||||
object PermissionHelper {
|
||||
|
||||
fun getRequiredPermissions(): Array<String> {
|
||||
val permissions = mutableListOf(
|
||||
Manifest.permission.INTERNET,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||
Manifest.permission.ACCESS_NETWORK_STATE,
|
||||
Manifest.permission.BLUETOOTH,
|
||||
Manifest.permission.BLUETOOTH_ADMIN,
|
||||
// Manifest.permission.ACTIVITY_RECOGNITION
|
||||
)
|
||||
|
||||
// For Android 12 (API level 31) and above, add specific permissions
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12 (API 31) and above
|
||||
permissions.add(Manifest.permission.BLUETOOTH_SCAN)
|
||||
permissions.add(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
permissions.add(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS)
|
||||
// }
|
||||
|
||||
return permissions.toTypedArray()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.ejada.hmg.PermissionManager
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
class PermissionManager(
|
||||
private val context: Context,
|
||||
val listener: PermissionListener,
|
||||
private val requestCode: Int,
|
||||
vararg permissions: String
|
||||
) {
|
||||
|
||||
private val permissionsArray = permissions
|
||||
|
||||
interface PermissionListener {
|
||||
fun onPermissionGranted()
|
||||
fun onPermissionDenied()
|
||||
}
|
||||
|
||||
fun arePermissionsGranted(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
permissionsArray.all {
|
||||
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun requestPermissions(activity: Activity) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ActivityCompat.requestPermissions(activity, permissionsArray, requestCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun handlePermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
if (this.requestCode == requestCode) {
|
||||
val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED }
|
||||
if (allGranted) {
|
||||
listener.onPermissionGranted()
|
||||
} else {
|
||||
listener.onPermissionDenied()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.ejada.hmg.PermissionManager
|
||||
|
||||
// PermissionResultReceiver.kt
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
class PermissionResultReceiver(
|
||||
private val callback: (Boolean) -> Unit
|
||||
) : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val granted = intent?.getBooleanExtra("PERMISSION_GRANTED", false) ?: false
|
||||
callback(granted)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
package com.ejada.hmg.penguin
|
||||
|
||||
enum class PenguinMethod {
|
||||
// initializePenguin("initializePenguin"),
|
||||
// configurePenguin("configurePenguin"),
|
||||
// showPenguinUI("showPenguinUI"),
|
||||
// onPenNavUIDismiss("onPenNavUIDismiss"),
|
||||
// onReportIssue("onReportIssue"),
|
||||
// onPenNavSuccess("onPenNavSuccess"),
|
||||
onPenNavInitializationError // onLocationOffCampus("onLocationOffCampus"),
|
||||
// navigateToPOI("navigateToPOI"),
|
||||
// openSharedLocation("openSharedLocation");
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
package com.ejada.hmg.penguin
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.Gson
|
||||
import com.peng.pennavmap.PlugAndPlaySDK
|
||||
import com.peng.pennavmap.connections.ApiController
|
||||
import com.peng.pennavmap.interfaces.RefIdDelegate
|
||||
import com.peng.pennavmap.models.TokenModel
|
||||
import com.peng.pennavmap.models.postmodels.PostToken
|
||||
import com.peng.pennavmap.utils.AppSharedData
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import android.util.Log
|
||||
|
||||
|
||||
class PenguinNavigator() {
|
||||
|
||||
fun navigateTo(mContext: Context, refID: String, delegate: RefIdDelegate,clientID : String,clientKey : String ) {
|
||||
val postToken = PostToken(clientID, clientKey)
|
||||
getToken(mContext, postToken, object : RefIdDelegate {
|
||||
override fun onRefByIDSuccess(PoiId: String?) {
|
||||
Log.e("navigateTo", "PoiId is+++++++ $PoiId")
|
||||
|
||||
PlugAndPlaySDK.navigateTo(mContext, refID, object : RefIdDelegate {
|
||||
override fun onRefByIDSuccess(PoiId: String?) {
|
||||
Log.e("navigateTo", "PoiId 2is+++++++ $PoiId")
|
||||
|
||||
delegate.onRefByIDSuccess(refID)
|
||||
|
||||
}
|
||||
|
||||
override fun onGetByRefIDError(error: String?) {
|
||||
delegate.onRefByIDSuccess(error)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onGetByRefIDError(error: String?) {
|
||||
delegate.onRefByIDSuccess(error)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fun getToken(mContext: Context, postToken: PostToken?, apiTokenCallBack: RefIdDelegate) {
|
||||
try {
|
||||
// Create the API call
|
||||
val purposesCall: Call<ResponseBody> = ApiController.getInstance(mContext)
|
||||
.apiMethods
|
||||
.getToken(postToken)
|
||||
|
||||
// Enqueue the call for asynchronous execution
|
||||
purposesCall.enqueue(object : Callback<ResponseBody?> {
|
||||
override fun onResponse(
|
||||
call: Call<ResponseBody?>,
|
||||
response: Response<ResponseBody?>
|
||||
) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
try {
|
||||
response.body()?.use { responseBody ->
|
||||
val responseBodyString: String = responseBody.string() // Use `string()` to get the actual response content
|
||||
if (responseBodyString.isNotEmpty()) {
|
||||
val tokenModel = Gson().fromJson(responseBodyString, TokenModel::class.java)
|
||||
if (tokenModel != null && tokenModel.token != null) {
|
||||
AppSharedData.apiToken = tokenModel.token
|
||||
apiTokenCallBack.onRefByIDSuccess(tokenModel.token)
|
||||
} else {
|
||||
apiTokenCallBack.onGetByRefIDError("Failed to parse token model")
|
||||
}
|
||||
} else {
|
||||
apiTokenCallBack.onGetByRefIDError("Response body is empty")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
apiTokenCallBack.onGetByRefIDError("An error occurred: ${e.message}")
|
||||
}
|
||||
} else {
|
||||
apiTokenCallBack.onGetByRefIDError("Unsuccessful response: " + response.code())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
|
||||
apiTokenCallBack.onGetByRefIDError(t.message)
|
||||
}
|
||||
})
|
||||
} catch (error: Exception) {
|
||||
apiTokenCallBack.onGetByRefIDError("Exception during API call: $error")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,376 @@
|
||||
package com.ejada.hmg.penguin
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Context.RECEIVER_EXPORTED
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.ejada.hmg.PermissionManager.PermissionManager
|
||||
import com.ejada.hmg.PermissionManager.PermissionResultReceiver
|
||||
import com.ejada.hmg.MainActivity
|
||||
import com.ejada.hmg.PermissionManager.PermissionHelper
|
||||
import com.peng.pennavmap.PlugAndPlayConfiguration
|
||||
import com.peng.pennavmap.PlugAndPlaySDK
|
||||
import com.peng.pennavmap.enums.InitializationErrorType
|
||||
import com.peng.pennavmap.interfaces.PenNavUIDelegate
|
||||
import com.peng.pennavmap.utils.Languages
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.platform.PlatformView
|
||||
import com.ejada.hmg.penguin.PenguinNavigator
|
||||
import com.peng.pennavmap.interfaces.PIEventsDelegate
|
||||
import com.peng.pennavmap.interfaces.PILocationDelegate
|
||||
import com.peng.pennavmap.interfaces.RefIdDelegate
|
||||
import com.peng.pennavmap.models.LocationMessage
|
||||
import com.peng.pennavmap.models.PIReportIssue
|
||||
import java.util.ArrayList
|
||||
import penguin.com.pennav.renderer.PIRendererSettings
|
||||
|
||||
/**
|
||||
* Custom PlatformView for displaying Penguin UI components within a Flutter app.
|
||||
* Implements `PlatformView` for rendering the view, `MethodChannel.MethodCallHandler` for handling method calls,
|
||||
* and `PenNavUIDelegate` for handling SDK events.
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
internal class PenguinView(
|
||||
context: Context,
|
||||
id: Int,
|
||||
val creationParams: Map<String, Any>,
|
||||
messenger: BinaryMessenger,
|
||||
activity: MainActivity,
|
||||
val channel: MethodChannel
|
||||
) : PlatformView, MethodChannel.MethodCallHandler, PenNavUIDelegate, PIEventsDelegate,
|
||||
PILocationDelegate {
|
||||
// The layout for displaying the Penguin UI
|
||||
private val mapLayout: RelativeLayout = RelativeLayout(context)
|
||||
private val _context: Context = context
|
||||
|
||||
private val permissionResultReceiver: PermissionResultReceiver
|
||||
private val permissionIntentFilter = IntentFilter("PERMISSION_RESULT_ACTION")
|
||||
|
||||
private companion object {
|
||||
const val PERMISSIONS_REQUEST_CODE = 1
|
||||
}
|
||||
|
||||
private lateinit var permissionManager: PermissionManager
|
||||
|
||||
// Reference to the main activity
|
||||
private var _activity: Activity = activity
|
||||
|
||||
private lateinit var mContext: Context
|
||||
|
||||
lateinit var navigator: PenguinNavigator
|
||||
|
||||
init {
|
||||
// Set layout parameters for the mapLayout
|
||||
mapLayout.layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
mContext = context
|
||||
|
||||
|
||||
permissionResultReceiver = PermissionResultReceiver { granted ->
|
||||
if (granted) {
|
||||
onPermissionsGranted()
|
||||
} else {
|
||||
onPermissionsDenied()
|
||||
}
|
||||
}
|
||||
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
mContext.registerReceiver(
|
||||
permissionResultReceiver,
|
||||
permissionIntentFilter,
|
||||
RECEIVER_EXPORTED
|
||||
)
|
||||
} else {
|
||||
mContext.registerReceiver(
|
||||
permissionResultReceiver,
|
||||
permissionIntentFilter,
|
||||
)
|
||||
}
|
||||
|
||||
// Set the background color of the layout
|
||||
mapLayout.setBackgroundColor(Color.RED)
|
||||
|
||||
permissionManager = PermissionManager(
|
||||
context = mContext,
|
||||
listener = object : PermissionManager.PermissionListener {
|
||||
override fun onPermissionGranted() {
|
||||
// Handle permissions granted
|
||||
onPermissionsGranted()
|
||||
}
|
||||
|
||||
override fun onPermissionDenied() {
|
||||
// Handle permissions denied
|
||||
onPermissionsDenied()
|
||||
}
|
||||
},
|
||||
requestCode = PERMISSIONS_REQUEST_CODE,
|
||||
PermissionHelper.getRequiredPermissions().get(0)
|
||||
)
|
||||
|
||||
if (!permissionManager.arePermissionsGranted()) {
|
||||
permissionManager.requestPermissions(_activity)
|
||||
} else {
|
||||
// Permissions already granted
|
||||
permissionManager.listener.onPermissionGranted()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun onPermissionsGranted() {
|
||||
// Handle the actions when permissions are granted
|
||||
Log.d("PermissionsResult", "onPermissionsGranted")
|
||||
// Register the platform view factory for creating custom views
|
||||
|
||||
// Initialize the Penguin SDK
|
||||
initPenguin()
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun onPermissionsDenied() {
|
||||
// Handle the actions when permissions are denied
|
||||
Log.d("PermissionsResult", "onPermissionsDenied")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view associated with this PlatformView.
|
||||
*
|
||||
* @return The main view for this PlatformView.
|
||||
*/
|
||||
override fun getView(): View {
|
||||
return mapLayout
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up resources associated with this PlatformView.
|
||||
*/
|
||||
override fun dispose() {
|
||||
// Cleanup code if needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles method calls from Dart code.
|
||||
*
|
||||
* @param call The method call from Dart.
|
||||
* @param result The result callback to send responses back to Dart.
|
||||
*/
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
// Handle method calls from Dart code here
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Penguin SDK with custom configuration and delegates.
|
||||
*/
|
||||
private fun initPenguin() {
|
||||
navigator = PenguinNavigator()
|
||||
// Configure the PlugAndPlaySDK
|
||||
val language = when (creationParams["languageCode"] as String) {
|
||||
"ar" -> Languages.ar
|
||||
"en" -> Languages.en
|
||||
else -> {
|
||||
Languages.en
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// PlugAndPlaySDK.configuration = Builder()
|
||||
// .setClientData(MConstantsDemo.CLIENT_ID, MConstantsDemo.CLIENT_KEY)
|
||||
// .setLanguageID(selectedLanguage)
|
||||
// .setBaseUrl(MConstantsDemo.DATA_URL, MConstantsDemo.POSITION_URL)
|
||||
// .setServiceName(MConstantsDemo.DATA_SERVICE_NAME, MConstantsDemo.POSITION_SERVICE_NAME)
|
||||
// .setUserName(name)
|
||||
// .setSimulationModeEnabled(isSimulation)
|
||||
// .setCustomizeColor(if (MConstantsDemo.APP_COLOR != null) MConstantsDemo.APP_COLOR else "#2CA0AF")
|
||||
// .setEnableBackButton(MConstantsDemo.SHOW_BACK_BUTTON)
|
||||
// .setCampusId(MConstantsDemo.selectedCampusId)
|
||||
//
|
||||
// .setShowUILoader(true)
|
||||
// .build()
|
||||
|
||||
PIRendererSettings.styleUri = "mapbox://styles/rwaid/cm3h30b36007v01qz7ik8a0sk"
|
||||
|
||||
PlugAndPlaySDK.configuration = PlugAndPlayConfiguration.Builder()
|
||||
.setBaseUrl(
|
||||
creationParams["dataURL"] as String,
|
||||
creationParams["positionURL"] as String
|
||||
)
|
||||
.setServiceName(
|
||||
creationParams["dataServiceName"] as String,
|
||||
creationParams["positionServiceName"] as String
|
||||
)
|
||||
.setClientData(
|
||||
creationParams["clientID"] as String,
|
||||
creationParams["clientKey"] as String
|
||||
)
|
||||
.setUserName(creationParams["username"] as String)
|
||||
// .setLanguageID(Languages.en)
|
||||
.setLanguageID(language)
|
||||
.setSimulationModeEnabled(creationParams["isSimulationModeEnabled"] as Boolean)
|
||||
.setEnableBackButton(true)
|
||||
// .setDeepLinkData("deeplink")
|
||||
.setCustomizeColor("#2CA0AF")
|
||||
.setDeepLinkSchema("", "")
|
||||
.setIsEnableReportIssue(true)
|
||||
.setDeepLinkData("")
|
||||
.setEnableSharedLocationCallBack(false)
|
||||
.setShowUILoader(true)
|
||||
.setCampusId(creationParams["projectID"] as Int)
|
||||
.build()
|
||||
|
||||
|
||||
Log.d(
|
||||
"TAG",
|
||||
"initPenguin: ${creationParams["projectID"]}"
|
||||
)
|
||||
|
||||
Log.d(
|
||||
"TAG",
|
||||
"initPenguin: creation param are ${creationParams}"
|
||||
)
|
||||
|
||||
// Set location delegate to handle location updates
|
||||
// PlugAndPlaySDK.setPiLocationDelegate {
|
||||
// Example code to handle location updates
|
||||
// Uncomment and modify as needed
|
||||
// if (location.size() > 0)
|
||||
// Toast.makeText(_context, "Location Info Latitude: ${location[0]}, Longitude: ${location[1]}", Toast.LENGTH_SHORT).show()
|
||||
// }
|
||||
|
||||
// Set events delegate for reporting issues
|
||||
// PlugAndPlaySDK.setPiEventsDelegate(new PIEventsDelegate() {
|
||||
// @Override
|
||||
// public void onReportIssue(PIReportIssue issue) {
|
||||
// Log.e("Issue Reported: ", issue.getReportType());
|
||||
// }
|
||||
// // Implement issue reporting logic here }
|
||||
// @Override
|
||||
// public void onSharedLocation(String link) {
|
||||
// // Implement Shared location logic here
|
||||
// }
|
||||
// })
|
||||
|
||||
// Start the Penguin SDK
|
||||
PlugAndPlaySDK.setPiEventsDelegate(this)
|
||||
PlugAndPlaySDK.setPiLocationDelegate(this)
|
||||
PlugAndPlaySDK.start(mContext, this)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Navigates to the specified reference ID.
|
||||
*
|
||||
* @param refID The reference ID to navigate to.
|
||||
*/
|
||||
fun navigateTo(refID: String) {
|
||||
try {
|
||||
if (refID.isBlank()) {
|
||||
Log.e("navigateTo", "Invalid refID: The reference ID is blank.")
|
||||
}
|
||||
// referenceId = refID
|
||||
navigator.navigateTo(mContext, refID,object : RefIdDelegate {
|
||||
override fun onRefByIDSuccess(PoiId: String?) {
|
||||
Log.e("navigateTo", "PoiId is penguin view+++++++ $PoiId")
|
||||
|
||||
// channelFlutter.invokeMethod(
|
||||
// PenguinMethod.navigateToPOI.name,
|
||||
// "navigateTo Success"
|
||||
// )
|
||||
}
|
||||
|
||||
override fun onGetByRefIDError(error: String?) {
|
||||
Log.e("navigateTo", "error is penguin view+++++++ $error")
|
||||
|
||||
// channelFlutter.invokeMethod(
|
||||
// PenguinMethod.navigateToPOI.name,
|
||||
// "navigateTo Failed: Invalid refID"
|
||||
// )
|
||||
}
|
||||
} , creationParams["clientID"] as String, creationParams["clientKey"] as String )
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("navigateTo", "Exception occurred during navigation: ${e.message}", e)
|
||||
// channelFlutter.invokeMethod(
|
||||
// PenguinMethod.navigateToPOI.name,
|
||||
// "Failed: Exception - ${e.message}"
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Penguin UI setup is successful.
|
||||
*
|
||||
* @param warningCode Optional warning code received from the SDK.
|
||||
*/
|
||||
override fun onPenNavSuccess(warningCode: String?) {
|
||||
val clinicId = creationParams["clinicID"] as String
|
||||
|
||||
if(clinicId.isEmpty()) return
|
||||
|
||||
navigateTo(clinicId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when there is an initialization error with Penguin UI.
|
||||
*
|
||||
* @param description Description of the error.
|
||||
* @param errorType Type of initialization error.
|
||||
*/
|
||||
override fun onPenNavInitializationError(
|
||||
description: String?,
|
||||
errorType: InitializationErrorType?
|
||||
) {
|
||||
val arguments: Map<String, Any?> = mapOf(
|
||||
"description" to description,
|
||||
"type" to errorType?.name
|
||||
)
|
||||
Log.d(
|
||||
"description",
|
||||
"description : ${description}"
|
||||
)
|
||||
|
||||
channel.invokeMethod(PenguinMethod.onPenNavInitializationError.name, arguments)
|
||||
Toast.makeText(mContext, "Navigation Error: $description", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Penguin UI is dismissed.
|
||||
*/
|
||||
override fun onPenNavUIDismiss() {
|
||||
// Handle UI dismissal if needed
|
||||
try {
|
||||
mContext.unregisterReceiver(permissionResultReceiver)
|
||||
dispose();
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e("PenguinView", "Receiver not registered: $e")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReportIssue(issue: PIReportIssue?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onSharedLocation(link: String?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onLocationOffCampus(location: ArrayList<Double>?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onLocationMessage(locationMessage: LocationMessage?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,3 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>
|
||||
<!-- <string name="mapbox_access_token" translatable="false" tools:ignore="UnusedResources">sk.eyJ1IjoicndhaWQiLCJhIjoiY2x6NWo0bTMzMWZodzJrcGZpemYzc3Z4dSJ9.uSSZuwNSGCcCdPAiORECmg</string>-->
|
||||
</resources>
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,4 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
|
||||
<path d="M11.9998 21.25C11.5858 21.2503 11.2498 21.5859 11.2498 22C11.2498 22.4141 11.5858 22.7497 11.9998 22.75H14.3953C14.577 22.75 14.7553 22.7915 14.9158 22.8691L16.9578 23.8564C17.3233 24.0333 17.7251 24.125 18.1316 24.125H19.1746C19.601 24.1251 19.9516 24.3534 20.1277 24.6719L18.1931 25.207C17.9121 25.2847 17.6116 25.2563 17.3523 25.1309L15.1687 24.0752C14.7959 23.895 14.3471 24.051 14.1667 24.4238C13.9866 24.7966 14.1427 25.2454 14.5154 25.4258L16.699 26.4814C17.2874 26.7661 17.9622 26.8265 18.5925 26.6523L21.1335 25.9502C21.4836 25.8534 21.7498 25.5327 21.7498 25.1416C21.7498 25.1125 21.7468 25.0836 21.7458 25.0547L25.8132 23.8057L25.8162 23.8047C26.3135 23.6499 26.8624 23.8288 27.1892 24.2812C27.3052 24.4419 27.2446 24.6626 27.1033 24.7441L19.5876 29.0811C19.2867 29.2546 18.9351 29.2953 18.6082 29.1982L12.2136 27.3008C11.8165 27.1829 11.3988 27.4095 11.281 27.8066C11.1632 28.2037 11.3898 28.6214 11.7869 28.7393L18.1814 30.6367C18.9059 30.8517 19.6827 30.7578 20.3376 30.3799L27.8533 26.0439C28.7864 25.5055 29.0265 24.2639 28.405 23.4033C27.7121 22.4438 26.5013 22.0206 25.3708 22.3721L25.3699 22.373L21.241 23.6416C20.7675 23.0215 20.0117 22.6251 19.1746 22.625H18.1316C17.95 22.625 17.7716 22.5844 17.6111 22.5068L15.5691 21.5186C15.2035 21.3417 14.8017 21.25 14.3953 21.25H11.9998ZM22.6511 9.91992C21.7188 9.02657 20.2817 9.02657 19.3494 9.91992C18.7347 10.5091 17.9734 11.3294 17.3611 12.2773C16.7542 13.2169 16.2498 14.3489 16.2498 15.5469C16.2498 17.8542 18.0478 20.25 21.0007 20.25C23.9534 20.2497 25.7507 17.854 25.7507 15.5469C25.7507 14.3489 25.2463 13.2169 24.6394 12.2773C24.0271 11.3294 23.2657 10.5091 22.6511 9.91992ZM20.3875 11.0029C20.7396 10.6657 21.2609 10.6657 21.613 11.0029C22.181 11.5474 22.8538 12.2767 23.3796 13.0908C23.9109 13.9134 24.2507 14.7575 24.2507 15.5469C24.2507 17.1367 23.0181 18.7497 21.0007 18.75C18.9831 18.75 17.7498 17.1369 17.7498 15.5469C17.7498 14.7575 18.0895 13.9134 18.6208 13.0908C19.1467 12.2767 19.8195 11.5474 20.3875 11.0029Z" fill="#8F9AA3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@ -1,3 +1,3 @@
|
||||
add_<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.1905 5.77665C20.59 6.15799 20.6047 6.79098 20.2234 7.19048L9.72336 18.1905C9.53745 18.3852 9.28086 18.4968 9.01163 18.4999C8.7424 18.5031 8.48328 18.3975 8.29289 18.2071L4.79289 14.7071C4.40237 14.3166 4.40237 13.6834 4.79289 13.2929C5.18342 12.9024 5.81658 12.9024 6.20711 13.2929L8.98336 16.0692L18.7766 5.80953C19.158 5.41003 19.791 5.39531 20.1905 5.77665Z" fill="white"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 537 B After Width: | Height: | Size: 533 B |
@ -0,0 +1,5 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.9999 9.25C17.9288 9.25 16.2499 10.9289 16.2499 13C16.2499 14.0736 16.805 15.1188 17.4741 15.8701C17.8161 16.2541 18.2118 16.5896 18.6284 16.8334C19.038 17.0732 19.5126 17.25 19.9999 17.25C20.4872 17.25 20.9617 17.0732 21.3714 16.8334C21.7879 16.5896 22.1837 16.2541 22.5257 15.8701C23.1947 15.1188 23.7499 14.0736 23.7499 13C23.7499 10.9289 22.0709 9.25 19.9999 9.25ZM17.7499 13C17.7499 11.7574 18.7572 10.75 19.9999 10.75C21.2425 10.75 22.2499 11.7574 22.2499 13C22.2499 13.5832 21.9259 14.2881 21.4054 14.8725C21.1526 15.1564 20.876 15.3853 20.6137 15.5388C20.3444 15.6964 20.1334 15.75 19.9999 15.75C19.8663 15.75 19.6554 15.6964 19.3861 15.5388C19.1238 15.3853 18.8471 15.1564 18.5943 14.8725C18.0738 14.2881 17.7499 13.5832 17.7499 13Z" fill="#8F9AA3"/>
|
||||
<path d="M24.536 16.4361C24.2245 16.163 23.7507 16.1941 23.4776 16.5056C23.2045 16.817 23.2356 17.2909 23.5471 17.5639C24.916 18.7642 25.6946 20.6997 24.9738 22.4145C24.8973 22.5965 24.7277 22.7039 24.5514 22.7039C24.4876 22.7039 24.4316 22.7013 24.3593 22.6978C24.3058 22.6953 24.2432 22.6923 24.1618 22.6897C24.0028 22.6845 23.7976 22.6828 23.5892 22.7127C23.3824 22.7424 23.1208 22.8099 22.8813 22.9807C22.6237 23.1644 22.4437 23.4309 22.3633 23.7588L21.266 28.2337C21.1173 28.8402 20.5889 29.25 20.0001 29.25C19.4112 29.25 18.8828 28.8402 18.7341 28.2337L17.6368 23.7588C17.5564 23.4309 17.3764 23.1644 17.1188 22.9807C16.8793 22.8099 16.6177 22.7424 16.4109 22.7127C16.2025 22.6828 15.9973 22.6845 15.8383 22.6897C15.7569 22.6923 15.6944 22.6953 15.6408 22.6978C15.5685 22.7013 15.5125 22.7039 15.4487 22.7039C15.2724 22.7039 15.1028 22.5965 15.0263 22.4145C14.3055 20.6997 15.0841 18.7642 16.4531 17.5639C16.7645 17.2909 16.7956 16.817 16.5226 16.5056C16.2495 16.1941 15.7756 16.163 15.4642 16.4361C13.7871 17.9065 12.5919 20.4941 13.6435 22.9958C13.9489 23.7223 14.6555 24.2039 15.4487 24.2039C15.5413 24.2039 15.6638 24.1985 15.7652 24.1939C15.8118 24.1918 15.8541 24.1899 15.8869 24.1889C16.0232 24.1845 16.1226 24.1867 16.1979 24.1975L16.2 24.1978L17.2772 28.591C17.5861 29.8506 18.7022 30.75 20.0001 30.75C21.2979 30.75 22.414 29.8506 22.7229 28.591L23.8001 24.1978L23.8022 24.1975C23.8775 24.1867 23.9769 24.1845 24.1132 24.1889C24.146 24.1899 24.1881 24.1918 24.2346 24.1939C24.336 24.1984 24.4588 24.2039 24.5514 24.2039C25.3446 24.2039 26.0512 23.7223 26.3566 22.9958C27.4082 20.4941 26.213 17.9065 24.536 16.4361Z" fill="#8F9AA3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.46313 15.0645C2.87679 15.044 3.22877 15.3627 3.24927 15.7764C3.35231 17.8551 3.66017 18.906 4.3772 19.623C5.09424 20.34 6.14517 20.6479 8.22388 20.751C8.63755 20.7715 8.95629 21.1234 8.93579 21.5371C8.91528 21.9508 8.56333 22.2695 8.14966 22.249C6.02926 22.1439 4.4621 21.829 3.31665 20.6836C2.17121 19.5382 1.85633 17.971 1.75122 15.8506C1.73072 15.4369 2.04943 15.085 2.46313 15.0645ZM21.5374 15.0645C21.951 15.085 22.2698 15.4369 22.2493 15.8506C22.1442 17.971 21.8292 19.5382 20.6838 20.6836C19.5384 21.829 17.9712 22.1439 15.8508 22.249C15.4371 22.2695 15.0852 21.9508 15.0647 21.5371C15.0442 21.1234 15.3629 20.7715 15.7766 20.751C17.8553 20.6479 18.9063 20.3401 19.6233 19.623C20.3403 18.906 20.6482 17.855 20.7512 15.7764C20.7717 15.3627 21.1236 15.044 21.5374 15.0645ZM12.0002 3.90039C13.1582 3.90046 14.0999 4.84198 14.0999 6C14.0998 6.84086 13.6022 7.56538 12.887 7.90039H13.136C14.762 7.90045 16.0999 9.11395 16.0999 10.624L16.0999 13.9297C16.0998 14.2828 15.7901 14.5546 15.427 14.5547H14.8362L14.4954 19.5156C14.4719 19.8513 14.1707 20.0996 13.8235 20.0996H10.1956C9.84928 20.0994 9.54928 19.8523 9.52466 19.5176V19.5166L9.16626 14.5547H8.57349C8.21034 14.5547 7.90065 14.2828 7.90063 13.9297L7.90063 10.624C7.90063 9.11391 9.23851 7.90039 10.8645 7.90039H11.1135C10.3982 7.56541 9.90065 6.84092 9.90063 6C9.90063 4.84194 10.8422 3.90039 12.0002 3.90039ZM14.3948 19.5078C14.3902 19.5747 14.3712 19.6378 14.342 19.6953C14.3561 19.6675 14.3685 19.6387 14.3772 19.6084L14.3958 19.5078L14.7434 14.4551L14.3948 19.5078ZM10.8645 9.14941C9.96391 9.14941 9.24536 9.81932 9.24536 10.624L9.24536 13.3057H9.79517C10.1415 13.3058 10.4415 13.5529 10.4661 13.8877V13.8887L10.8245 18.8506H13.1926L13.5334 13.8896C13.5569 13.5537 13.858 13.3047 14.2053 13.3047H14.7542L14.7542 10.624C14.7542 9.81935 14.0367 9.14947 13.136 9.14941H10.8645ZM8.00903 14.0225C8.00955 14.0251 8.01043 14.0277 8.01099 14.0303C8.00412 13.9978 8.00024 13.964 8.00024 13.9297L8.00903 14.0225ZM8.14966 1.75098C8.56334 1.73051 8.91529 2.04921 8.93579 2.46289C8.95628 2.87656 8.63754 3.22849 8.22388 3.24902C6.14515 3.35206 5.09424 3.65995 4.3772 4.37695C3.66016 5.09399 3.35231 6.14489 3.24927 8.22363C3.22877 8.6373 2.87679 8.95598 2.46313 8.93555C2.04943 8.91504 1.73072 8.56312 1.75122 8.14941C1.85633 6.02899 2.1712 4.46186 3.31665 3.31641C4.4621 2.17099 6.02924 1.85608 8.14966 1.75098ZM15.8508 1.75098C17.9712 1.85609 19.5384 2.17098 20.6838 3.31641C21.8292 4.46185 22.1442 6.02903 22.2493 8.14941C22.2698 8.56308 21.951 8.91498 21.5374 8.93555C21.1236 8.95605 20.7717 8.63734 20.7512 8.22363C20.6482 6.14495 20.3403 5.09399 19.6233 4.37695C18.9063 3.65993 17.8553 3.35207 15.7766 3.24902C15.3629 3.22852 15.0442 2.87659 15.0647 2.46289C15.0852 2.04919 15.4371 1.73047 15.8508 1.75098ZM12.0002 5.09961C11.504 5.09961 11.0999 5.50374 11.0999 6C11.0999 6.49625 11.504 6.90039 12.0002 6.90039C12.4964 6.90032 12.9006 6.49622 12.9006 6C12.9006 5.50378 12.4965 5.09968 12.0002 5.09961ZM10.0364 6.37793C10.0371 6.38156 10.0386 6.38505 10.0393 6.38867C10.0273 6.32802 10.0173 6.26657 10.011 6.2041L10.0364 6.37793Z" fill="#2B353E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
@ -0,0 +1,118 @@
|
||||
//
|
||||
// MainFlutterVC.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 25/03/1442 AH.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Flutter
|
||||
import NetworkExtension
|
||||
import SystemConfiguration.CaptiveNetwork
|
||||
|
||||
class MainFlutterVC: FlutterViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
// flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in
|
||||
//
|
||||
// if methodCall.method == "connectHMGInternetWifi"{
|
||||
// self.connectHMGInternetWifi(methodCall:methodCall, result: result)
|
||||
//
|
||||
// }else if methodCall.method == "connectHMGGuestWifi"{
|
||||
// self.connectHMGGuestWifi(methodCall:methodCall, result: result)
|
||||
//
|
||||
// }else if methodCall.method == "isHMGNetworkAvailable"{
|
||||
// self.isHMGNetworkAvailable(methodCall:methodCall, result: result)
|
||||
//
|
||||
// }else if methodCall.method == "registerHmgGeofences"{
|
||||
// self.registerHmgGeofences(result: result)
|
||||
// }
|
||||
//
|
||||
// print("")
|
||||
// }
|
||||
//
|
||||
// FlutterText.with(key: "errorConnectingHmgNetwork") { (localized) in
|
||||
// print(localized)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Connect HMG Wifi and Internet
|
||||
func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
|
||||
|
||||
guard let pateintId = (methodCall.arguments as? [Any])?.first as? String
|
||||
else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") }
|
||||
|
||||
|
||||
HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in
|
||||
result(status ? 1 : 0)
|
||||
if status{
|
||||
self.showMessage(title:"Congratulations", message:message)
|
||||
}else{
|
||||
self.showMessage(title:"Ooops,", message:message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect HMG-Guest for App Access
|
||||
func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
|
||||
HMG_GUEST.shared.connect() { (status, message) in
|
||||
result(status ? 1 : 0)
|
||||
if status{
|
||||
self.showMessage(title:"Congratulations", message:message)
|
||||
}else{
|
||||
self.showMessage(title:"Ooops,", message:message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{
|
||||
guard let ssid = methodCall.arguments as? String else {
|
||||
assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'")
|
||||
return false
|
||||
}
|
||||
|
||||
let queue = DispatchQueue.init(label: "com.hmg.wifilist")
|
||||
NEHotspotHelper.register(options: nil, queue: queue) { (command) in
|
||||
print(command)
|
||||
|
||||
if(command.commandType == NEHotspotHelperCommandType.filterScanList) {
|
||||
if let networkList = command.networkList{
|
||||
for network in networkList{
|
||||
print(network.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Message Dailog
|
||||
func showMessage(title:String, message:String){
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
|
||||
self.present(alert, animated: true) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register Geofence
|
||||
func registerHmgGeofences(result: @escaping FlutterResult){
|
||||
flutterMethodChannel?.invokeMethod("getGeofencePreferenceKey", arguments: nil){ geoFencesJsonString in
|
||||
if let jsonString = geoFencesJsonString as? String{
|
||||
let allZones = GeoZoneModel.list(from: jsonString)
|
||||
HMG_Geofence().register(geoZones: allZones)
|
||||
|
||||
}else{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
//
|
||||
// API.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 04/04/1442 AH.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
fileprivate let DOMAIN = "https://uat.hmgwebservices.com"
|
||||
fileprivate let SERVICE = "Services/Patients.svc/REST"
|
||||
fileprivate let BASE_URL = "\(DOMAIN)/\(SERVICE)"
|
||||
|
||||
struct API {
|
||||
static let WIFI_CREDENTIALS = "\(BASE_URL)/Hmg_SMS_Get_By_ProjectID_And_PatientID"
|
||||
}
|
||||
|
||||
|
||||
//struct API {
|
||||
// static let WIFI_CREDENTIALS = FlutterConstants.WIFI_CREDENTIALS_URL
|
||||
// static let LOG_GEOFENCE = FlutterConstants.LOG_GEOFENCE_URL
|
||||
//}
|
||||
@ -0,0 +1,150 @@
|
||||
//
|
||||
// Extensions.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 04/04/1442 AH.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
extension String{
|
||||
func toUrl() -> URL?{
|
||||
return URL(string: self)
|
||||
}
|
||||
|
||||
func removeSpace() -> String?{
|
||||
return self.replacingOccurrences(of: " ", with: "")
|
||||
}
|
||||
}
|
||||
|
||||
extension Date{
|
||||
func toString(format:String) -> String{
|
||||
let df = DateFormatter()
|
||||
df.dateFormat = format
|
||||
return df.string(from: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension Dictionary{
|
||||
func merge(dict:[String:Any?]) -> [String:Any?]{
|
||||
var self_ = self as! [String:Any?]
|
||||
dict.forEach { (kv) in
|
||||
self_.updateValue(kv.value, forKey: kv.key)
|
||||
}
|
||||
return self_
|
||||
}
|
||||
}
|
||||
|
||||
extension Bundle {
|
||||
|
||||
func certificate(named name: String) -> SecCertificate {
|
||||
let cerURL = self.url(forResource: name, withExtension: "cer")!
|
||||
let cerData = try! Data(contentsOf: cerURL)
|
||||
let cer = SecCertificateCreateWithData(nil, cerData as CFData)!
|
||||
return cer
|
||||
}
|
||||
|
||||
func identity(named name: String, password: String) -> SecIdentity {
|
||||
let p12URL = self.url(forResource: name, withExtension: "p12")!
|
||||
let p12Data = try! Data(contentsOf: p12URL)
|
||||
|
||||
var importedCF: CFArray? = nil
|
||||
let options = [kSecImportExportPassphrase as String: password]
|
||||
let err = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &importedCF)
|
||||
precondition(err == errSecSuccess)
|
||||
let imported = importedCF! as NSArray as! [[String:AnyObject]]
|
||||
precondition(imported.count == 1)
|
||||
|
||||
return (imported[0][kSecImportItemIdentity as String]!) as! SecIdentity
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SecCertificate{
|
||||
func trust() -> Bool?{
|
||||
var optionalTrust: SecTrust?
|
||||
let policy = SecPolicyCreateBasicX509()
|
||||
|
||||
let status = SecTrustCreateWithCertificates([self] as AnyObject,
|
||||
policy,
|
||||
&optionalTrust)
|
||||
guard status == errSecSuccess else { return false}
|
||||
let trust = optionalTrust!
|
||||
|
||||
let stat = optionalTrust?.evaluateAllowing(rootCertificates: [self])
|
||||
return stat
|
||||
}
|
||||
|
||||
func secTrustObject() -> SecTrust?{
|
||||
var optionalTrust: SecTrust?
|
||||
let policy = SecPolicyCreateBasicX509()
|
||||
|
||||
let status = SecTrustCreateWithCertificates([self] as AnyObject,
|
||||
policy,
|
||||
&optionalTrust)
|
||||
return optionalTrust
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension SecTrust {
|
||||
|
||||
func evaluate() -> Bool {
|
||||
var trustResult: SecTrustResultType = .invalid
|
||||
let err = SecTrustEvaluate(self, &trustResult)
|
||||
guard err == errSecSuccess else { return false }
|
||||
return [.proceed, .unspecified].contains(trustResult)
|
||||
}
|
||||
|
||||
func evaluateAllowing(rootCertificates: [SecCertificate]) -> Bool {
|
||||
|
||||
// Apply our custom root to the trust object.
|
||||
|
||||
var err = SecTrustSetAnchorCertificates(self, rootCertificates as CFArray)
|
||||
guard err == errSecSuccess else { return false }
|
||||
|
||||
// Re-enable the system's built-in root certificates.
|
||||
|
||||
err = SecTrustSetAnchorCertificatesOnly(self, false)
|
||||
guard err == errSecSuccess else { return false }
|
||||
|
||||
// Run a trust evaluation and only allow the connection if it succeeds.
|
||||
|
||||
return self.evaluate()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension UIView{
|
||||
func show(){
|
||||
self.alpha = 0.0
|
||||
self.isHidden = false
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.alpha = 1
|
||||
}) { (complete) in
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func hide(){
|
||||
UIView.animate(withDuration: 0.25, animations: {
|
||||
self.alpha = 0.0
|
||||
}) { (complete) in
|
||||
self.isHidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension UIViewController{
|
||||
func showAlert(withTitle: String, message: String){
|
||||
let alert = UIAlertController(title: withTitle, message: message, preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
|
||||
present(alert, animated: true) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
//
|
||||
// FlutterConstants.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 22/12/2020.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class FlutterConstants{
|
||||
static var LOG_GEOFENCE_URL:String?
|
||||
static var WIFI_CREDENTIALS_URL:String?
|
||||
static var DEFAULT_HTTP_PARAMS:[String:Any?]?
|
||||
|
||||
class func set(){
|
||||
|
||||
// (FiX) Take a start with FlutterMethodChannel (kikstart)
|
||||
/* First call to flutter method is not returning the correct value (Always returning 'NSObject') then after it wroking fine and returning correct value*/
|
||||
FlutterText.with(key: "test") { (test) in
|
||||
|
||||
flutterMethodChannel?.invokeMethod("getDefaultHttpParameters", arguments: nil){ (response) in
|
||||
if let defaultHTTPParams = response as? [String:Any?]{
|
||||
DEFAULT_HTTP_PARAMS = defaultHTTPParams
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flutterMethodChannel?.invokeMethod("getLogGeofenceFullUrl", arguments:nil){ (response) in
|
||||
if let url = response as? String{
|
||||
LOG_GEOFENCE_URL = url
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
//
|
||||
// GeoZoneModel.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 13/12/2020.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreLocation
|
||||
|
||||
class GeoZoneModel{
|
||||
var geofenceId:Int = -1
|
||||
var description:String = ""
|
||||
var descriptionN:String?
|
||||
var latitude:String?
|
||||
var longitude:String?
|
||||
var radius:Int?
|
||||
var type:Int?
|
||||
var projectID:Int?
|
||||
var imageURL:String?
|
||||
var isCity:String?
|
||||
|
||||
func identifier() -> String{
|
||||
return "\(geofenceId)_hmg"
|
||||
}
|
||||
|
||||
func message() -> String{
|
||||
return description
|
||||
}
|
||||
|
||||
func toRegion(locationManager:CLLocationManager) -> CLCircularRegion?{
|
||||
if let rad = radius, let lat = latitude?.removeSpace(), let long = longitude?.removeSpace(),
|
||||
let radius_d = Double("\(rad)"), let lat_d = Double(lat), let long_d = Double(long){
|
||||
|
||||
let coordinate = CLLocationCoordinate2D(latitude: lat_d, longitude: long_d)
|
||||
let validatedRadius = min(radius_d, locationManager.maximumRegionMonitoringDistance)
|
||||
|
||||
let region = CLCircularRegion(center: coordinate, radius: validatedRadius, identifier: identifier())
|
||||
region.notifyOnExit = true
|
||||
region.notifyOnEntry = true
|
||||
return region
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
class func from(json:[String:Any]) -> GeoZoneModel{
|
||||
let model = GeoZoneModel()
|
||||
model.geofenceId = json["GEOF_ID"] as? Int ?? 0
|
||||
model.radius = json["Radius"] as? Int
|
||||
model.projectID = json["ProjectID"] as? Int
|
||||
model.type = json["Type"] as? Int
|
||||
model.description = json["Description"] as? String ?? ""
|
||||
model.descriptionN = json["DescriptionN"] as? String
|
||||
model.latitude = json["Latitude"] as? String
|
||||
model.longitude = json["Longitude"] as? String
|
||||
model.imageURL = json["ImageURL"] as? String
|
||||
model.isCity = json["IsCity"] as? String
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
class func list(from jsonString:String) -> [GeoZoneModel]{
|
||||
let value = dictionaryArray(from: jsonString)
|
||||
let geoZones = value.map { GeoZoneModel.from(json: $0) }
|
||||
return geoZones
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
//
|
||||
// GlobalHelper.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 29/03/1442 AH.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
func dictionaryArray(from:String) -> [[String:Any]]{
|
||||
if let data = from.data(using: .utf8) {
|
||||
do {
|
||||
return try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] ?? []
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
return []
|
||||
|
||||
}
|
||||
|
||||
func dictionary(from:String) -> [String:Any]?{
|
||||
if let data = from.data(using: .utf8) {
|
||||
do {
|
||||
return try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
let HmgLocalNotificationCategoryIdentifier = "hmg.local.notification"
|
||||
func showNotification(identifier:String? = nil, title:String?, subtitle:String?, message:String?, sound:UNNotificationSound = UNNotificationSound.default, categoryIdentifier:String = HmgLocalNotificationCategoryIdentifier){
|
||||
DispatchQueue.main.async {
|
||||
let notificationContent = UNMutableNotificationContent()
|
||||
notificationContent.categoryIdentifier = categoryIdentifier
|
||||
|
||||
if identifier != nil { notificationContent.categoryIdentifier = identifier! }
|
||||
if title != nil { notificationContent.title = title! }
|
||||
if subtitle != nil { notificationContent.body = message! }
|
||||
if message != nil { notificationContent.subtitle = subtitle! }
|
||||
|
||||
notificationContent.sound = UNNotificationSound.default
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSinceNow)", content: notificationContent, trigger: trigger)
|
||||
|
||||
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error = error {
|
||||
print("Error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appLanguageCode() -> Int{
|
||||
let lang = UserDefaults.standard.string(forKey: "language") ?? "ar"
|
||||
return lang == "ar" ? 2 : 1
|
||||
}
|
||||
|
||||
func userProfile() -> [String:Any?]?{
|
||||
var userProf = UserDefaults.standard.string(forKey: "flutter.imei-user-data")
|
||||
if(userProf == nil){
|
||||
userProf = UserDefaults.standard.string(forKey: "flutter.user-profile")
|
||||
}
|
||||
return dictionary(from: userProf ?? "{}")
|
||||
}
|
||||
|
||||
fileprivate let defaultHTTPParams:[String : Any?] = [
|
||||
"ZipCode" : "966",
|
||||
"VersionID" : 5.8,
|
||||
"Channel" : 3,
|
||||
"LanguageID" : appLanguageCode(),
|
||||
"IPAdress" : "10.20.10.20",
|
||||
"generalid" : "Cs2020@2016$2958",
|
||||
"PatientOutSA" : 0,
|
||||
"SessionID" : nil,
|
||||
"isDentalAllowedBackend" : false,
|
||||
"DeviceTypeID" : 2
|
||||
]
|
||||
|
||||
func httpPostRequest(urlString:String, jsonBody:[String:Any?], completion:((Bool,[String:Any]?)->Void)?){
|
||||
var json: [String: Any?] = jsonBody
|
||||
json = json.merge(dict: defaultHTTPParams)
|
||||
let jsonData = try? JSONSerialization.data(withJSONObject: json)
|
||||
|
||||
// create post request
|
||||
let url = URL(string: urlString)!
|
||||
var request = URLRequest(url: url)
|
||||
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.addValue("*/*", forHTTPHeaderField: "Accept")
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = jsonData
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
guard let data = data, error == nil else {
|
||||
print(error?.localizedDescription ?? "No data")
|
||||
return
|
||||
}
|
||||
|
||||
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
|
||||
if let responseJSON = responseJSON as? [String: Any], let status = responseJSON["MessageStatus"] as? Int{
|
||||
print(responseJSON)
|
||||
if status == 1{
|
||||
completion?(true,responseJSON)
|
||||
}else{
|
||||
completion?(false,responseJSON)
|
||||
}
|
||||
|
||||
}else{
|
||||
completion?(false,nil)
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
import Foundation
|
||||
import FLAnimatedImage
|
||||
|
||||
|
||||
var flutterMethodChannelPenguinIn:FlutterMethodChannel? = nil
|
||||
fileprivate var mainViewController:MainFlutterVC!
|
||||
|
||||
class HMGPenguinInPlatformBridge{
|
||||
|
||||
private let channelName = "launch_penguin_ui"
|
||||
private static var shared_:HMGPenguinInPlatformBridge?
|
||||
|
||||
class func initialize(flutterViewController:MainFlutterVC){
|
||||
shared_ = HMGPenguinInPlatformBridge()
|
||||
mainViewController = flutterViewController
|
||||
shared_?.openChannel()
|
||||
}
|
||||
|
||||
func shared() -> HMGPenguinInPlatformBridge{
|
||||
assert((HMGPenguinInPlatformBridge.shared_ != nil), "HMGPenguinInPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
|
||||
return HMGPenguinInPlatformBridge.shared_!
|
||||
}
|
||||
|
||||
private func openChannel(){
|
||||
flutterMethodChannelPenguinIn = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger)
|
||||
|
||||
flutterMethodChannelPenguinIn?.setMethodCallHandler { (methodCall, result) in
|
||||
print("Called function \(methodCall.method)")
|
||||
|
||||
if let arguments = methodCall.arguments as Any? {
|
||||
if methodCall.method == "launchPenguin"{
|
||||
print("====== launchPenguinView Launched =========")
|
||||
self.launchPenguinView(arguments: arguments, result: result)
|
||||
}
|
||||
} else {
|
||||
result(FlutterError(code: "INVALID_ARGUMENT", message: "Storyboard name is required", details: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func launchPenguinView(arguments: Any, result: @escaping FlutterResult) {
|
||||
|
||||
let penguinView = PenguinView(
|
||||
frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height),
|
||||
viewIdentifier: 0,
|
||||
arguments: arguments,
|
||||
binaryMessenger: mainViewController.binaryMessenger
|
||||
)
|
||||
|
||||
let penguinUIView = penguinView.view()
|
||||
penguinUIView.frame = mainViewController.view.bounds
|
||||
penguinUIView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
mainViewController.view.addSubview(penguinUIView)
|
||||
|
||||
guard let args = arguments as? [String: Any],
|
||||
let loaderImageData = args["loaderImage"] as? FlutterStandardTypedData else {
|
||||
print("loaderImage data not found in arguments")
|
||||
result(FlutterError(code: "ARGUMENT_ERROR", message: "Missing loaderImage data", details: nil))
|
||||
return
|
||||
}
|
||||
|
||||
let loadingOverlay = UIView(frame: UIScreen.main.bounds)
|
||||
loadingOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.5) // Semi-transparent overlay
|
||||
loadingOverlay.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
// Display the GIF using FLAnimatedImage
|
||||
let animatedImage = FLAnimatedImage(animatedGIFData: loaderImageData.data)
|
||||
let gifImageView = FLAnimatedImageView()
|
||||
gifImageView.animatedImage = animatedImage
|
||||
gifImageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
|
||||
gifImageView.center = loadingOverlay.center
|
||||
gifImageView.contentMode = .scaleAspectFit
|
||||
loadingOverlay.addSubview(gifImageView)
|
||||
|
||||
|
||||
if let window = UIApplication.shared.windows.first {
|
||||
window.addSubview(loadingOverlay)
|
||||
|
||||
} else {
|
||||
print("Error: Main window not found")
|
||||
}
|
||||
|
||||
penguinView.onSuccess = {
|
||||
// Hide and remove the loader
|
||||
DispatchQueue.main.async {
|
||||
loadingOverlay.removeFromSuperview()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
result(nil)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
//
|
||||
// HMGPlatformBridge.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 14/12/2020.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import NetworkExtension
|
||||
import SystemConfiguration.CaptiveNetwork
|
||||
|
||||
var flutterMethodChannel:FlutterMethodChannel? = nil
|
||||
fileprivate var mainViewController:MainFlutterVC!
|
||||
|
||||
class HMGPlatformBridge{
|
||||
private let channelName = "HMG-Platform-Bridge"
|
||||
private static var shared_:HMGPlatformBridge?
|
||||
|
||||
class func initialize(flutterViewController:MainFlutterVC){
|
||||
shared_ = HMGPlatformBridge()
|
||||
mainViewController = flutterViewController
|
||||
shared_?.openChannel()
|
||||
}
|
||||
|
||||
func shared() -> HMGPlatformBridge{
|
||||
assert((HMGPlatformBridge.shared_ != nil), "HMGPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
|
||||
return HMGPlatformBridge.shared_!
|
||||
}
|
||||
|
||||
private func openChannel(){
|
||||
flutterMethodChannel = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger)
|
||||
flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in
|
||||
print("Called function \(methodCall.method)")
|
||||
if methodCall.method == "connectHMGInternetWifi"{
|
||||
self.connectHMGInternetWifi(methodCall:methodCall, result: result)
|
||||
|
||||
}else if methodCall.method == "connectHMGGuestWifi"{
|
||||
self.connectHMGGuestWifi(methodCall:methodCall, result: result)
|
||||
|
||||
}else if methodCall.method == "isHMGNetworkAvailable"{
|
||||
self.isHMGNetworkAvailable(methodCall:methodCall, result: result)
|
||||
|
||||
}else if methodCall.method == "registerHmgGeofences"{
|
||||
self.registerHmgGeofences(result: result)
|
||||
|
||||
}else if methodCall.method == "unRegisterHmgGeofences"{
|
||||
self.unRegisterHmgGeofences(result: result)
|
||||
}
|
||||
|
||||
print("")
|
||||
}
|
||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
|
||||
FlutterConstants.set()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Connect HMG Wifi and Internet
|
||||
func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
|
||||
|
||||
guard let pateintId = (methodCall.arguments as? [Any])?.first as? String
|
||||
else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") }
|
||||
|
||||
|
||||
HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in
|
||||
result(status ? 1 : 0)
|
||||
if status{
|
||||
self.showMessage(title:"Congratulations", message:message)
|
||||
}else{
|
||||
self.showMessage(title:"Ooops,", message:message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Connect HMG-Guest for App Access
|
||||
func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){
|
||||
HMG_GUEST.shared.connect() { (status, message) in
|
||||
result(status ? 1 : 0)
|
||||
if status{
|
||||
self.showMessage(title:"Congratulations", message:message)
|
||||
}else{
|
||||
self.showMessage(title:"Ooops,", message:message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{
|
||||
guard let ssid = methodCall.arguments as? String else {
|
||||
assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'")
|
||||
return false
|
||||
}
|
||||
|
||||
let queue = DispatchQueue.init(label: "com.hmg.wifilist")
|
||||
NEHotspotHelper.register(options: nil, queue: queue) { (command) in
|
||||
print(command)
|
||||
|
||||
if(command.commandType == NEHotspotHelperCommandType.filterScanList) {
|
||||
if let networkList = command.networkList{
|
||||
for network in networkList{
|
||||
print(network.ssid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Message Dailog
|
||||
func showMessage(title:String, message:String){
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil))
|
||||
mainViewController.present(alert, animated: true) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register Geofence
|
||||
func registerHmgGeofences(result: @escaping FlutterResult){
|
||||
flutterMethodChannel?.invokeMethod("getGeoZones", arguments: nil){ geoFencesJsonString in
|
||||
if let jsonString = geoFencesJsonString as? String{
|
||||
let allZones = GeoZoneModel.list(from: jsonString)
|
||||
HMG_Geofence.shared().register(geoZones: allZones)
|
||||
result(true)
|
||||
}else{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register Geofence
|
||||
func unRegisterHmgGeofences(result: @escaping FlutterResult){
|
||||
HMG_Geofence.shared().unRegisterAll()
|
||||
result(true)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
//
|
||||
// HMG_Geofence.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 13/12/2020.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreLocation
|
||||
|
||||
fileprivate var df = DateFormatter()
|
||||
fileprivate var transition = ""
|
||||
|
||||
enum Transition:Int {
|
||||
case entry = 1
|
||||
case exit = 2
|
||||
func name() -> String{
|
||||
return self.rawValue == 1 ? "Enter" : "Exit"
|
||||
}
|
||||
}
|
||||
|
||||
class HMG_Geofence:NSObject{
|
||||
|
||||
var geoZones:[GeoZoneModel]?
|
||||
var locationManager:CLLocationManager!{
|
||||
didSet{
|
||||
// https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati
|
||||
|
||||
locationManager.allowsBackgroundLocationUpdates = true
|
||||
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
||||
locationManager.activityType = .other
|
||||
locationManager.delegate = self
|
||||
locationManager.requestAlwaysAuthorization()
|
||||
// locationManager.distanceFilter = 500
|
||||
// locationManager.startMonitoringSignificantLocationChanges()
|
||||
}
|
||||
}
|
||||
|
||||
private static var shared_:HMG_Geofence?
|
||||
class func shared() -> HMG_Geofence{
|
||||
if HMG_Geofence.shared_ == nil{
|
||||
HMG_Geofence.initGeofencing()
|
||||
}
|
||||
return shared_!
|
||||
}
|
||||
|
||||
class func initGeofencing(){
|
||||
shared_ = HMG_Geofence()
|
||||
shared_?.locationManager = CLLocationManager()
|
||||
}
|
||||
|
||||
func register(geoZones:[GeoZoneModel]){
|
||||
|
||||
self.geoZones = geoZones
|
||||
|
||||
let monitoredRegions_ = monitoredRegions()
|
||||
self.geoZones?.forEach({ (zone) in
|
||||
if let region = zone.toRegion(locationManager: locationManager){
|
||||
if let already = monitoredRegions_.first(where: {$0.identifier == zone.identifier()}){
|
||||
debugPrint("Already monitering region: \(already)")
|
||||
}else{
|
||||
startMonitoring(region: region)
|
||||
}
|
||||
}else{
|
||||
debugPrint("Invalid region: \(zone.latitude ?? "invalid_latitude"),\(zone.longitude ?? "invalid_longitude"),r\(zone.radius ?? 0) | \(zone.identifier())")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func monitoredRegions() -> Set<CLRegion>{
|
||||
return locationManager.monitoredRegions
|
||||
}
|
||||
|
||||
func unRegisterAll(){
|
||||
for region in locationManager.monitoredRegions {
|
||||
locationManager.stopMonitoring(for: region)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CLLocationManager Delegates
|
||||
extension HMG_Geofence : CLLocationManagerDelegate{
|
||||
|
||||
func startMonitoring(region: CLCircularRegion) {
|
||||
if !CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
|
||||
return
|
||||
}
|
||||
|
||||
if CLLocationManager.authorizationStatus() != .authorizedAlways {
|
||||
let message = """
|
||||
Your geotification is saved but will only be activated once you grant
|
||||
HMG permission to access the device location.
|
||||
"""
|
||||
debugPrint(message)
|
||||
}
|
||||
|
||||
locationManager.startMonitoring(for: region)
|
||||
locationManager.requestState(for: region)
|
||||
debugPrint("Starts monitering region: \(region)")
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
|
||||
debugPrint("didEnterRegion: \(region)")
|
||||
if region is CLCircularRegion {
|
||||
handleEvent(for: region,transition: .entry, location: manager.location)
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
|
||||
debugPrint("didExitRegion: \(region)")
|
||||
if region is CLCircularRegion {
|
||||
handleEvent(for: region,transition: .exit, location: manager.location)
|
||||
}
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
|
||||
debugPrint("didDetermineState: \(state)")
|
||||
}
|
||||
|
||||
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
|
||||
debugPrint("didUpdateLocations: \(locations)")
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Helpers
|
||||
extension HMG_Geofence{
|
||||
|
||||
func handleEvent(for region: CLRegion!, transition:Transition, location:CLLocation?) {
|
||||
if let userProfile = userProfile(){
|
||||
notifyUser(forRegion: region, transition: transition, location: locationManager.location, userProfile: userProfile)
|
||||
notifyServer(forRegion: region, transition: transition, location: locationManager.location, userProfile: userProfile)
|
||||
}
|
||||
}
|
||||
|
||||
func geoZone(by id: String) -> GeoZoneModel? {
|
||||
var zone:GeoZoneModel? = nil
|
||||
if let zones_ = geoZones{
|
||||
zone = zones_.first(where: { $0.identifier() == id})
|
||||
}else{
|
||||
// let jsonArray = UserDefaults.standard.string(forKey: "hmg-geo-fences")
|
||||
}
|
||||
return zone
|
||||
}
|
||||
|
||||
|
||||
func notifyUser(forRegion:CLRegion, transition:Transition, location:CLLocation?, userProfile:[String:Any?]){
|
||||
if let patientId = userProfile["PatientID"] as? Int{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func notifyServer(forRegion:CLRegion, transition:Transition, location:CLLocation?, userProfile:[String:Any?]){
|
||||
if let patientId = userProfile["PatientID"] as? Int{
|
||||
|
||||
if let idString = forRegion.identifier.split(separator: "_").first, let idInt = Int(idString){
|
||||
let body:[String:Any] = [
|
||||
"PointsID":idInt,
|
||||
"GeoType":transition.rawValue,
|
||||
"PatientID":patientId
|
||||
]
|
||||
|
||||
var logs = UserDefaults.init(suiteName: "GeoFenceLog")?.dictionary(forKey: "GEOFENCE_LOGS") ?? [:]
|
||||
var geo = (logs[forRegion.identifier] as? [String]) ?? []
|
||||
|
||||
let url = "https://hmgwebservices.com/Services/Patients.svc/REST/GeoF_InsertPatientFileInfo"
|
||||
httpPostRequest(urlString: url, jsonBody: body){ (status,json) in
|
||||
let status_ = status ? "Notified successfully:" : "Failed to notify:"
|
||||
showNotification(title: transition.name(), subtitle: forRegion.identifier, message: status_)
|
||||
|
||||
|
||||
geo.append("\(status_) \(transition.name()) at \(Date().toString(format: "dd/MMM/yyy hh:mm:ss"))")
|
||||
logs.updateValue( geo, forKey: forRegion.identifier)
|
||||
|
||||
UserDefaults.init(suiteName: "GeoFenceLog")?.set(logs, forKey: "GEOFENCE_LOGS")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
//
|
||||
// LocalizedFromFlutter.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 10/04/1442 AH.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class FlutterText{
|
||||
|
||||
class func with(key:String,completion: @escaping (String)->Void){
|
||||
flutterMethodChannel?.invokeMethod("localizedValue", arguments: key, result: { (result) in
|
||||
if let localized = result as? String{
|
||||
completion(localized)
|
||||
}else{
|
||||
completion(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
//
|
||||
// HMGPlatformBridge.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by ZiKambrani on 14/12/2020.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import NetworkExtension
|
||||
import SystemConfiguration.CaptiveNetwork
|
||||
|
||||
|
||||
fileprivate var openTok:OpenTok?
|
||||
|
||||
class OpenTokPlatformBridge : NSObject{
|
||||
private var methodChannel:FlutterMethodChannel? = nil
|
||||
private var mainViewController:MainFlutterVC!
|
||||
private static var shared_:OpenTokPlatformBridge?
|
||||
|
||||
class func initialize(flutterViewController:MainFlutterVC, registrar:FlutterPluginRegistrar?){
|
||||
shared_ = OpenTokPlatformBridge()
|
||||
shared_?.mainViewController = flutterViewController
|
||||
|
||||
shared_?.openChannel()
|
||||
openTok = OpenTok(mainViewController: flutterViewController, registrar: registrar)
|
||||
}
|
||||
|
||||
func shared() -> OpenTokPlatformBridge{
|
||||
assert((OpenTokPlatformBridge.shared_ != nil), "OpenTokPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.")
|
||||
return OpenTokPlatformBridge.shared_!
|
||||
}
|
||||
|
||||
private func openChannel(){
|
||||
methodChannel = FlutterMethodChannel(name: "OpenTok-Platform-Bridge", binaryMessenger: mainViewController.binaryMessenger)
|
||||
methodChannel?.setMethodCallHandler { (call, result) in
|
||||
print("Called function \(call.method)")
|
||||
|
||||
switch(call.method) {
|
||||
case "initSession":
|
||||
openTok?.initSession(call: call, result: result)
|
||||
|
||||
case "swapCamera":
|
||||
openTok?.swapCamera(call: call, result: result)
|
||||
|
||||
case "toggleAudio":
|
||||
openTok?.toggleAudio(call: call, result: result)
|
||||
|
||||
case "toggleVideo":
|
||||
openTok?.toggleVideo(call: call, result: result)
|
||||
|
||||
case "hangupCall":
|
||||
openTok?.hangupCall(call: call, result: result)
|
||||
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
|
||||
print("")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
//
|
||||
// PenguinModel.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Amir on 06/08/2024.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// Define the model class
|
||||
struct PenguinModel {
|
||||
let baseURL: String
|
||||
let dataURL: String
|
||||
let dataServiceName: String
|
||||
let positionURL: String
|
||||
let clientKey: String
|
||||
let storyboardName: String
|
||||
let mapBoxKey: String
|
||||
let clientID: String
|
||||
let positionServiceName: String
|
||||
let username: String
|
||||
let isSimulationModeEnabled: Bool
|
||||
let isShowUserName: Bool
|
||||
let isUpdateUserLocationSmoothly: Bool
|
||||
let isEnableReportIssue: Bool
|
||||
let languageCode: String
|
||||
let clinicID: String
|
||||
let patientID: String
|
||||
let projectID: String
|
||||
|
||||
// Initialize the model from a dictionary
|
||||
init?(from dictionary: [String: Any]) {
|
||||
guard
|
||||
let baseURL = dictionary["baseURL"] as? String,
|
||||
let dataURL = dictionary["dataURL"] as? String,
|
||||
let dataServiceName = dictionary["dataServiceName"] as? String,
|
||||
let positionURL = dictionary["positionURL"] as? String,
|
||||
let clientKey = dictionary["clientKey"] as? String,
|
||||
let storyboardName = dictionary["storyboardName"] as? String,
|
||||
let mapBoxKey = dictionary["mapBoxKey"] as? String,
|
||||
let clientID = dictionary["clientID"] as? String,
|
||||
let positionServiceName = dictionary["positionServiceName"] as? String,
|
||||
let username = dictionary["username"] as? String,
|
||||
let isSimulationModeEnabled = dictionary["isSimulationModeEnabled"] as? Bool,
|
||||
let isShowUserName = dictionary["isShowUserName"] as? Bool,
|
||||
let isUpdateUserLocationSmoothly = dictionary["isUpdateUserLocationSmoothly"] as? Bool,
|
||||
let isEnableReportIssue = dictionary["isEnableReportIssue"] as? Bool,
|
||||
let languageCode = dictionary["languageCode"] as? String,
|
||||
let clinicID = dictionary["clinicID"] as? String,
|
||||
let patientID = dictionary["patientID"] as? String,
|
||||
let projectID = dictionary["projectID"] as? String
|
||||
else {
|
||||
print("Initialization failed due to missing or invalid keys.")
|
||||
return nil
|
||||
}
|
||||
|
||||
self.baseURL = baseURL
|
||||
self.dataURL = dataURL
|
||||
self.dataServiceName = dataServiceName
|
||||
self.positionURL = positionURL
|
||||
self.clientKey = clientKey
|
||||
self.storyboardName = storyboardName
|
||||
self.mapBoxKey = mapBoxKey
|
||||
self.clientID = clientID
|
||||
self.positionServiceName = positionServiceName
|
||||
self.username = username
|
||||
self.isSimulationModeEnabled = isSimulationModeEnabled
|
||||
self.isShowUserName = isShowUserName
|
||||
self.isUpdateUserLocationSmoothly = isUpdateUserLocationSmoothly
|
||||
self.isEnableReportIssue = isEnableReportIssue
|
||||
self.languageCode = languageCode
|
||||
self.clinicID = clinicID
|
||||
self.patientID = patientID
|
||||
self.projectID = projectID
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import PenNavUI
|
||||
import UIKit
|
||||
|
||||
class PenguinNavigator {
|
||||
private var config: PenguinModel
|
||||
|
||||
init(config: PenguinModel) {
|
||||
self.config = config
|
||||
}
|
||||
|
||||
private func logError(_ message: String) {
|
||||
// Centralized logging function
|
||||
print("PenguinSDKNavigator Error: \(message)")
|
||||
}
|
||||
|
||||
func navigateToPOI( referenceId:String,completion: @escaping (Bool, String?) -> Void) {
|
||||
PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey) { [weak self] token, error in
|
||||
|
||||
if let error = error {
|
||||
let errorMessage = "Token error while getting the for Navigate to method"
|
||||
completion(false, "Failed to get token: \(errorMessage)")
|
||||
|
||||
print("Failed to get token: \(errorMessage)")
|
||||
return
|
||||
}
|
||||
|
||||
guard let token = token else {
|
||||
completion(false, "Token is nil")
|
||||
print("Token is nil")
|
||||
return
|
||||
}
|
||||
print("Token Generated")
|
||||
print(token);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func handleNavigation(referenceId: String, token: String, completion: @escaping (Bool, String?) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
PenNavUIManager.shared.setToken(token: token)
|
||||
|
||||
PenNavUIManager.shared.navigate(to: referenceId) { [weak self] _, navError in
|
||||
guard let self = self else { return }
|
||||
|
||||
if let navError = navError {
|
||||
self.logError("Navigation error: Reference ID invalid")
|
||||
completion(false, "Navigation error: \(navError.localizedDescription)")
|
||||
return
|
||||
}
|
||||
|
||||
// Navigation successful
|
||||
completion(true, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
//
|
||||
// BlueGpsPlugin.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Penguin .
|
||||
//
|
||||
|
||||
//import Foundation
|
||||
//import Flutter
|
||||
//
|
||||
///**
|
||||
// * A Flutter plugin for integrating Penguin SDK functionality.
|
||||
// * This class registers a view factory with the Flutter engine to create native views.
|
||||
// */
|
||||
//class PenguinPlugin: NSObject, FlutterPlugin {
|
||||
//
|
||||
// /**
|
||||
// * Registers the plugin with the Flutter engine.
|
||||
// *
|
||||
// * @param registrar The [FlutterPluginRegistrar] used to register the plugin.
|
||||
// * This method is called when the plugin is initialized, and it sets up the communication
|
||||
// * between Flutter and native code.
|
||||
// */
|
||||
// public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
// // Create an instance of PenguinViewFactory with the binary messenger from the registrar
|
||||
// let factory = PenguinViewFactory(messenger: registrar.messenger())
|
||||
//
|
||||
// // Register the view factory with a unique ID for use in Flutter code
|
||||
// registrar.register(factory, withId: "penguin_native")
|
||||
// }
|
||||
//}
|
||||
@ -0,0 +1,445 @@
|
||||
//
|
||||
|
||||
// BlueGpsView.swift
|
||||
|
||||
// Runner
|
||||
|
||||
//
|
||||
|
||||
// Created by Penguin.
|
||||
|
||||
//
|
||||
|
||||
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Flutter
|
||||
import PenNavUI
|
||||
|
||||
import Foundation
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* A custom Flutter platform view for displaying Penguin UI components.
|
||||
|
||||
* This class integrates with the Penguin navigation SDK and handles UI events.
|
||||
|
||||
*/
|
||||
|
||||
class PenguinView: NSObject, FlutterPlatformView, PIEventsDelegate, PenNavInitializationDelegate
|
||||
|
||||
{
|
||||
// The main view displayed within the platform view
|
||||
|
||||
private var _view: UIView
|
||||
|
||||
private var model: PenguinModel?
|
||||
|
||||
private var methodChannel: FlutterMethodChannel
|
||||
|
||||
var onSuccess: (() -> Void)?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Initializes the PenguinView with the provided parameters.
|
||||
|
||||
*
|
||||
|
||||
* @param frame The frame of the view, specifying its size and position.
|
||||
|
||||
* @param viewId A unique identifier for this view instance.
|
||||
|
||||
* @param args Optional arguments provided for creating the view.
|
||||
|
||||
* @param messenger The [FlutterBinaryMessenger] used for communication with Dart.
|
||||
|
||||
*/
|
||||
|
||||
init(
|
||||
|
||||
frame: CGRect,
|
||||
|
||||
viewIdentifier viewId: Int64,
|
||||
|
||||
arguments args: Any?,
|
||||
|
||||
binaryMessenger messenger: FlutterBinaryMessenger?
|
||||
|
||||
) {
|
||||
|
||||
_view = UIView()
|
||||
|
||||
methodChannel = FlutterMethodChannel(name: "launch_penguin_ui", binaryMessenger: messenger!)
|
||||
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
|
||||
// Get the screen's width and height to set the view's frame
|
||||
|
||||
let screenWidth = UIScreen.main.bounds.width
|
||||
|
||||
let screenHeight = UIScreen.main.bounds.height
|
||||
|
||||
|
||||
|
||||
// Uncomment to set the background color of the view
|
||||
|
||||
// _view.backgroundColor = UIColor.red
|
||||
|
||||
|
||||
|
||||
// Set the frame of the view to cover the entire screen
|
||||
|
||||
_view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
|
||||
|
||||
print("========Inside Penguin View ========")
|
||||
|
||||
print(args)
|
||||
|
||||
guard let arguments = args as? [String: Any] else {
|
||||
|
||||
print("Error: Arguments are not in the expected format.")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
print("===== i got tha Args=======")
|
||||
|
||||
|
||||
|
||||
// Initialize the model from the arguments
|
||||
|
||||
if let penguinModel = PenguinModel(from: arguments) {
|
||||
|
||||
self.model = penguinModel
|
||||
|
||||
initPenguin(args: penguinModel)
|
||||
|
||||
} else {
|
||||
|
||||
print("Error: Failed to initialize PenguinModel from arguments ")
|
||||
|
||||
}
|
||||
|
||||
// Initialize the Penguin SDK with required configurations
|
||||
|
||||
// initPenguin( arguments: args)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Initializes the Penguin SDK with custom configuration settings.
|
||||
|
||||
*/
|
||||
|
||||
func initPenguin(args: PenguinModel) {
|
||||
|
||||
// Set the initialization delegate to handle SDK initialization events
|
||||
|
||||
PenNavUIManager.shared.initializationDelegate = self
|
||||
|
||||
// Configure the Penguin SDK with necessary parameters
|
||||
|
||||
PenNavUIManager.shared
|
||||
|
||||
.setClientKey(args.clientKey)
|
||||
|
||||
.setClientID(args.clientID)
|
||||
|
||||
.setUsername(args.username)
|
||||
|
||||
.setSimulationModeEnabled(isEnable: args.isSimulationModeEnabled)
|
||||
|
||||
.setBaseURL(dataURL: args.dataURL, positionURL: args.positionURL)
|
||||
|
||||
.setServiceName(dataServiceName: args.dataServiceName, positionServiceName: args.positionServiceName)
|
||||
|
||||
.setIsShowUserName(args.isShowUserName)
|
||||
|
||||
.setIsUpdateUserLocationSmoothly(args.isUpdateUserLocationSmoothly)
|
||||
|
||||
.setEnableReportIssue(enable: args.isEnableReportIssue)
|
||||
|
||||
.setLanguage(args.languageCode)
|
||||
|
||||
.setBackButtonVisibility(true)
|
||||
|
||||
.build()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Returns the main view associated with this platform view.
|
||||
|
||||
*
|
||||
|
||||
* @return The UIView instance that represents this platform view.
|
||||
|
||||
*/
|
||||
|
||||
func view() -> UIView {
|
||||
|
||||
return _view
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - PIEventsDelegate Methods
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Called when the Penguin UI is dismissed.
|
||||
|
||||
*/
|
||||
|
||||
func onPenNavUIDismiss() {
|
||||
|
||||
// Handle UI dismissal if needed
|
||||
|
||||
print("====== onPenNavUIDismiss =========")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
self.view().removeFromSuperview()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Called when a report issue is generated.
|
||||
|
||||
*
|
||||
|
||||
* @param issue The type of issue reported.
|
||||
|
||||
*/
|
||||
|
||||
func onReportIssue(_ issue: PenNavUI.IssueType) {
|
||||
|
||||
// Handle report issue events if needed
|
||||
|
||||
print("====== onReportIssueError =========")
|
||||
|
||||
methodChannel.invokeMethod("onReportIssue", arguments: ["issueType": issue])
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Called when the Penguin UI setup is successful.
|
||||
|
||||
*/
|
||||
|
||||
func onPenNavSuccess() {
|
||||
|
||||
print("====== onPenNavSuccess =========")
|
||||
|
||||
onSuccess?()
|
||||
|
||||
methodChannel.invokeMethod("onPenNavSuccess", arguments: nil)
|
||||
|
||||
// Obtain the FlutterViewController instance
|
||||
|
||||
let controller: FlutterViewController = UIApplication.shared.windows.first?.rootViewController as! FlutterViewController
|
||||
|
||||
|
||||
|
||||
print("====== after controller onPenNavSuccess =========")
|
||||
|
||||
|
||||
|
||||
// Set the events delegate to handle SDK events
|
||||
|
||||
PenNavUIManager.shared.eventsDelegate = self
|
||||
|
||||
|
||||
|
||||
print("====== after eventsDelegate onPenNavSuccess =========")
|
||||
|
||||
|
||||
|
||||
// Present the Penguin UI on top of the Flutter view controller
|
||||
|
||||
PenNavUIManager.shared.present(root: controller, view: _view)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
print("====== after present onPenNavSuccess =========")
|
||||
|
||||
print(model?.clinicID)
|
||||
|
||||
print("====== after present onPenNavSuccess =========")
|
||||
|
||||
|
||||
|
||||
guard let config = self.model else {
|
||||
|
||||
print("Error: Config Model is nil")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
guard let clinicID = self.model?.clinicID,
|
||||
|
||||
let clientID = self.model?.clientID, !clientID.isEmpty else {
|
||||
|
||||
print("Error: Config Client ID is nil or empty")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
let navigator = PenguinNavigator(config: config)
|
||||
|
||||
|
||||
|
||||
PenNavUIManager.shared.getToken(clientID: config.clientID, clientKey: config.clientKey) { [weak self] token, error in
|
||||
|
||||
if let error = error {
|
||||
|
||||
let errorMessage = "Token error while getting the for Navigate to method"
|
||||
|
||||
print("Failed to get token: \(errorMessage)")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
guard let token = token else {
|
||||
|
||||
print("Token is nil")
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
print("Token Generated")
|
||||
|
||||
print(token);
|
||||
|
||||
|
||||
|
||||
self?.handleNavigation(clinicID: clinicID, token: token) { success, errorMessage in
|
||||
|
||||
if success {
|
||||
|
||||
print("Navigation successful")
|
||||
|
||||
} else {
|
||||
|
||||
print("Navigation failed: \(errorMessage ?? "Unknown error")")
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
print("====== after Token onPenNavSuccess =========")
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private func handleNavigation(clinicID: String, token: String, completion: @escaping (Bool, String?) -> Void) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
PenNavUIManager.shared.setToken(token: token)
|
||||
|
||||
PenNavUIManager.shared.navigate(to: clinicID)
|
||||
|
||||
completion(true,nil)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
* Called when there is an initialization error with the Penguin UI.
|
||||
|
||||
*
|
||||
|
||||
* @param errorType The type of initialization error.
|
||||
|
||||
* @param errorDescription A description of the error.
|
||||
|
||||
*/
|
||||
|
||||
func onPenNavInitializationError(errorType: PenNavUI.PenNavUIError, errorDescription: String) {
|
||||
|
||||
// Handle initialization errors if needed
|
||||
|
||||
print("onPenNavInitializationErrorType: \(errorType.rawValue)")
|
||||
|
||||
print("onPenNavInitializationError: \(errorDescription)")
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
//
|
||||
// BlueGpsViewFactory.swift
|
||||
// Runner
|
||||
//
|
||||
// Created by Penguin .
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Flutter
|
||||
|
||||
/**
|
||||
* A factory class for creating instances of [PenguinView].
|
||||
* This class implements `FlutterPlatformViewFactory` to create and manage native views.
|
||||
*/
|
||||
class PenguinViewFactory: NSObject, FlutterPlatformViewFactory {
|
||||
|
||||
// The binary messenger used for communication with the Flutter engine
|
||||
private var messenger: FlutterBinaryMessenger
|
||||
|
||||
/**
|
||||
* Initializes the PenguinViewFactory with the given messenger.
|
||||
*
|
||||
* @param messenger The [FlutterBinaryMessenger] used to communicate with Dart code.
|
||||
*/
|
||||
init(messenger: FlutterBinaryMessenger) {
|
||||
self.messenger = messenger
|
||||
super.init()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of [PenguinView].
|
||||
*
|
||||
* @param frame The frame of the view, specifying its size and position.
|
||||
* @param viewId A unique identifier for this view instance.
|
||||
* @param args Optional arguments provided for creating the view.
|
||||
* @return An instance of [PenguinView] configured with the provided parameters.
|
||||
*/
|
||||
func create(
|
||||
withFrame frame: CGRect,
|
||||
viewIdentifier viewId: Int64,
|
||||
arguments args: Any?
|
||||
) -> FlutterPlatformView {
|
||||
return PenguinView(
|
||||
frame: frame,
|
||||
viewIdentifier: viewId,
|
||||
arguments: args,
|
||||
binaryMessenger: messenger)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec used for encoding and decoding method channel arguments.
|
||||
* This method is required when `arguments` in `create` is not `nil`.
|
||||
*
|
||||
* @return A [FlutterMessageCodec] instance used for serialization.
|
||||
*/
|
||||
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
|
||||
return FlutterStandardMessageCodec.sharedInstance()
|
||||
}
|
||||
}
|
||||
@ -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,81 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class BdProjectsHaveBdClinicsModel {
|
||||
List<BdGetProjectsHaveBdClinic>? bdGetProjectsHaveBdClinics;
|
||||
|
||||
BdProjectsHaveBdClinicsModel({
|
||||
this.bdGetProjectsHaveBdClinics,
|
||||
});
|
||||
|
||||
factory BdProjectsHaveBdClinicsModel.fromRawJson(String str) => BdProjectsHaveBdClinicsModel.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory BdProjectsHaveBdClinicsModel.fromJson(Map<String, dynamic> json) => BdProjectsHaveBdClinicsModel(
|
||||
bdGetProjectsHaveBdClinics: json["BD_getProjectsHaveBDClinics"] == null ? [] : List<BdGetProjectsHaveBdClinic>.from(json["BD_getProjectsHaveBDClinics"]!.map((x) => BdGetProjectsHaveBdClinic.fromJson(x))),
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"BD_getProjectsHaveBDClinics": bdGetProjectsHaveBdClinics == null ? [] : List<dynamic>.from(bdGetProjectsHaveBdClinics!.map((x) => x.toJson())),
|
||||
};
|
||||
}
|
||||
|
||||
class BdGetProjectsHaveBdClinic {
|
||||
int? rowId;
|
||||
int? id;
|
||||
int? projectId;
|
||||
int? numberOfRooms;
|
||||
bool? isActive;
|
||||
int? createdBy;
|
||||
String? createdOn;
|
||||
dynamic editedBy;
|
||||
dynamic editedOn;
|
||||
String? projectName;
|
||||
dynamic projectNameN;
|
||||
|
||||
BdGetProjectsHaveBdClinic({
|
||||
this.rowId,
|
||||
this.id,
|
||||
this.projectId,
|
||||
this.numberOfRooms,
|
||||
this.isActive,
|
||||
this.createdBy,
|
||||
this.createdOn,
|
||||
this.editedBy,
|
||||
this.editedOn,
|
||||
this.projectName,
|
||||
this.projectNameN,
|
||||
});
|
||||
|
||||
factory BdGetProjectsHaveBdClinic.fromRawJson(String str) => BdGetProjectsHaveBdClinic.fromJson(json.decode(str));
|
||||
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
factory BdGetProjectsHaveBdClinic.fromJson(Map<String, dynamic> json) => BdGetProjectsHaveBdClinic(
|
||||
rowId: json["RowID"],
|
||||
id: json["ID"],
|
||||
projectId: json["ProjectID"],
|
||||
numberOfRooms: json["NumberOfRooms"],
|
||||
isActive: json["IsActive"],
|
||||
createdBy: json["CreatedBy"],
|
||||
createdOn: json["CreatedOn"],
|
||||
editedBy: json["EditedBy"],
|
||||
editedOn: json["EditedON"],
|
||||
projectName: json["ProjectName"],
|
||||
projectNameN: json["ProjectNameN"],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"RowID": rowId,
|
||||
"ID": id,
|
||||
"ProjectID": projectId,
|
||||
"NumberOfRooms": numberOfRooms,
|
||||
"IsActive": isActive,
|
||||
"CreatedBy": createdBy,
|
||||
"CreatedOn": createdOn,
|
||||
"EditedBy": editedBy,
|
||||
"EditedON": editedOn,
|
||||
"ProjectName": projectName,
|
||||
"ProjectNameN": projectNameN,
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||
import 'package:hmg_patient_app_new/core/app_state.dart';
|
||||
import 'package:hmg_patient_app_new/core/dependencies.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
|
||||
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||
import 'package:hmg_patient_app_new/features/blood_donation/blood_donation_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/blood_donation/models/blood_group_hospitals_model.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class HospitalBottomSheetBodySelection extends StatelessWidget {
|
||||
final Function(BdGetProjectsHaveBdClinic userSelection) onUserHospitalSelection;
|
||||
|
||||
const HospitalBottomSheetBodySelection({super.key, required this.onUserHospitalSelection(BdGetProjectsHaveBdClinic userSelection)});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloodDonationVm = Provider.of<BloodDonationViewModel>(context, listen: false);
|
||||
AppState appState = getIt.get<AppState>();
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Please select the hospital you want to make an appointment.".needTranslation,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.greyTextColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
SizedBox(
|
||||
height: MediaQuery.sizeOf(context).height * .4,
|
||||
child: ListView.separated(
|
||||
itemBuilder: (_, index) {
|
||||
return DecoratedBox(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 8.h,
|
||||
children: [
|
||||
hospitalName(bloodDonationVm.hospitalList[index]).onPress(() {
|
||||
onUserHospitalSelection(bloodDonationVm.hospitalList[index]);
|
||||
Navigator.of(context).pop();
|
||||
})
|
||||
],
|
||||
),
|
||||
),
|
||||
Transform.flip(
|
||||
flipX: appState.isArabic(),
|
||||
child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.blackColor, width: 40.h, height: 40.h, fit: BoxFit.contain),
|
||||
),
|
||||
],
|
||||
).paddingSymmetrical(16.h, 16.h),
|
||||
).onPress(() {
|
||||
bloodDonationVm.setSelectedHospital(bloodDonationVm.hospitalList[index]);
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
},
|
||||
separatorBuilder: (_, __) => SizedBox(height: 16.h),
|
||||
itemCount: bloodDonationVm.hospitalList.length),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget hospitalName(dynamic hospital) => Row(
|
||||
children: [
|
||||
Utils.buildSvgWithAssets(icon: AppAssets.hmg).paddingOnly(right: 10),
|
||||
Expanded(
|
||||
child: Text(hospital.projectName ?? "", style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16, color: AppColors.blackColor)),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:hmg_patient_app_new/core/api/api_client.dart';
|
||||
import 'package:hmg_patient_app_new/core/api_consts.dart';
|
||||
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
|
||||
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
|
||||
import 'package:hmg_patient_app_new/services/logger_service.dart';
|
||||
|
||||
abstract class MonthlyReportRepo {
|
||||
Future<Either<Failure, GenericApiModel<dynamic>>> updatePatientHealthSummaryReport({required bool rSummaryReport});
|
||||
}
|
||||
|
||||
class MonthlyReportRepoImp implements MonthlyReportRepo {
|
||||
final ApiClient apiClient;
|
||||
final LoggerService loggerService;
|
||||
|
||||
MonthlyReportRepoImp({required this.loggerService, required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GenericApiModel<dynamic>>> updatePatientHealthSummaryReport({required bool rSummaryReport}) async {
|
||||
Map<String, dynamic> mapDevice = {
|
||||
"RSummaryReport": rSummaryReport,
|
||||
};
|
||||
|
||||
try {
|
||||
GenericApiModel<dynamic>? apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
UPDATE_HEALTH_TERMS,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
try {
|
||||
apiResponse = GenericApiModel<dynamic>(
|
||||
messageStatus: messageStatus,
|
||||
statusCode: statusCode,
|
||||
errorMessage: errorMessage,
|
||||
data: response,
|
||||
);
|
||||
} catch (e) {
|
||||
failure = DataParsingFailure(e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
|
||||
return Right(apiResponse!);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_repo.dart';
|
||||
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
|
||||
|
||||
class MonthlyReportViewModel extends ChangeNotifier {
|
||||
MonthlyReportRepo monthlyReportRepo;
|
||||
ErrorHandlerService errorHandlerService;
|
||||
|
||||
bool isUpdateHealthSummaryLoading = false;
|
||||
bool isHealthSummaryEnabled = false;
|
||||
|
||||
MonthlyReportViewModel({
|
||||
required this.monthlyReportRepo,
|
||||
required this.errorHandlerService,
|
||||
});
|
||||
|
||||
setHealthSummaryEnabled(bool value) {
|
||||
isHealthSummaryEnabled = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updatePatientHealthSummaryReport({
|
||||
required bool rSummaryReport,
|
||||
Function(dynamic)? onSuccess,
|
||||
Function(String)? onError,
|
||||
}) async {
|
||||
isUpdateHealthSummaryLoading = true;
|
||||
notifyListeners();
|
||||
|
||||
final result = await monthlyReportRepo.updatePatientHealthSummaryReport(
|
||||
rSummaryReport: rSummaryReport,
|
||||
);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
isUpdateHealthSummaryLoading = false;
|
||||
notifyListeners();
|
||||
await errorHandlerService.handleError(failure: failure);
|
||||
if (onError != null) {
|
||||
onError(failure.toString());
|
||||
}
|
||||
},
|
||||
(apiResponse) {
|
||||
isUpdateHealthSummaryLoading = false;
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
notifyListeners();
|
||||
if (onError != null) {
|
||||
onError(apiResponse.errorMessage ?? "Unknown error");
|
||||
}
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
// Update the local state on success
|
||||
isHealthSummaryEnabled = rSummaryReport;
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue