Merge branch 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New into dev_sultan
commit
34e8386ca1
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
@ -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,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,6 @@
|
||||
<svg width="67" height="74" viewBox="0 0 67 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M66.6078 32.2697C66.1744 30.4738 65.1769 29.2328 63.7991 28.7779C63.3117 28.6169 62.7964 28.5751 62.2712 28.6277C63.2293 24.3157 64.7466 19.7654 64.5501 15.5873C64.1645 7.35177 58.7442 0.579242 41.8415 0.0148163C31.0287 -0.346213 20.1447 5.91597 20.6036 16.0287C20.8633 21.7671 21.9015 25.0884 22.6027 28.1906L22.5955 28.6872C21.9433 28.5588 21.3005 28.5802 20.7048 28.7779C19.3245 29.2328 18.327 30.4738 17.8953 32.2697C17.4945 33.929 17.6233 35.8995 18.2581 37.8187C19.2879 40.9412 21.4426 43.2444 23.6866 43.6967C24.8086 48.1266 26.7007 52.0335 29.1423 55.1157L28.172 72.4976C28.1488 72.947 28.4967 73.3275 28.9447 73.3489C28.9581 73.3504 28.9715 73.3518 28.9853 73.3518C29.4158 73.3518 29.7757 73.0131 29.7974 72.5777L30.6851 56.8736C33.9673 60.2504 37.9975 62.271 42.3673 62.271C46.7608 62.271 50.8119 60.229 54.1046 56.8167L54.9949 72.5777C55.0166 73.0131 55.3771 73.3518 55.8074 73.3518C55.8211 73.3518 55.8345 73.3504 55.848 73.3489C56.296 73.3275 56.6436 72.947 56.6204 72.4976L55.646 55.0476C58.0739 51.9631 59.9511 48.0599 61.0666 43.6372C63.217 43.0688 65.2513 40.8234 66.2445 37.8187C66.8779 35.8995 67.0069 33.9304 66.6078 32.2697ZM19.8023 37.3084C19.2636 35.6723 19.1472 34.0197 19.4762 32.6514C19.7725 31.421 20.3896 30.5942 21.2152 30.3207C21.6323 30.184 22.0894 30.1988 22.5741 30.3493L22.5226 34.373C22.5226 36.9688 22.7961 39.47 23.2684 41.8561C21.862 41.1861 20.5169 39.474 19.8023 37.3084ZM42.3673 60.6429C32.3223 60.6429 24.1495 48.8586 24.1495 34.3838L24.2782 24.4253C24.2957 23.0704 23.6463 18.5731 28.6563 18.5731C35.6019 18.5731 35.2 23.0896 41.772 23.0896C48.4944 23.0896 49.0054 18.4254 55.848 18.4254C61.0748 18.4254 60.4495 23.4953 60.4698 25.083L60.5874 34.373C60.5874 48.8586 52.4154 60.6429 42.3673 60.6429ZM64.6994 37.3084C64.028 39.3398 62.8019 40.9779 61.4919 41.7277C61.9508 39.3764 62.2157 36.9159 62.2157 34.3635L62.1642 30.287C62.5604 30.2003 62.9383 30.2071 63.2873 30.3221C64.1118 30.5942 64.7292 31.421 65.0258 32.6514C65.3557 34.0197 65.2381 35.6723 64.6994 37.3084Z" fill="#2B353E"/>
|
||||
<path d="M14.4426 56.2374C14.4426 59.3856 11.8899 61.9383 8.74195 61.9383C5.59398 61.9383 3.04248 59.3856 3.04248 56.2374C3.04248 53.0909 5.59398 50.5382 8.74195 50.5382C11.8899 50.5382 14.4426 53.0909 14.4426 56.2374Z" fill="#EC1B2A"/>
|
||||
<path d="M8.74187 64.9804C3.92094 64.9804 0 61.058 0 56.2374C0 53.2991 1.50647 50.5382 3.9587 48.9287V19.423C3.9587 16.7865 6.10398 14.6387 8.74187 14.6387C11.3809 14.6387 13.5276 16.7865 13.5276 19.423V48.9302C15.9784 50.5382 17.4849 53.2991 17.4849 56.2374C17.4849 61.058 13.5617 64.9804 8.74187 64.9804ZM8.74187 16.1909C6.95934 16.1909 5.51009 17.6405 5.51009 19.423V49.807L5.1256 50.0307C2.92082 51.3189 1.55138 53.6967 1.55138 56.2374C1.55138 60.2029 4.77773 63.4293 8.74187 63.4293C12.706 63.4293 15.9326 60.2029 15.9326 56.2374C15.9326 53.6982 14.5644 51.3189 12.361 50.0318L11.9751 49.807V19.423C11.9751 17.6405 10.5258 16.1909 8.74187 16.1909Z" fill="#2B353E"/>
|
||||
<path d="M10.4569 51.7886H7.02686V25.6216H10.4569V51.7886Z" fill="#EC1B2A"/>
|
||||
</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,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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,489 @@
|
||||
class GetInvoiceDetailsResponseModel {
|
||||
int? projectID;
|
||||
int? doctorID;
|
||||
num? grandTotal;
|
||||
num? quantity;
|
||||
num? total;
|
||||
num? discount;
|
||||
num? subTotal;
|
||||
int? invoiceNo;
|
||||
String? createdOn;
|
||||
String? procedureID;
|
||||
String? procedureName;
|
||||
String? procedureNameN;
|
||||
num? procedurePrice;
|
||||
num? patientShare;
|
||||
num? companyShare;
|
||||
num? totalPatientShare;
|
||||
num? totalCompanyShare;
|
||||
num? totalShare;
|
||||
num? discountAmount;
|
||||
num? vATPercentage;
|
||||
num? patientVATAmount;
|
||||
num? companyVATAmount;
|
||||
num? totalVATAmount;
|
||||
num? price;
|
||||
int? patientID;
|
||||
String? patientIdentificationNo;
|
||||
String? patientName;
|
||||
String? patientNameN;
|
||||
String? nationalityID;
|
||||
String? doctorName;
|
||||
String? doctorNameN;
|
||||
int? clinicID;
|
||||
String? clinicDescription;
|
||||
String? clinicDescriptionN;
|
||||
String? appointmentDate;
|
||||
int? appointmentNo;
|
||||
String? insuranceID;
|
||||
int? companyID;
|
||||
String? companyName;
|
||||
String? companyNameN;
|
||||
String? companyAddress;
|
||||
String? companyAddressN;
|
||||
String? companyGroupAddress;
|
||||
String? groupName;
|
||||
String? groupNameN;
|
||||
String? patientAddress;
|
||||
String? vATNo;
|
||||
String? paymentDate;
|
||||
String? projectName;
|
||||
num? totalDiscount;
|
||||
num? totalPatientShareWithQuantity;
|
||||
String? legalName;
|
||||
String? legalNameN;
|
||||
num? advanceAdjustment;
|
||||
String? patientCityName;
|
||||
String? patientCityNameN;
|
||||
String? doctorImageURL;
|
||||
List<ListConsultation>? listConsultation;
|
||||
|
||||
GetInvoiceDetailsResponseModel(
|
||||
{this.projectID,
|
||||
this.doctorID,
|
||||
this.grandTotal,
|
||||
this.quantity,
|
||||
this.total,
|
||||
this.discount,
|
||||
this.subTotal,
|
||||
this.invoiceNo,
|
||||
this.createdOn,
|
||||
this.procedureID,
|
||||
this.procedureName,
|
||||
this.procedureNameN,
|
||||
this.procedurePrice,
|
||||
this.patientShare,
|
||||
this.companyShare,
|
||||
this.totalPatientShare,
|
||||
this.totalCompanyShare,
|
||||
this.totalShare,
|
||||
this.discountAmount,
|
||||
this.vATPercentage,
|
||||
this.patientVATAmount,
|
||||
this.companyVATAmount,
|
||||
this.totalVATAmount,
|
||||
this.price,
|
||||
this.patientID,
|
||||
this.patientIdentificationNo,
|
||||
this.patientName,
|
||||
this.patientNameN,
|
||||
this.nationalityID,
|
||||
this.doctorName,
|
||||
this.doctorNameN,
|
||||
this.clinicID,
|
||||
this.clinicDescription,
|
||||
this.clinicDescriptionN,
|
||||
this.appointmentDate,
|
||||
this.appointmentNo,
|
||||
this.insuranceID,
|
||||
this.companyID,
|
||||
this.companyName,
|
||||
this.companyNameN,
|
||||
this.companyAddress,
|
||||
this.companyAddressN,
|
||||
this.companyGroupAddress,
|
||||
this.groupName,
|
||||
this.groupNameN,
|
||||
this.patientAddress,
|
||||
this.vATNo,
|
||||
this.paymentDate,
|
||||
this.projectName,
|
||||
this.totalDiscount,
|
||||
this.totalPatientShareWithQuantity,
|
||||
this.legalName,
|
||||
this.legalNameN,
|
||||
this.advanceAdjustment,
|
||||
this.patientCityName,
|
||||
this.patientCityNameN,
|
||||
this.doctorImageURL,
|
||||
this.listConsultation});
|
||||
|
||||
GetInvoiceDetailsResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
projectID = json['ProjectID'];
|
||||
doctorID = json['DoctorID'];
|
||||
grandTotal = json['GrandTotal'];
|
||||
quantity = json['Quantity'];
|
||||
total = json['Total'];
|
||||
discount = json['Discount'];
|
||||
subTotal = json['SubTotal'];
|
||||
invoiceNo = json['InvoiceNo'];
|
||||
createdOn = json['CreatedOn'];
|
||||
procedureID = json['ProcedureID'];
|
||||
procedureName = json['ProcedureName'];
|
||||
procedureNameN = json['ProcedureNameN'];
|
||||
procedurePrice = json['ProcedurePrice'];
|
||||
patientShare = json['PatientShare'];
|
||||
companyShare = json['CompanyShare'];
|
||||
totalPatientShare = json['TotalPatientShare'];
|
||||
totalCompanyShare = json['TotalCompanyShare'];
|
||||
totalShare = json['TotalShare'];
|
||||
discountAmount = json['DiscountAmount'];
|
||||
vATPercentage = json['VATPercentage'];
|
||||
patientVATAmount = json['PatientVATAmount'];
|
||||
companyVATAmount = json['CompanyVATAmount'];
|
||||
totalVATAmount = json['TotalVATAmount'];
|
||||
price = json['Price'];
|
||||
patientID = json['PatientID'];
|
||||
patientIdentificationNo = json['PatientIdentificationNo'];
|
||||
patientName = json['PatientName'];
|
||||
patientNameN = json['PatientNameN'];
|
||||
nationalityID = json['NationalityID'];
|
||||
doctorName = json['DoctorName'];
|
||||
doctorNameN = json['DoctorNameN'];
|
||||
clinicID = json['ClinicID'];
|
||||
clinicDescription = json['ClinicDescription'];
|
||||
clinicDescriptionN = json['ClinicDescriptionN'];
|
||||
appointmentDate = json['AppointmentDate'];
|
||||
appointmentNo = json['AppointmentNo'];
|
||||
insuranceID = json['InsuranceID'];
|
||||
companyID = json['CompanyID'];
|
||||
companyName = json['CompanyName'];
|
||||
companyNameN = json['CompanyNameN'];
|
||||
companyAddress = json['CompanyAddress'];
|
||||
companyAddressN = json['CompanyAddressN'];
|
||||
companyGroupAddress = json['CompanyGroupAddress'];
|
||||
groupName = json['GroupName'];
|
||||
groupNameN = json['GroupNameN'];
|
||||
patientAddress = json['PatientAddress'];
|
||||
vATNo = json['VATNo'];
|
||||
paymentDate = json['PaymentDate'];
|
||||
projectName = json['ProjectName'];
|
||||
totalDiscount = json['TotalDiscount'];
|
||||
totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
|
||||
legalName = json['LegalName'];
|
||||
legalNameN = json['LegalNameN'];
|
||||
advanceAdjustment = json['AdvanceAdjustment'];
|
||||
patientCityName = json['PatientCityName'];
|
||||
patientCityNameN = json['PatientCityNameN'];
|
||||
doctorImageURL = json['DoctorImageURL'];
|
||||
if (json['listConsultation'] != null) {
|
||||
listConsultation = <ListConsultation>[];
|
||||
json['listConsultation'].forEach((v) {
|
||||
listConsultation!.add(new ListConsultation.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['ProjectID'] = this.projectID;
|
||||
data['DoctorID'] = this.doctorID;
|
||||
data['GrandTotal'] = this.grandTotal;
|
||||
data['Quantity'] = this.quantity;
|
||||
data['Total'] = this.total;
|
||||
data['Discount'] = this.discount;
|
||||
data['SubTotal'] = this.subTotal;
|
||||
data['InvoiceNo'] = this.invoiceNo;
|
||||
data['CreatedOn'] = this.createdOn;
|
||||
data['ProcedureID'] = this.procedureID;
|
||||
data['ProcedureName'] = this.procedureName;
|
||||
data['ProcedureNameN'] = this.procedureNameN;
|
||||
data['ProcedurePrice'] = this.procedurePrice;
|
||||
data['PatientShare'] = this.patientShare;
|
||||
data['CompanyShare'] = this.companyShare;
|
||||
data['TotalPatientShare'] = this.totalPatientShare;
|
||||
data['TotalCompanyShare'] = this.totalCompanyShare;
|
||||
data['TotalShare'] = this.totalShare;
|
||||
data['DiscountAmount'] = this.discountAmount;
|
||||
data['VATPercentage'] = this.vATPercentage;
|
||||
data['PatientVATAmount'] = this.patientVATAmount;
|
||||
data['CompanyVATAmount'] = this.companyVATAmount;
|
||||
data['TotalVATAmount'] = this.totalVATAmount;
|
||||
data['Price'] = this.price;
|
||||
data['PatientID'] = this.patientID;
|
||||
data['PatientIdentificationNo'] = this.patientIdentificationNo;
|
||||
data['PatientName'] = this.patientName;
|
||||
data['PatientNameN'] = this.patientNameN;
|
||||
data['NationalityID'] = this.nationalityID;
|
||||
data['DoctorName'] = this.doctorName;
|
||||
data['DoctorNameN'] = this.doctorNameN;
|
||||
data['ClinicID'] = this.clinicID;
|
||||
data['ClinicDescription'] = this.clinicDescription;
|
||||
data['ClinicDescriptionN'] = this.clinicDescriptionN;
|
||||
data['AppointmentDate'] = this.appointmentDate;
|
||||
data['AppointmentNo'] = this.appointmentNo;
|
||||
data['InsuranceID'] = this.insuranceID;
|
||||
data['CompanyID'] = this.companyID;
|
||||
data['CompanyName'] = this.companyName;
|
||||
data['CompanyNameN'] = this.companyNameN;
|
||||
data['CompanyAddress'] = this.companyAddress;
|
||||
data['CompanyAddressN'] = this.companyAddressN;
|
||||
data['CompanyGroupAddress'] = this.companyGroupAddress;
|
||||
data['GroupName'] = this.groupName;
|
||||
data['GroupNameN'] = this.groupNameN;
|
||||
data['PatientAddress'] = this.patientAddress;
|
||||
data['VATNo'] = this.vATNo;
|
||||
data['PaymentDate'] = this.paymentDate;
|
||||
data['ProjectName'] = this.projectName;
|
||||
data['TotalDiscount'] = this.totalDiscount;
|
||||
data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
|
||||
data['LegalName'] = this.legalName;
|
||||
data['LegalNameN'] = this.legalNameN;
|
||||
data['AdvanceAdjustment'] = this.advanceAdjustment;
|
||||
data['PatientCityName'] = this.patientCityName;
|
||||
data['PatientCityNameN'] = this.patientCityNameN;
|
||||
data['DoctorImageURL'] = this.doctorImageURL;
|
||||
if (this.listConsultation != null) {
|
||||
data['listConsultation'] =
|
||||
this.listConsultation!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class ListConsultation {
|
||||
int? projectID;
|
||||
int? doctorID;
|
||||
num? grandTotal;
|
||||
int? quantity;
|
||||
num? total;
|
||||
num? discount;
|
||||
num? subTotal;
|
||||
int? invoiceNo;
|
||||
String? createdOn;
|
||||
String? procedureID;
|
||||
String? procedureName;
|
||||
String? procedureNameN;
|
||||
num? procedurePrice;
|
||||
num? patientShare;
|
||||
num? companyShare;
|
||||
num? totalPatientShare;
|
||||
num? totalCompanyShare;
|
||||
num? totalShare;
|
||||
num? discountAmount;
|
||||
num? vATPercentage;
|
||||
num? patientVATAmount;
|
||||
num? companyVATAmount;
|
||||
num? totalVATAmount;
|
||||
num? price;
|
||||
int? patientID;
|
||||
int? patientIdentificationNo;
|
||||
String? patientName;
|
||||
String? patientNameN;
|
||||
String? nationalityID;
|
||||
String? doctorName;
|
||||
String? doctorNameN;
|
||||
int? clinicID;
|
||||
String? clinicDescription;
|
||||
String? clinicDescriptionN;
|
||||
String? appointmentDate;
|
||||
dynamic appointmentNo;
|
||||
dynamic insuranceID;
|
||||
dynamic companyID;
|
||||
String? companyName;
|
||||
String? companyNameN;
|
||||
String? companyAddress;
|
||||
String? companyAddressN;
|
||||
String? companyGroupAddress;
|
||||
String? groupName;
|
||||
String? groupNameN;
|
||||
String? patientAddress;
|
||||
String? vATNo;
|
||||
String? paymentDate;
|
||||
String? projectName;
|
||||
num? totalDiscount;
|
||||
num? totalPatientShareWithQuantity;
|
||||
String? legalName;
|
||||
String? legalNameN;
|
||||
num? advanceAdjustment;
|
||||
String? patientCityName;
|
||||
String? patientCityNameN;
|
||||
|
||||
ListConsultation(
|
||||
{this.projectID,
|
||||
this.doctorID,
|
||||
this.grandTotal,
|
||||
this.quantity,
|
||||
this.total,
|
||||
this.discount,
|
||||
this.subTotal,
|
||||
this.invoiceNo,
|
||||
this.createdOn,
|
||||
this.procedureID,
|
||||
this.procedureName,
|
||||
this.procedureNameN,
|
||||
this.procedurePrice,
|
||||
this.patientShare,
|
||||
this.companyShare,
|
||||
this.totalPatientShare,
|
||||
this.totalCompanyShare,
|
||||
this.totalShare,
|
||||
this.discountAmount,
|
||||
this.vATPercentage,
|
||||
this.patientVATAmount,
|
||||
this.companyVATAmount,
|
||||
this.totalVATAmount,
|
||||
this.price,
|
||||
this.patientID,
|
||||
this.patientIdentificationNo,
|
||||
this.patientName,
|
||||
this.patientNameN,
|
||||
this.nationalityID,
|
||||
this.doctorName,
|
||||
this.doctorNameN,
|
||||
this.clinicID,
|
||||
this.clinicDescription,
|
||||
this.clinicDescriptionN,
|
||||
this.appointmentDate,
|
||||
this.appointmentNo,
|
||||
this.insuranceID,
|
||||
this.companyID,
|
||||
this.companyName,
|
||||
this.companyNameN,
|
||||
this.companyAddress,
|
||||
this.companyAddressN,
|
||||
this.companyGroupAddress,
|
||||
this.groupName,
|
||||
this.groupNameN,
|
||||
this.patientAddress,
|
||||
this.vATNo,
|
||||
this.paymentDate,
|
||||
this.projectName,
|
||||
this.totalDiscount,
|
||||
this.totalPatientShareWithQuantity,
|
||||
this.legalName,
|
||||
this.legalNameN,
|
||||
this.advanceAdjustment,
|
||||
this.patientCityName,
|
||||
this.patientCityNameN});
|
||||
|
||||
ListConsultation.fromJson(Map<String, dynamic> json) {
|
||||
projectID = json['ProjectID'];
|
||||
doctorID = json['DoctorID'];
|
||||
grandTotal = json['GrandTotal'];
|
||||
quantity = json['Quantity'];
|
||||
total = json['Total'];
|
||||
discount = json['Discount'];
|
||||
subTotal = json['SubTotal'];
|
||||
invoiceNo = json['InvoiceNo'];
|
||||
createdOn = json['CreatedOn'];
|
||||
procedureID = json['ProcedureID'];
|
||||
procedureName = json['ProcedureName'];
|
||||
procedureNameN = json['ProcedureNameN'];
|
||||
procedurePrice = json['ProcedurePrice'];
|
||||
patientShare = json['PatientShare'];
|
||||
companyShare = json['CompanyShare'];
|
||||
totalPatientShare = json['TotalPatientShare'];
|
||||
totalCompanyShare = json['TotalCompanyShare'];
|
||||
totalShare = json['TotalShare'];
|
||||
discountAmount = json['DiscountAmount'];
|
||||
vATPercentage = json['VATPercentage'];
|
||||
patientVATAmount = json['PatientVATAmount'];
|
||||
companyVATAmount = json['CompanyVATAmount'];
|
||||
totalVATAmount = json['TotalVATAmount'];
|
||||
price = json['Price'];
|
||||
patientID = json['PatientID'];
|
||||
patientIdentificationNo = json['PatientIdentificationNo'];
|
||||
patientName = json['PatientName'];
|
||||
patientNameN = json['PatientNameN'];
|
||||
nationalityID = json['NationalityID'];
|
||||
doctorName = json['DoctorName'];
|
||||
doctorNameN = json['DoctorNameN'];
|
||||
clinicID = json['ClinicID'];
|
||||
clinicDescription = json['ClinicDescription'];
|
||||
clinicDescriptionN = json['ClinicDescriptionN'];
|
||||
appointmentDate = json['AppointmentDate'];
|
||||
appointmentNo = json['AppointmentNo'];
|
||||
insuranceID = json['InsuranceID'];
|
||||
companyID = json['CompanyID'];
|
||||
companyName = json['CompanyName'];
|
||||
companyNameN = json['CompanyNameN'];
|
||||
companyAddress = json['CompanyAddress'];
|
||||
companyAddressN = json['CompanyAddressN'];
|
||||
companyGroupAddress = json['CompanyGroupAddress'];
|
||||
groupName = json['GroupName'];
|
||||
groupNameN = json['GroupNameN'];
|
||||
patientAddress = json['PatientAddress'];
|
||||
vATNo = json['VATNo'];
|
||||
paymentDate = json['PaymentDate'];
|
||||
projectName = json['ProjectName'];
|
||||
totalDiscount = json['TotalDiscount'];
|
||||
totalPatientShareWithQuantity = json['TotalPatientShareWithQuantity'];
|
||||
legalName = json['LegalName'];
|
||||
legalNameN = json['LegalNameN'];
|
||||
advanceAdjustment = json['AdvanceAdjustment'];
|
||||
patientCityName = json['PatientCityName'];
|
||||
patientCityNameN = json['PatientCityNameN'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = new Map<String, dynamic>();
|
||||
data['ProjectID'] = this.projectID;
|
||||
data['DoctorID'] = this.doctorID;
|
||||
data['GrandTotal'] = this.grandTotal;
|
||||
data['Quantity'] = this.quantity;
|
||||
data['Total'] = this.total;
|
||||
data['Discount'] = this.discount;
|
||||
data['SubTotal'] = this.subTotal;
|
||||
data['InvoiceNo'] = this.invoiceNo;
|
||||
data['CreatedOn'] = this.createdOn;
|
||||
data['ProcedureID'] = this.procedureID;
|
||||
data['ProcedureName'] = this.procedureName;
|
||||
data['ProcedureNameN'] = this.procedureNameN;
|
||||
data['ProcedurePrice'] = this.procedurePrice;
|
||||
data['PatientShare'] = this.patientShare;
|
||||
data['CompanyShare'] = this.companyShare;
|
||||
data['TotalPatientShare'] = this.totalPatientShare;
|
||||
data['TotalCompanyShare'] = this.totalCompanyShare;
|
||||
data['TotalShare'] = this.totalShare;
|
||||
data['DiscountAmount'] = this.discountAmount;
|
||||
data['VATPercentage'] = this.vATPercentage;
|
||||
data['PatientVATAmount'] = this.patientVATAmount;
|
||||
data['CompanyVATAmount'] = this.companyVATAmount;
|
||||
data['TotalVATAmount'] = this.totalVATAmount;
|
||||
data['Price'] = this.price;
|
||||
data['PatientID'] = this.patientID;
|
||||
data['PatientIdentificationNo'] = this.patientIdentificationNo;
|
||||
data['PatientName'] = this.patientName;
|
||||
data['PatientNameN'] = this.patientNameN;
|
||||
data['NationalityID'] = this.nationalityID;
|
||||
data['DoctorName'] = this.doctorName;
|
||||
data['DoctorNameN'] = this.doctorNameN;
|
||||
data['ClinicID'] = this.clinicID;
|
||||
data['ClinicDescription'] = this.clinicDescription;
|
||||
data['ClinicDescriptionN'] = this.clinicDescriptionN;
|
||||
data['AppointmentDate'] = this.appointmentDate;
|
||||
data['AppointmentNo'] = this.appointmentNo;
|
||||
data['InsuranceID'] = this.insuranceID;
|
||||
data['CompanyID'] = this.companyID;
|
||||
data['CompanyName'] = this.companyName;
|
||||
data['CompanyNameN'] = this.companyNameN;
|
||||
data['CompanyAddress'] = this.companyAddress;
|
||||
data['CompanyAddressN'] = this.companyAddressN;
|
||||
data['CompanyGroupAddress'] = this.companyGroupAddress;
|
||||
data['GroupName'] = this.groupName;
|
||||
data['GroupNameN'] = this.groupNameN;
|
||||
data['PatientAddress'] = this.patientAddress;
|
||||
data['VATNo'] = this.vATNo;
|
||||
data['PaymentDate'] = this.paymentDate;
|
||||
data['ProjectName'] = this.projectName;
|
||||
data['TotalDiscount'] = this.totalDiscount;
|
||||
data['TotalPatientShareWithQuantity'] = this.totalPatientShareWithQuantity;
|
||||
data['LegalName'] = this.legalName;
|
||||
data['LegalNameN'] = this.legalNameN;
|
||||
data['AdvanceAdjustment'] = this.advanceAdjustment;
|
||||
data['PatientCityName'] = this.patientCityName;
|
||||
data['PatientCityNameN'] = this.patientCityNameN;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
class GetInvoicesListResponseModel {
|
||||
String? setupId;
|
||||
int? projectID;
|
||||
int? patientID;
|
||||
int? appointmentNo;
|
||||
String? appointmentDate;
|
||||
String? appointmentDateN;
|
||||
int? clinicID;
|
||||
int? doctorID;
|
||||
int? invoiceNo;
|
||||
int? status;
|
||||
String? arrivedOn;
|
||||
String? doctorName;
|
||||
String? doctorNameN;
|
||||
String? clinicName;
|
||||
double? decimalDoctorRate;
|
||||
String? doctorImageURL;
|
||||
int? doctorRate;
|
||||
int? patientNumber;
|
||||
String? projectName;
|
||||
|
||||
GetInvoicesListResponseModel(
|
||||
{this.setupId,
|
||||
this.projectID,
|
||||
this.patientID,
|
||||
this.appointmentNo,
|
||||
this.appointmentDate,
|
||||
this.appointmentDateN,
|
||||
this.clinicID,
|
||||
this.doctorID,
|
||||
this.invoiceNo,
|
||||
this.status,
|
||||
this.arrivedOn,
|
||||
this.doctorName,
|
||||
this.doctorNameN,
|
||||
this.clinicName,
|
||||
this.decimalDoctorRate,
|
||||
this.doctorImageURL,
|
||||
this.doctorRate,
|
||||
this.patientNumber,
|
||||
this.projectName});
|
||||
|
||||
GetInvoicesListResponseModel.fromJson(Map<String, dynamic> json) {
|
||||
setupId = json['SetupId'];
|
||||
projectID = json['ProjectID'];
|
||||
patientID = json['PatientID'];
|
||||
appointmentNo = json['AppointmentNo'];
|
||||
appointmentDate = json['AppointmentDate'];
|
||||
appointmentDateN = json['AppointmentDateN'];
|
||||
clinicID = json['ClinicID'];
|
||||
doctorID = json['DoctorID'];
|
||||
invoiceNo = json['InvoiceNo'];
|
||||
status = json['Status'];
|
||||
arrivedOn = json['ArrivedOn'];
|
||||
doctorName = json['DoctorName'];
|
||||
doctorNameN = json['DoctorNameN'];
|
||||
clinicName = json['ClinicName'];
|
||||
decimalDoctorRate = json['DecimalDoctorRate'];
|
||||
doctorImageURL = json['DoctorImageURL'];
|
||||
doctorRate = json['DoctorRate'];
|
||||
patientNumber = json['PatientNumber'];
|
||||
projectName = json['ProjectName'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['SetupId'] = this.setupId;
|
||||
data['ProjectID'] = this.projectID;
|
||||
data['PatientID'] = this.patientID;
|
||||
data['AppointmentNo'] = this.appointmentNo;
|
||||
data['AppointmentDate'] = this.appointmentDate;
|
||||
data['AppointmentDateN'] = this.appointmentDateN;
|
||||
data['ClinicID'] = this.clinicID;
|
||||
data['DoctorID'] = this.doctorID;
|
||||
data['InvoiceNo'] = this.invoiceNo;
|
||||
data['Status'] = this.status;
|
||||
data['ArrivedOn'] = this.arrivedOn;
|
||||
data['DoctorName'] = this.doctorName;
|
||||
data['DoctorNameN'] = this.doctorNameN;
|
||||
data['ClinicName'] = this.clinicName;
|
||||
data['DecimalDoctorRate'] = this.decimalDoctorRate;
|
||||
data['DoctorImageURL'] = this.doctorImageURL;
|
||||
data['DoctorRate'] = this.doctorRate;
|
||||
data['PatientNumber'] = this.patientNumber;
|
||||
data['ProjectName'] = this.projectName;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:hmg_patient_app_new/core/api/api_client.dart';
|
||||
import 'package:hmg_patient_app_new/core/api_consts.dart';
|
||||
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
|
||||
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/services/logger_service.dart';
|
||||
|
||||
abstract class MyInvoicesRepo {
|
||||
Future<Either<Failure, GenericApiModel<List<GetInvoicesListResponseModel>>>> getAllInvoicesList();
|
||||
|
||||
Future<Either<Failure, GenericApiModel<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID});
|
||||
|
||||
Future<Either<Failure, GenericApiModel<dynamic>>> sendInvoiceEmail({required num appointmentNo, required int projectID});
|
||||
}
|
||||
|
||||
class MyInvoicesRepoImp implements MyInvoicesRepo {
|
||||
final ApiClient apiClient;
|
||||
final LoggerService loggerService;
|
||||
|
||||
MyInvoicesRepoImp({required this.loggerService, required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GenericApiModel<List<GetInvoicesListResponseModel>>>> getAllInvoicesList() async {
|
||||
Map<String, dynamic> mapDevice = {};
|
||||
|
||||
try {
|
||||
GenericApiModel<List<GetInvoicesListResponseModel>>? apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
try {
|
||||
final list = response['List_DentalAppointments'];
|
||||
|
||||
final invoicesList = list.map((item) => GetInvoicesListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetInvoicesListResponseModel>();
|
||||
|
||||
apiResponse = GenericApiModel<List<GetInvoicesListResponseModel>>(
|
||||
messageStatus: messageStatus,
|
||||
statusCode: statusCode,
|
||||
errorMessage: null,
|
||||
data: invoicesList,
|
||||
);
|
||||
} catch (e) {
|
||||
failure = DataParsingFailure(e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
|
||||
return Right(apiResponse!);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GenericApiModel<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async {
|
||||
Map<String, dynamic> mapDevice = {
|
||||
"AppointmentNo": appointmentNo,
|
||||
"InvoiceNo": invoiceNo,
|
||||
"IsRegistered": true,
|
||||
"ProjectID": projectID,
|
||||
};
|
||||
|
||||
try {
|
||||
GenericApiModel<GetInvoiceDetailsResponseModel>? apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
GET_DENTAL_APPOINTMENT_INVOICE,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
try {
|
||||
final list = response['List_eInvoiceForDental'];
|
||||
final invoicesList = GetInvoiceDetailsResponseModel.fromJson(list[0]);
|
||||
|
||||
apiResponse = GenericApiModel<GetInvoiceDetailsResponseModel>(
|
||||
messageStatus: messageStatus,
|
||||
statusCode: statusCode,
|
||||
errorMessage: null,
|
||||
data: invoicesList,
|
||||
);
|
||||
} catch (e) {
|
||||
failure = DataParsingFailure(e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
|
||||
return Right(apiResponse!);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<Failure, GenericApiModel>> sendInvoiceEmail({required num appointmentNo, required int projectID}) async {
|
||||
Map<String, dynamic> mapDevice = {
|
||||
"AppointmentNo": appointmentNo,
|
||||
"IsRegistered": true,
|
||||
"ProjectID": projectID,
|
||||
};
|
||||
|
||||
try {
|
||||
GenericApiModel<GetInvoiceDetailsResponseModel>? apiResponse;
|
||||
Failure? failure;
|
||||
await apiClient.post(
|
||||
SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL,
|
||||
body: mapDevice,
|
||||
onFailure: (error, statusCode, {messageStatus, failureType}) {
|
||||
failure = failureType;
|
||||
},
|
||||
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
|
||||
try {
|
||||
apiResponse = GenericApiModel(
|
||||
messageStatus: messageStatus,
|
||||
statusCode: statusCode,
|
||||
errorMessage: null,
|
||||
data: response,
|
||||
);
|
||||
} catch (e) {
|
||||
failure = DataParsingFailure(e.toString());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (failure != null) return Left(failure!);
|
||||
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
|
||||
return Right(apiResponse!);
|
||||
} catch (e) {
|
||||
return Left(UnknownFailure(e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
|
||||
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
|
||||
import 'package:hmg_patient_app_new/services/navigation_service.dart';
|
||||
|
||||
class MyInvoicesViewModel extends ChangeNotifier {
|
||||
bool isInvoicesListLoading = false;
|
||||
bool isInvoiceDetailsLoading = false;
|
||||
|
||||
MyInvoicesRepo myInvoicesRepo;
|
||||
ErrorHandlerService errorHandlerService;
|
||||
NavigationService navServices;
|
||||
|
||||
List<GetInvoicesListResponseModel> allInvoicesList = [];
|
||||
late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel;
|
||||
|
||||
MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices});
|
||||
|
||||
setInvoicesListLoading() {
|
||||
isInvoicesListLoading = true;
|
||||
allInvoicesList.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
setInvoiceDetailLoading() {
|
||||
isInvoiceDetailsLoading = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
|
||||
final result = await myInvoicesRepo.getAllInvoicesList();
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
isInvoicesListLoading = false;
|
||||
notifyListeners();
|
||||
},
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
allInvoicesList = apiResponse.data!;
|
||||
isInvoicesListLoading = false;
|
||||
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
|
||||
final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
isInvoiceDetailsLoading = false;
|
||||
notifyListeners();
|
||||
},
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
invoiceDetailsResponseModel = apiResponse.data!;
|
||||
isInvoiceDetailsLoading = false;
|
||||
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendInvoiceEmail({required num appointmentNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
|
||||
final result = await myInvoicesRepo.sendInvoiceEmail(appointmentNo: appointmentNo, projectID: projectID);
|
||||
|
||||
result.fold(
|
||||
(failure) async {
|
||||
notifyListeners();
|
||||
},
|
||||
(apiResponse) {
|
||||
if (apiResponse.messageStatus == 2) {
|
||||
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
|
||||
} else if (apiResponse.messageStatus == 1) {
|
||||
notifyListeners();
|
||||
if (onSuccess != null) {
|
||||
onSuccess(apiResponse);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,272 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
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/date_util.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/my_invoices/models/get_invoice_details_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MyInvoicesDetailsPage extends StatefulWidget {
|
||||
GetInvoiceDetailsResponseModel getInvoiceDetailsResponseModel;
|
||||
|
||||
MyInvoicesDetailsPage({super.key, required this.getInvoiceDetailsResponseModel});
|
||||
|
||||
@override
|
||||
State<MyInvoicesDetailsPage> createState() => _MyInvoicesDetailsPageState();
|
||||
}
|
||||
|
||||
class _MyInvoicesDetailsPageState extends State<MyInvoicesDetailsPage> {
|
||||
late MyInvoicesViewModel myInvoicesViewModel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.bgScaffoldColor,
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: CollapsingListView(
|
||||
title: "Invoice Details".needTranslation,
|
||||
sendEmail: () async {
|
||||
LoaderBottomSheet.showLoader(loadingText: "Sending email, Please wait...".needTranslation);
|
||||
await myInvoicesViewModel.sendInvoiceEmail(
|
||||
appointmentNo: widget.getInvoiceDetailsResponseModel.appointmentNo!,
|
||||
projectID: widget.getInvoiceDetailsResponseModel.projectID!,
|
||||
onSuccess: (val) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
showCommonBottomSheetWithoutHeight(
|
||||
context,
|
||||
child: Utils.getSuccessWidget(loadingText: "Email sent successfully.".needTranslation),
|
||||
callBackFunc: () {},
|
||||
isFullScreen: false,
|
||||
isCloseButtonVisible: true,
|
||||
);
|
||||
},
|
||||
onError: (err) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
showCommonBottomSheetWithoutHeight(
|
||||
context,
|
||||
child: Utils.getErrorWidget(loadingText: err),
|
||||
callBackFunc: () {},
|
||||
isFullScreen: false,
|
||||
isCloseButtonVisible: true,
|
||||
);
|
||||
});
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(24.h),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: true,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Image.network(
|
||||
widget.getInvoiceDetailsResponseModel.doctorImageURL!,
|
||||
width: 63.h,
|
||||
height: 63.h,
|
||||
fit: BoxFit.cover,
|
||||
).circle(100.r),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
(getIt<AppState>().isArabic() ? widget.getInvoiceDetailsResponseModel.doctorNameN! : widget.getInvoiceDetailsResponseModel.doctorName!).toText16(isBold: true),
|
||||
SizedBox(height: 8.h),
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 6.w,
|
||||
runSpacing: 6.h,
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
labelText: "${LocaleKeys.invoiceNo}: ${widget.getInvoiceDetailsResponseModel.invoiceNo!}",
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText: (widget.getInvoiceDetailsResponseModel.clinicDescription!.length > 15
|
||||
? '${widget.getInvoiceDetailsResponseModel.clinicDescription!.substring(0, 12)}...'
|
||||
: widget.getInvoiceDetailsResponseModel.clinicDescription!),
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText: widget.getInvoiceDetailsResponseModel.projectName!,
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
|
||||
icon: AppAssets.doctor_calendar_icon,
|
||||
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.getInvoiceDetailsResponseModel.appointmentDate), false),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: true,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
widget.getInvoiceDetailsResponseModel.listConsultation!.first.procedureName!.toText16(isBold: true),
|
||||
SizedBox(height: 16.h),
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 6.w,
|
||||
runSpacing: 6.h,
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
labelText: "${LocaleKeys.quantity.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.quantity!}",
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText: "${LocaleKeys.price.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.price!} ${LocaleKeys.sar.tr()}",
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText: "${LocaleKeys.total.tr()}: ${widget.getInvoiceDetailsResponseModel.listConsultation!.first.total!} ${LocaleKeys.sar.tr()}",
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: true,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
"Insurance Details".toText16(isBold: true),
|
||||
SizedBox(height: 16.h),
|
||||
widget.getInvoiceDetailsResponseModel.groupName!.toText14(isBold: true),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: widget.getInvoiceDetailsResponseModel.companyName!.toText14(isBold: true)),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12.h),
|
||||
Row(
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
labelText: "Insurance ID: ${widget.getInvoiceDetailsResponseModel.insuranceID ?? "-"}",
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 24.h,
|
||||
hasShadow: false,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24.h),
|
||||
"Total Balance".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
|
||||
SizedBox(height: 17.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"Amount before tax".needTranslation.toText14(isBold: true),
|
||||
Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalShare.toString().toText16(isBold: true), AppColors.blackColor, 13,
|
||||
isSaudiCurrency: true),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
widget.getInvoiceDetailsResponseModel.listConsultation!.first.totalVATAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
|
||||
isSaudiCurrency: true),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"Discount".needTranslation.toText14(isBold: true),
|
||||
Utils.getPaymentAmountWithSymbol(widget.getInvoiceDetailsResponseModel.listConsultation!.first.discountAmount!.toString().toText14(isBold: true, color: AppColors.primaryRedColor),
|
||||
AppColors.primaryRedColor, 13,
|
||||
isSaudiCurrency: true),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
"Paid".needTranslation.toText14(isBold: true),
|
||||
Utils.getPaymentAmountWithSymbol(
|
||||
widget.getInvoiceDetailsResponseModel.listConsultation!.first.grandTotal!.toString().toText14(isBold: true, color: AppColors.textColor), AppColors.textColor, 13,
|
||||
isSaudiCurrency: true),
|
||||
],
|
||||
).paddingSymmetrical(24.h, 0.h),
|
||||
SizedBox(height: 24.h),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
|
||||
import 'package: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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
|
||||
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart';
|
||||
import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MyInvoicesList extends StatefulWidget {
|
||||
const MyInvoicesList({super.key});
|
||||
|
||||
@override
|
||||
State<MyInvoicesList> createState() => _MyInvoicesListState();
|
||||
}
|
||||
|
||||
class _MyInvoicesListState extends State<MyInvoicesList> {
|
||||
late MyInvoicesViewModel myInvoicesViewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scheduleMicrotask(() {
|
||||
myInvoicesViewModel.setInvoicesListLoading();
|
||||
myInvoicesViewModel.getAllInvoicesList();
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
myInvoicesViewModel = Provider.of<MyInvoicesViewModel>(context, listen: false);
|
||||
return CollapsingListView(
|
||||
title: LocaleKeys.invoiceList.tr(context: context),
|
||||
child: SingleChildScrollView(
|
||||
child: Consumer<MyInvoicesViewModel>(builder: (context, myInvoicesVM, child) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24.h),
|
||||
ListView.builder(
|
||||
itemCount: myInvoicesVM.isInvoicesListLoading ? 4 : myInvoicesVM.allInvoicesList.length,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsetsGeometry.zero,
|
||||
itemBuilder: (context, index) {
|
||||
return myInvoicesVM.isInvoicesListLoading
|
||||
? LabResultItemView(
|
||||
onTap: () {},
|
||||
labOrder: null,
|
||||
index: index,
|
||||
isLoading: true,
|
||||
)
|
||||
: AnimationConfiguration.staggeredList(
|
||||
position: index,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: SlideAnimation(
|
||||
verticalOffset: 100.0,
|
||||
child: FadeInAnimation(
|
||||
child: AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
child: InvoiceListCard(
|
||||
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
|
||||
onTap: () async {
|
||||
myInvoicesVM.setInvoiceDetailLoading();
|
||||
LoaderBottomSheet.showLoader(loadingText: "Fetching invoice details, Please wait...".needTranslation);
|
||||
await myInvoicesVM.getInvoiceDetails(
|
||||
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
|
||||
invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
|
||||
projectID: myInvoicesVM.allInvoicesList[index].projectID!,
|
||||
onSuccess: (val) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
Navigator.of(context).push(
|
||||
CustomPageRoute(
|
||||
page: MyInvoicesDetailsPage(getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel),
|
||||
),
|
||||
);
|
||||
},
|
||||
onError: (err) {
|
||||
LoaderBottomSheet.hideLoader();
|
||||
showCommonBottomSheetWithoutHeight(
|
||||
context,
|
||||
child: Utils.getErrorWidget(loadingText: err),
|
||||
callBackFunc: () {},
|
||||
isFullScreen: false,
|
||||
isCloseButtonVisible: true,
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).paddingSymmetrical(24.w, 0.h),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
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/date_util.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/my_invoices/models/get_invoices_list_response_model.dart';
|
||||
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
|
||||
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
|
||||
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
|
||||
|
||||
class InvoiceListCard extends StatelessWidget {
|
||||
final GetInvoicesListResponseModel getInvoicesListResponseModel;
|
||||
Function? onTap;
|
||||
|
||||
InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
borderRadius: 20.h,
|
||||
hasShadow: true,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(14.h),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Wrap(
|
||||
alignment: WrapAlignment.start,
|
||||
direction: Axis.horizontal,
|
||||
spacing: 6.w,
|
||||
runSpacing: 6.h,
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
icon: AppAssets.walkin_appointment_icon,
|
||||
iconColor: AppColors.textColor,
|
||||
labelText: 'Walk In'.needTranslation,
|
||||
textColor: AppColors.textColor,
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText: 'OutPatient'.needTranslation,
|
||||
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
|
||||
textColor: AppColors.primaryRedColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Image.network(
|
||||
getInvoicesListResponseModel.doctorImageURL!,
|
||||
width: 63.h,
|
||||
height: 63.h,
|
||||
fit: BoxFit.cover,
|
||||
).circle(100.r),
|
||||
Transform.translate(
|
||||
offset: Offset(0.0, -20.h),
|
||||
child: Container(
|
||||
width: 40.w,
|
||||
height: 40.h,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.whiteColor,
|
||||
shape: BoxShape.circle, // Makes the container circular
|
||||
border: Border.all(
|
||||
color: AppColors.scaffoldBgColor, // Color of the border
|
||||
width: 1.5.w, // Width of the border
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h),
|
||||
SizedBox(height: 2.h),
|
||||
"${getInvoicesListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor),
|
||||
],
|
||||
),
|
||||
).circle(100),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(width: 16.w),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
(getIt<AppState>().isArabic() ? getInvoicesListResponseModel.doctorNameN! : getInvoicesListResponseModel.doctorName!).toText16(isBold: true),
|
||||
SizedBox(height: 8.h),
|
||||
Wrap(
|
||||
direction: Axis.horizontal,
|
||||
spacing: 6.w,
|
||||
runSpacing: 6.h,
|
||||
children: [
|
||||
AppCustomChipWidget(
|
||||
labelText: "${LocaleKeys.invoiceNo}: ${getInvoicesListResponseModel.invoiceNo!}",
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText:
|
||||
(getInvoicesListResponseModel.clinicName!.length > 15 ? '${getInvoicesListResponseModel.clinicName!.substring(0, 12)}...' : getInvoicesListResponseModel.clinicName!),
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelText: getInvoicesListResponseModel.projectName!,
|
||||
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
|
||||
),
|
||||
AppCustomChipWidget(
|
||||
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
|
||||
icon: AppAssets.doctor_calendar_icon,
|
||||
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(getInvoicesListResponseModel.appointmentDate), false),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16.h),
|
||||
CustomButton(
|
||||
text: "View invoice details".needTranslation,
|
||||
onPressed: () {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
}
|
||||
},
|
||||
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
|
||||
borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01),
|
||||
textColor: AppColors.primaryRedColor,
|
||||
fontSize: 14.f,
|
||||
fontWeight: FontWeight.w500,
|
||||
borderRadius: 12.r,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||
height: 40.h,
|
||||
iconSize: 14.h,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
).paddingOnly(bottom: 16.h);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue