From a7f53d3b3c3fc80d8ecc6117ff4b6e509edcea3a Mon Sep 17 00:00:00 2001 From: Aamir Muhammad Date: Wed, 2 Jul 2025 10:36:06 +0300 Subject: [PATCH] updated app to latest flutter sdk. --- README.md | 2 +- android/app/build.gradle | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 7 + android/app/src/main/AndroidManifest.xml | 1 - .../com/cloud/hmg_patient_app/Application.kt | 59 +++ .../FirebaseCloudMessagingPluginRegistrant.kt | 25 ++ .../com/cloud/hmg_patient_app/MainActivity.kt | 75 ++++ .../PenguinInPlatformBridge.kt | 53 +++ .../PermissionManager/PermissionHelper.kt | 28 ++ .../PermissionManager/PermissionManager.kt | 50 +++ .../PermissionResultReceiver.kt | 15 + .../CustomFlutterHmsMessageService.java | 56 +++ .../hmg_patient_app/geofence/GeoZoneModel.kt | 58 +++ .../hmg_patient_app/geofence/HMG_Geofence.kt | 291 +++++++++++++++ .../GeofenceBroadcastReceiver.kt | 71 ++++ ...GeofenceBroadcastReceiverWithJobService.kt | 16 + .../intent_receivers/GeofenceErrorMessages.kt | 43 +++ .../GeofenceTransitionsJobIntentService.kt | 97 +++++ .../GeofencingRebootBroadcastReceiver.kt | 23 ++ .../LocationProviderChangeReceiver.kt | 25 ++ .../ReregisterGeofenceJobService.kt | 24 ++ .../hmg_patient_app/hmgwifi/HMG_Guest.kt | 257 +++++++++++++ .../hmg_patient_app/hmgwifi/HMG_Internet.kt | 120 ++++++ .../com/cloud/hmg_patient_app/hmgwifi/WPA.kt | 105 ++++++ .../hmg_patient_app/hmgwifi/WpaEnterprise.kt | 166 +++++++++ .../hmg_patient_app/opentok/LocalVideo.kt | 58 +++ .../cloud/hmg_patient_app/opentok/OpenTok.kt | 181 +++++++++ .../hmg_patient_app/opentok/RemoteVideo.kt | 58 +++ .../hmg_patient_app/penguin/PenguinMethod.kt | 13 + .../penguin/PenguinNavigator.kt | 97 +++++ .../hmg_patient_app/penguin/PenguinView.kt | 321 ++++++++++++++++ .../com/cloud/hmg_patient_app/utils/API.kt | 11 + .../cloud/hmg_patient_app/utils/Constants.kt | 8 + .../hmg_patient_app/utils/FlutterText.kt | 36 ++ .../cloud/hmg_patient_app/utils/HMGUtils.kt | 224 +++++++++++ .../cloud/hmg_patient_app/utils/HMG_Wifi_.kt | 351 ++++++++++++++++++ .../com/cloud/hmg_patient_app/utils/Logs.kt | 145 ++++++++ .../utils/OpenTokPlatformBridge.kt | 45 +++ .../hmg_patient_app/utils/PlatformBridge.kt | 199 ++++++++++ .../whatsapp/AppSignatureRetriever.java | 81 ++++ .../hmg_patient_app/whatsapp/WhatsApp.kt | 31 ++ .../whatsapp/WhatsAppCodeActivity.kt | 17 + .../whatsapp/WhatsAppOtpPlatformBridge.kt | 48 +++ android/build.gradle | 21 +- assets/images/new-design/passport.svg | 268 ++++++------- assets/images/new/download_1.svg | 10 +- assets/images/new/medical_instructions.svg | 3 + assets/images/new/red-star.svg | 153 ++++++++ .../new/services/medical_instructions.svg | 3 + ios/Flutter/ephemeral/flutter_lldb_helper.py | 32 ++ ios/Flutter/ephemeral/flutter_lldbinit | 5 + ios/Runner/ar.lproj/LaunchScreen.strings | 1 + ios/Runner/ar.lproj/Main.strings | 1 + ios/Runner/ar.lproj/Main_Custom.strings | 1 + lib/uitl/app_toast.dart | 3 +- .../medical/LabResult/FlowChartPage.dart | 7 +- 56 files changed, 3936 insertions(+), 169 deletions(-) create mode 100644 android/app/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/Application.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/FirebaseCloudMessagingPluginRegistrant.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/MainActivity.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/PenguinInPlatformBridge.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionHelper.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionManager.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionResultReceiver.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/bkp_files/CustomFlutterHmsMessageService.java create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/GeoZoneModel.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/HMG_Geofence.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiver.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiverWithJobService.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceErrorMessages.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceTransitionsJobIntentService.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofencingRebootBroadcastReceiver.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/LocationProviderChangeReceiver.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/ReregisterGeofenceJobService.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Guest.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Internet.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WPA.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WpaEnterprise.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/LocalVideo.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/OpenTok.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/RemoteVideo.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinMethod.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinNavigator.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinView.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/API.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Constants.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/FlutterText.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMGUtils.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMG_Wifi_.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Logs.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/OpenTokPlatformBridge.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/PlatformBridge.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/AppSignatureRetriever.java create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsApp.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppCodeActivity.kt create mode 100644 android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppOtpPlatformBridge.kt create mode 100644 assets/images/new/medical_instructions.svg create mode 100644 assets/images/new/red-star.svg create mode 100644 assets/images/new/services/medical_instructions.svg create mode 100644 ios/Flutter/ephemeral/flutter_lldb_helper.py create mode 100644 ios/Flutter/ephemeral/flutter_lldbinit create mode 100644 ios/Runner/ar.lproj/LaunchScreen.strings create mode 100644 ios/Runner/ar.lproj/Main.strings create mode 100644 ios/Runner/ar.lproj/Main_Custom.strings diff --git a/README.md b/README.md index 8a284cae..25605462 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# diplomaticquarterapp +# hmg_patient_app A new Flutter application. diff --git a/android/app/build.gradle b/android/app/build.gradle index a14da293..5136988a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -55,7 +55,7 @@ configurations.all { android { compileSdkVersion 34 // ndkVersion "24.0.8215888" - namespace = "com.ejada.hmg" + buildFeatures { viewBinding true dataBinding true @@ -133,7 +133,7 @@ android { compileOptions { - coreLibraryDesugaringEnabled true +// coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -147,7 +147,7 @@ flutter { } dependencies { - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' +// coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "com.google.firebase:firebase-messaging:21.0.0" diff --git a/android/app/gradle/wrapper/gradle-wrapper.properties b/android/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..a2c20300 --- /dev/null +++ b/android/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +#Sun Sep 20 09:53:06 EEST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e9bc7171..907a2729 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -193,7 +193,6 @@ - diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/Application.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/Application.kt new file mode 100644 index 00000000..e179bc6a --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/Application.kt @@ -0,0 +1,59 @@ +//package com.cloud.diplomaticquarterapp +package com.ejada.hmg + +import com.facebook.stetho.Stetho +import io.flutter.app.FlutterApplication +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback +//import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService + +class Application : FlutterApplication(), PluginRegistrantCallback { + override fun onCreate() { + super.onCreate() + // FlutterFirebaseMessagingService.setPluginRegistrant(this) + +// Stetho.initializeWithDefaults(this); + // Create an InitializerBuilder + // Create an InitializerBuilder + val initializerBuilder = Stetho.newInitializerBuilder(this) + + + // Enable Chrome DevTools + initializerBuilder.enableWebKitInspector( + Stetho.defaultInspectorModulesProvider(this) + ) + + // Enable command line interface + initializerBuilder.enableDumpapp( + Stetho.defaultDumperPluginsProvider(this) + ) + + + // Use the InitializerBuilder to generate an Initializer + val initializer = initializerBuilder.build() + + + // Initialize Stetho with the Initializer + Stetho.initialize(initializer) + } + + override fun registerWith(registry: PluginRegistry) { + // io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")); + } +} + +//import io.flutter.app.FlutterApplication +//import io.flutter.plugin.common.PluginRegistry +//import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback +//import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService +// +//class Application : FlutterApplication(), PluginRegistrantCallback { +// override fun onCreate() { +// super.onCreate() +// FlutterFirebaseMessagingService.setPluginRegistrant(this) +// } +// +// override fun registerWith(registry: PluginRegistry?) { +// FirebaseCloudMessagingPluginRegistrant.registerWith(registry) +// } +//} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/FirebaseCloudMessagingPluginRegistrant.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/FirebaseCloudMessagingPluginRegistrant.kt new file mode 100644 index 00000000..df471796 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/FirebaseCloudMessagingPluginRegistrant.kt @@ -0,0 +1,25 @@ +//package com.cloud.diplomaticquarterapp +package com.ejada.hmg + +import io.flutter.plugin.common.PluginRegistry +//import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin + +object FirebaseCloudMessagingPluginRegistrant { + fun registerWith(registry: PluginRegistry?) { + if (alreadyRegisteredWith(registry)) { + return + } + // FirebaseMessagingPlugin.registerWith(registry?.registrarFor("io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin")) + } + + private fun alreadyRegisteredWith(registry: PluginRegistry?): Boolean { + val key: String? = FirebaseCloudMessagingPluginRegistrant::class.java.canonicalName + if (key?.let { registry?.hasPlugin(it) }!!) { + return true + } + if (registry != null) { + registry.registrarFor(key) + } + return false + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/MainActivity.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/MainActivity.kt new file mode 100644 index 00000000..6f47d6be --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/MainActivity.kt @@ -0,0 +1,75 @@ +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.cloud.hmg_patient_app.PenguinInPlatformBridge +import com.cloud.hmg_patient_app.whatsapp.AppSignatureRetriever +import com.ejada.hmg.utils.* +import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugins.GeneratedPluginRegistrant +import com.cloud.hmg_patient_app.whatsapp.WhatsApp +import com.cloud.hmg_patient_app.whatsapp.WhatsAppOtpPlatformBridge + + +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) + + PlatformBridge(flutterEngine, this).create() + OpenTokPlatformBridge(flutterEngine, this).create() + PenguinInPlatformBridge(flutterEngine, this).create() + WhatsAppOtpPlatformBridge(flutterEngine, this).invoke() + AppSignatureRetriever().logSignatures(this) +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {x +// val mChannel = NotificationChannel("video_call_noti", "video call", NotificationManager.IMPORTANCE_HIGH) +// val soundUri = Uri.parse("android.resource://" + getApplicationContext() +// .getPackageName() + "/" + R.raw.alert) +// System.out.println("soundUri"); +// System.out.println("soundUri: $soundUri"); +// System.out.println("soundUri : ${soundUri.path}"); +// val att = AudioAttributes.Builder() +// .setUsage(AudioAttributes.USAGE_NOTIFICATION) +// .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) +// .build(); +// mChannel.setSound(soundUri , att) +// mChannel.description = "Video Call Notifications" +// val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager +// notificationManager.createNotificationChannel(mChannel) +// } + +// val time = timeToMillis("04:00:00", "HH:mm:ss") + + } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + 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() + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PenguinInPlatformBridge.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PenguinInPlatformBridge.kt new file mode 100644 index 00000000..2fd03a5d --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PenguinInPlatformBridge.kt @@ -0,0 +1,53 @@ +package com.cloud.hmg_patient_app + +import com.ejada.hmg.MainActivity +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.cloud.hmg_patient_app.penguin.PenguinView +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +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") + val args = call.arguments as Map? + Log.d("TAG", "configureFlutterEngine: $args") + println("args") + args?.let { + PenguinView( + mainActivity, + 100, + args, + flutterEngine.dartExecutor.binaryMessenger, + activity = mainActivity, + channel + ) + } + } + + else -> { + result.notImplemented() + } + } + } + } + +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionHelper.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionHelper.kt new file mode 100644 index 00000000..b308af05 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionHelper.kt @@ -0,0 +1,28 @@ +package com.cloud.diplomaticquarterapp.PermissionManager + +import android.Manifest +import android.os.Build + +object PermissionHelper { + + fun getRequiredPermissions(): Array { + val permissions = mutableListOf( + Manifest.permission.INTERNET, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.BLUETOOTH, + Manifest.permission.BLUETOOTH_ADMIN, +// Manifest.permission.ACTIVITY_RECOGNITION + ) + + // For Android 12 (API level 31) and above, add specific permissions + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // Android 12 (API 31) and above + permissions.add(Manifest.permission.BLUETOOTH_SCAN) + permissions.add(Manifest.permission.BLUETOOTH_CONNECT) + permissions.add(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS) + } + + return permissions.toTypedArray() + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionManager.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionManager.kt new file mode 100644 index 00000000..81f2a6f0 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionManager.kt @@ -0,0 +1,50 @@ +package com.cloud.diplomaticquarterapp.PermissionManager + +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat + +class PermissionManager( + private val context: Context, + val listener: PermissionListener, + private val requestCode: Int, + vararg permissions: String +) { + + private val permissionsArray = permissions + + interface PermissionListener { + fun onPermissionGranted() + fun onPermissionDenied() + } + + fun arePermissionsGranted(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + permissionsArray.all { + ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED + } + } else { + true + } + } + + fun requestPermissions(activity: Activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ActivityCompat.requestPermissions(activity, permissionsArray, requestCode) + } + } + + fun handlePermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + if (this.requestCode == requestCode) { + val allGranted = grantResults.all { it == PackageManager.PERMISSION_GRANTED } + if (allGranted) { + listener.onPermissionGranted() + } else { + listener.onPermissionDenied() + } + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionResultReceiver.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionResultReceiver.kt new file mode 100644 index 00000000..33765192 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/PermissionManager/PermissionResultReceiver.kt @@ -0,0 +1,15 @@ +package com.cloud.diplomaticquarterapp.PermissionManager + +// PermissionResultReceiver.kt +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class PermissionResultReceiver( + private val callback: (Boolean) -> Unit +) : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + val granted = intent?.getBooleanExtra("PERMISSION_GRANTED", false) ?: false + callback(granted) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/bkp_files/CustomFlutterHmsMessageService.java b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/bkp_files/CustomFlutterHmsMessageService.java new file mode 100644 index 00000000..c96a60af --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/bkp_files/CustomFlutterHmsMessageService.java @@ -0,0 +1,56 @@ +/* +* --------------- +* Note: Todo +* --------------- +* Need to be place in huawei_push (package com.huawei.hms.flutter.push.hms) and define in huawei_push manifest.xml +* */ + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.util.Log; + +import com.huawei.hms.flutter.push.hms.FlutterHmsMessageService; +import com.huawei.hms.flutter.push.utils.ApplicationUtils; + +import org.json.JSONObject; + +//package com.huawei.hms.flutter.push.hms +// +// +//import android.content.Context; +//import android.content.Intent; +//import android.content.SharedPreferences; +// +//import com.huawei.hms.flutter.push.hms.FlutterHmsMessageService; +//import com.huawei.hms.flutter.push.utils.ApplicationUtils; +//import com.huawei.hms.push.RemoteMessage; +// +//import org.json.JSONObject; +// +//public class CustomFlutterHmsMessageService extends FlutterHmsMessageService { +// @Override +// public void onMessageReceived(RemoteMessage remoteMessage) { +// super.onMessageReceived(remoteMessage); +// try { +// String jsonStr = remoteMessage.getData(); +// JSONObject json_data = new JSONObject(jsonStr); +// JSONObject json_data_data = new JSONObject(json_data.getString("data")); +// if(json_data_data.getString("is_call").equalsIgnoreCase("true")){ +// boolean isApplicationInForeground = ApplicationUtils.isApplicationInForeground(this); +// if(!isApplicationInForeground){ +// SharedPreferences preferences = getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE); +// preferences.edit().putString("flutter.call_data", json_data.getString("data")).apply(); +// +// Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName()); +// intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); +// startActivity(intent); +// Log.v("onMessageReceived", "startActivity(intent) called"); +// } +// } +// +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } +//} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/GeoZoneModel.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/GeoZoneModel.kt new file mode 100644 index 00000000..16ed0939 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/GeoZoneModel.kt @@ -0,0 +1,58 @@ +package com.ejada.hmg.geofence + +import com.google.android.gms.location.Geofence +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class GeoZoneModel { + var GEOF_ID:Int = 0 + var Radius:Int = 0 + var Type:Int = 0 + var ProjectID:Int = 0 + + var Description:String? = null + var DescriptionN:String? = null + var Latitude:String? = null + var Longitude:String? = null + var ImageURL:String? = null + var IsCity:String? = null + + fun identifier():String{ + return "$GEOF_ID" + "_hmg" + } + + fun message():String{ + return Description ?: "nil" + } + + fun listFrom(jsonString: String) : List{ + val type = object : TypeToken?>() {}.getType() + return Gson().fromJson(jsonString, type) + } + + fun toGeofence() : Geofence?{ + if (!Latitude.isNullOrEmpty() && !Longitude.isNullOrEmpty() && Radius > 50) { + val lat = Latitude!!.trim().toDoubleOrNull() + val long = Longitude!!.trim().toDoubleOrNull() + val rad = Radius.toFloat() + if(lat != null && long != null){ + + val loiteringDelayMinutes:Int = 2 // in Minutes + return Geofence.Builder() + .setRequestId(identifier()) + .setCircularRegion( + lat, + long, + rad + ) + .setTransitionTypes(GeofenceTransition.ENTER_EXIT.value) + .setNotificationResponsiveness(0) + .setLoiteringDelay(loiteringDelayMinutes * 60 * 1000) + .setExpirationDuration(Geofence.NEVER_EXPIRE) + .build() + } + } + return null + } + +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/HMG_Geofence.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/HMG_Geofence.kt new file mode 100644 index 00000000..8452a083 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/HMG_Geofence.kt @@ -0,0 +1,291 @@ +package com.ejada.hmg.geofence + +import android.Manifest +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.location.Location +import androidx.core.content.ContextCompat +import com.ejada.hmg.geofence.intent_receivers.GeofenceBroadcastReceiver +import com.ejada.hmg.geofence.intent_receivers.ReregisterGeofenceJobService +import com.ejada.hmg.utils.* +import com.google.android.gms.location.Geofence +import com.google.android.gms.location.GeofencingClient +import com.google.android.gms.location.GeofencingRequest +import com.google.android.gms.location.LocationServices +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +enum class GeofenceTransition(val value: Int) { + ENTER(1), + EXIT(2), + DWELL(4), + + ENTER_EXIT((ENTER.value or EXIT.value)), + DWELL_EXIT((DWELL.value or EXIT.value)); + + companion object { + fun fromInt(value: Int) = GeofenceTransition.values().first { it.value == value } + } + + fun named(): String { + if (value == 1) return "Enter" + if (value == 2) return "Exit" + if (value == 4) return "dWell" + if (value == (ENTER.value or EXIT.value)) return "Enter or Exit" + if (value == (DWELL.value or EXIT.value)) return "DWell or Exit" + return "unknown" + } +} + +class HMG_Geofence { + // https://developer.android.com/training/location/geofencing#java + + private lateinit var context: Context + private lateinit var preferences: SharedPreferences + private val gson = Gson() + + private lateinit var geofencingClient: GeofencingClient + private val geofencePendingIntent: PendingIntent by lazy { + val intent = Intent(context, GeofenceBroadcastReceiver::class.java) + PendingIntent.getBroadcast( + context, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE + ) + } + + companion object { + + var instance: HMG_Geofence? = null + fun shared(context: Context): HMG_Geofence { + if (instance == null) { + instance = HMG_Geofence() + instance?.context = context + instance?.geofencingClient = LocationServices.getGeofencingClient(context) + instance?.preferences = + context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + } + return instance!! + } + } + + private fun limitize(zones: List): List { + var geoZones_ = zones + if (zones.size > 100) + geoZones_ = zones.subList(0, 99) + return geoZones_ + } + + + fun register(completion: ((Boolean, java.lang.Exception?) -> Unit)) { + unRegisterAll { status, exception -> + val geoZones = getGeoZonesFromPreference(context) + doRegister(geoZones) { status_, error -> + completion.let { it(status_, error) } + } + } + } + + fun unRegisterAll(completion: (status: Boolean, exception: Exception?) -> Unit) { + getActiveGeofences({ success -> + removeActiveGeofences() + if (success.isNotEmpty()) + geofencingClient + .removeGeofences(success) + .addOnSuccessListener { + completion(true, null) + } + .addOnFailureListener { + completion(false, it) + saveLog(context, "error:REMOVE_GEOFENCES", it.localizedMessage) + } + else + completion(true, null) + + }, { failed -> + // Nothing to do with failed geofences. + }) + } + + private fun doRegister( + geoZones: List, + completion: ((Boolean, java.lang.Exception?) -> Unit)? = null + ) { + if (geoZones.isEmpty()) + return + + val geoZones_ = limitize(geoZones) + + fun buildGeofencingRequest(geofences: List): GeofencingRequest { + return GeofencingRequest.Builder() + .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_DWELL) + .addGeofences(geofences) + .build() + } + + getActiveGeofences({ active -> + + val geofences = mutableListOf() + geoZones_.forEach { + it.toGeofence()?.let { geof -> + if (!active.contains(geof.requestId)) { // if not already registered then register + geofences.add(geof) + } + } + } + + if (checkPermission() && geofences.isNotEmpty()) { + geofencingClient + .addGeofences(buildGeofencingRequest(geofences), geofencePendingIntent) + .addOnSuccessListener { + Logs.RegisterGeofence.save( + context, + "SUCCESS", + "Successfuly registered the geofences", + Logs.STATUS.SUCCESS + ) + saveActiveGeofence(geofences.map { it.requestId }, listOf()) + completion?.let { it(true, null) } + } + .addOnFailureListener { exc -> + Logs.RegisterGeofence.save( + context, + "FAILED_TO_REGISTER", + "Failed to register geofence", + Logs.STATUS.ERROR + ) + completion?.let { it(false, exc) } + } + + // Schedule the job to register after specified duration (due to: events not calling after long period.. days or days [Needs to register fences again]) + HMGUtils.scheduleJob( + context, + ReregisterGeofenceJobService::class.java, + ReregisterGeofenceJobService.JobID, + ReregisterGeofenceJobService.TriggerIntervalDuration + ) + } + + }, null) + + } + + fun getGeoZonesFromPreference(context: Context): List { + val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + val json = pref.getString(PREF_KEY_HMG_ZONES, "[]") + + val geoZones = GeoZoneModel().listFrom(json!!) + return geoZones + } + + fun saveActiveGeofence(success: List, failed: List) { + val jsonSuccess = gson.toJson(success) + val jsonFailure = gson.toJson(failed) + preferences.edit().putString(PREF_KEY_SUCCESS, jsonSuccess).apply() + preferences.edit().putString(PREF_KEY_FAILED, jsonFailure).apply() + } + + fun removeActiveGeofences() { + preferences.edit().putString(PREF_KEY_SUCCESS, "[]").apply() + preferences.edit().putString(PREF_KEY_FAILED, "[]").apply() + } + + fun getActiveGeofences( + success: (success: List) -> Unit, + failure: ((failed: List) -> Unit)? + ) { + val type = object : TypeToken?>() {}.type + + val jsonSuccess = preferences.getString(PREF_KEY_SUCCESS, "[]") + val success = gson.fromJson>(jsonSuccess, type) + success(success) + + if (failure != null) { + val jsonFailure = preferences.getString(PREF_KEY_FAILED, "[]") + val failed = gson.fromJson>(jsonFailure, type) + failure(failed) + } + + } + + private fun checkPermission(): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + fun getPatientID(): Int? { + var profileJson = preferences.getString("flutter.imei-user-data", null) + if (profileJson == null) + profileJson = preferences.getString("flutter.user-profile", null) + + val type = object : TypeToken?>() {}.type + return gson.fromJson?>(profileJson, type) + ?.get("PatientID") + .toString() + .toDoubleOrNull() + ?.toInt() + } + + + fun handleEvent( + triggerGeofences: List, + location: Location, + transition: GeofenceTransition + ) { + getPatientID()?.let { patientId -> + getActiveGeofences({ activeGeofences -> + + triggerGeofences.forEach { geofence -> + // Extract PointID from 'geofence.requestId' and find from active geofences + val pointID = + activeGeofences.firstOrNull { it == geofence.requestId }?.split('_') + ?.first() + if (!pointID.isNullOrEmpty() && pointID.toIntOrNull() != null) { + + val body = mutableMapOf( + "PointsID" to pointID.toIntOrNull(), + "GeoType" to transition.value, + "PatientID" to patientId + ) + body.putAll(HMGUtils.defaultHTTPParams(context)) + + httpPost>(API.LOG_GEOFENCE, body, { response -> + saveLog( + context, + "HMG_GEOFENCE_NOTIFY", + "Success: Notified to server\uD83D\uDE0E." + ) + sendNotification( + context, + transition.named(), + geofence.requestId, + "Notified to server.😎" + ) + }, { exception -> + val errorMessage = "${transition.named()}, ${geofence.requestId}" + saveLog( + context, + "HMG_GEOFENCE_NOTIFY", + "failed: $errorMessage | error: ${exception.localizedMessage}" + ) + sendNotification( + context, + transition.named(), + geofence.requestId, + "Failed to notify server😔 -> ${exception.localizedMessage}" + ) + }) + + } + } + + }, null) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiver.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiver.kt new file mode 100644 index 00000000..2ed2daf4 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiver.kt @@ -0,0 +1,71 @@ +package com.ejada.hmg.geofence.intent_receivers + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.util.Log +import com.ejada.hmg.geofence.GeofenceTransition +import com.ejada.hmg.geofence.HMG_Geofence +import com.ejada.hmg.utils.Logs +import com.google.android.gms.location.GeofenceStatusCodes +import com.google.android.gms.location.GeofencingEvent + +class GeofenceBroadcastReceiver : BroadcastReceiver() { + private val LOG_TAG = "GeofenceBroadcastReceiver" + + @SuppressLint("LongLogTag") + override fun onReceive(context: Context, intent: Intent) { + + val geofencingEvent = GeofencingEvent.fromIntent(intent) + if (geofencingEvent != null) { + if (geofencingEvent.hasError()) { + val errorMessage = + GeofenceErrorMessages.getErrorString(context, geofencingEvent.errorCode) + Log.e(LOG_TAG, errorMessage) + + Logs.GeofenceEvent.save( + context, + LOG_TAG, + "Error while triggering geofence event", + Logs.STATUS.ERROR + ) + doReRegisterIfRequired(context, geofencingEvent.errorCode) + + return + } + } + if (geofencingEvent != null) { + Logs.GeofenceEvent.save( + context, + LOG_TAG, + "Geofence event triggered: ${GeofenceTransition.fromInt(geofencingEvent.geofenceTransition).value} for ${geofencingEvent.triggeringGeofences?.map { it.requestId }}", + Logs.STATUS.SUCCESS + ) + geofencingEvent.triggeringLocation?.let { + geofencingEvent.triggeringGeofences?.let { it1 -> + HMG_Geofence.shared(context).handleEvent( + it1, + it, GeofenceTransition.fromInt(geofencingEvent.geofenceTransition) + ) + } + } + }; + + } + + fun doReRegisterIfRequired(context: Context, errorCode: Int) { + val errorRequiredReregister = listOf( + GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE, + GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES, + GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS, + GeofenceStatusCodes.GEOFENCE_REQUEST_TOO_FREQUENT + ) + + if (errorRequiredReregister.contains(errorCode)) + HMG_Geofence.shared(context).register() { status, error -> + + } + + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiverWithJobService.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiverWithJobService.kt new file mode 100644 index 00000000..4e4f19ff --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceBroadcastReceiverWithJobService.kt @@ -0,0 +1,16 @@ + + +package com.ejada.hmg.geofence.intent_receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.ejada.hmg.geofence.HMG_Geofence +import com.google.android.gms.location.GeofenceStatusCodes + +class GeofenceBroadcastReceiverWithJobService : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + GeofenceTransitionsJobIntentService.enqueueWork(context, intent) + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceErrorMessages.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceErrorMessages.kt new file mode 100644 index 00000000..94ceaa3d --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceErrorMessages.kt @@ -0,0 +1,43 @@ + + +package com.ejada.hmg.geofence.intent_receivers + +import android.content.Context +import com.ejada.hmg.R +import com.ejada.hmg.geofence.HMG_Geofence +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.location.GeofenceStatusCodes + +object GeofenceErrorMessages { + fun getErrorString(context: Context, e: Exception): String { + return if (e is ApiException) { + getErrorString(context, e.statusCode) + } else { + context.resources.getString(R.string.geofence_unknown_error) + } + } + + fun getErrorString(context: Context, errorCode: Int): String { + val resources = context.resources + val errorMessage = when (errorCode) { + GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE -> + resources.getString(R.string.geofence_not_available) + + GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES -> + resources.getString(R.string.geofence_too_many_geofences) + + GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS -> + resources.getString(R.string.geofence_too_many_pending_intents) + + GeofenceStatusCodes.GEOFENCE_INSUFFICIENT_LOCATION_PERMISSION -> + resources.getString(R.string.GEOFENCE_INSUFFICIENT_LOCATION_PERMISSION) + + GeofenceStatusCodes.GEOFENCE_REQUEST_TOO_FREQUENT -> + resources.getString(R.string.GEOFENCE_REQUEST_TOO_FREQUENT) + + else -> resources.getString(R.string.geofence_unknown_error) + } + + return errorMessage + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceTransitionsJobIntentService.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceTransitionsJobIntentService.kt new file mode 100644 index 00000000..b083e04e --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofenceTransitionsJobIntentService.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018 Razeware LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * Notwithstanding the foregoing, you may not use, copy, modify, merge, publish, + * distribute, sublicense, create a derivative work, and/or sell copies of the + * Software in any work that is designed, intended, or marketed for pedagogical or + * instructional purposes related to programming, coding, application development, + * or information technology. Permission for such use, copying, modification, + * merger, publication, distribution, sublicensing, creation of derivative works, + * or sale is expressly withheld. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +package com.ejada.hmg.geofence.intent_receivers + +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.core.app.JobIntentService +import com.ejada.hmg.geofence.GeofenceTransition +import com.ejada.hmg.geofence.HMG_Geofence +import com.ejada.hmg.utils.saveLog +import com.google.android.gms.location.GeofenceStatusCodes +import com.google.android.gms.location.GeofencingEvent + +class GeofenceTransitionsJobIntentService : JobIntentService() { + + companion object { + private const val LOG_TAG = "GeoTrIntentService" + + private const val JOB_ID = 95902 + var context_: Context? = null + fun enqueueWork(context: Context, intent: Intent) { + context_ = context + enqueueWork( + context, + GeofenceTransitionsJobIntentService::class.java, JOB_ID, + intent) + } + } + + override fun onHandleWork(intent: Intent) { + val geofencingEvent = GeofencingEvent.fromIntent(intent) + if (geofencingEvent != null) { + if (geofencingEvent.hasError()) { + val errorMessage = GeofenceErrorMessages.getErrorString(context_!!, geofencingEvent.errorCode) + Log.e(LOG_TAG, errorMessage) + + + saveLog(context_!!,LOG_TAG,errorMessage) + doReRegisterIfRequired(context_!!, geofencingEvent.errorCode) + + return + } + } + + if (geofencingEvent != null) { + geofencingEvent.triggeringGeofences?.let { geofencingEvent.triggeringLocation?.let { it1 -> + HMG_Geofence.shared(context_!!).handleEvent(it, + it1, GeofenceTransition.fromInt(geofencingEvent.geofenceTransition)) + } } + }; + + } + + + fun doReRegisterIfRequired(context: Context, errorCode: Int){ + val errorRequiredReregister = listOf( + GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE, + GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES, + GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS, + GeofenceStatusCodes.GEOFENCE_REQUEST_TOO_FREQUENT + ) + + if(errorRequiredReregister.contains(errorCode)) + HMG_Geofence.shared(context).register(){ status, exc -> } + + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofencingRebootBroadcastReceiver.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofencingRebootBroadcastReceiver.kt new file mode 100644 index 00000000..809f92bc --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/GeofencingRebootBroadcastReceiver.kt @@ -0,0 +1,23 @@ + + +package com.ejada.hmg.geofence.intent_receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.ejada.hmg.geofence.HMG_Geofence +import com.ejada.hmg.utils.PREFS_STORAGE + +class GeofencingRebootBroadcastReceiver : BroadcastReceiver(){ + override fun onReceive(context: Context, intent: Intent) { + + if (Intent.ACTION_BOOT_COMPLETED.equals(intent.action)) { +// if (intent.action.equals("android.intent.action.BOOT_COMPLETE")) { + val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + pref.edit().putString("REBOOT_DETECTED","YES").apply() + + HMG_Geofence.shared(context).register(){ status, error -> } + } + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/LocationProviderChangeReceiver.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/LocationProviderChangeReceiver.kt new file mode 100644 index 00000000..8c34dedb --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/LocationProviderChangeReceiver.kt @@ -0,0 +1,25 @@ + + +package com.ejada.hmg.geofence.intent_receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.location.LocationManager +import com.ejada.hmg.geofence.HMG_Geofence +import com.ejada.hmg.utils.HMGUtils +import com.ejada.hmg.utils.PREFS_STORAGE + +class LocationProviderChangeReceiver : BroadcastReceiver() { + private val LOG_TAG = "LocationProviderChangeReceiver" + override fun onReceive(context: Context, intent: Intent) { + + if (LocationManager.PROVIDERS_CHANGED_ACTION.equals(intent.action)) { + val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + pref.edit().putString("LOCATION_PROVIDER_CHANGE","YES").apply() + + HMG_Geofence.shared(context).register(){ s, e -> } + } + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/ReregisterGeofenceJobService.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/ReregisterGeofenceJobService.kt new file mode 100644 index 00000000..e7c0a82d --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/geofence/intent_receivers/ReregisterGeofenceJobService.kt @@ -0,0 +1,24 @@ +package com.ejada.hmg.geofence.intent_receivers + +import android.app.job.JobParameters +import android.app.job.JobService +import com.ejada.hmg.geofence.HMG_Geofence +import com.ejada.hmg.utils.Logs + +class ReregisterGeofenceJobService : JobService(){ + companion object{ + val TriggerIntervalDuration:String = "06:00:00" + val JobID = 918273 + } + override fun onStartJob(params: JobParameters?): Boolean { + Logs.save(applicationContext,"ReregisterGeofenceJobService.onStartJob", "triggered to re-register the geofences after $TriggerIntervalDuration >> [HH:mm:ss]") + HMG_Geofence.shared(applicationContext).register(){ status, error -> + jobFinished(params, true) + } + return true + } + + override fun onStopJob(params: JobParameters?): Boolean { + return true + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Guest.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Guest.kt new file mode 100644 index 00000000..7b3cc2d1 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Guest.kt @@ -0,0 +1,257 @@ +package com.ejada.hmg.hmgwifi + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.net.* +import android.net.wifi.* +import android.os.Build +import android.os.PatternMatcher +import android.provider.Settings +import android.util.Log +import androidx.annotation.RequiresApi +import com.ejada.hmg.MainActivity +import com.ejada.hmg.utils.HMGUtils + + + + + +class HMG_Guest(private var context: MainActivity, ssid: String) { + private val TAG = "HMG_Guest" + private val TEST = false + private var SSID = ssid +// private var SSID = "HMG-MOHEMM" + + val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager? + + private lateinit var completionListener: ((status: Boolean, message: String) -> Unit) + + fun completionOnUiThread(status: Boolean, message: String){ + completionListener(status, message) + } + + fun enableWifi(){ + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){ + wifiManager?.setWifiEnabled(true) + HMGUtils.popFlutterText(context,"enablingWifi"); + HMGUtils.timer(2000,false){ + connectApiLessThen29() + } + }else { + val panelIntent = Intent(Settings.Panel.ACTION_WIFI) + context.startActivityForResult(panelIntent, 1) + } + } + /* + * Helpful: + * http://stackoverflow.com/questions/8818290/how-to-connect-to-a-specific-wifi-network-in-android-programmatically + */ + fun connectToHMGGuestNetwork(completion: (status: Boolean, message: String) -> Unit) { + completionListener = completion + wifiManager?.let { wm -> + if (!wm.isWifiEnabled){ + enableWifi() + }else{ + connectWifi() + } + } + + } + + private fun errorConnecting(){ + completionOnUiThread(false, "errorConnectingHmgNetwork") + } + + fun connectWifi(){ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + connectApiGreaterThen28() + }else { + connectApiLessThen29() + } + } + + // I }else{f CompileSDK is greater and equals to APILevel 29 + @RequiresApi(Build.VERSION_CODES.Q) + private fun connectApiGreaterThen28(){ + Log.e(TAG, "connection wifi with Android Q+") + + val networkRequest: NetworkRequest = NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) //removeCapability added for hotspots without internet + .setNetworkSpecifier( + WifiNetworkSpecifier.Builder() + .setSsid(SSID) + .build() + + ).build() + + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + connectivityManager.bindProcessToNetwork(network) + HMGUtils.timer(2000,false){ + completionListener(true, "Success") + } + Log.e(TAG, "onAvailable") + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + Log.e(TAG, "onLosing") + completionListener(false, "fail") + } + + override fun onLost(network: Network) { + super.onLost(network) + Log.e(TAG, "onLosing") + Log.e(TAG, "losing active connection") + completionListener(false, "fail") + } + + override fun onUnavailable() { + super.onUnavailable() + Log.e(TAG, "onUnavailable") + completionListener(false, "fail") + } + + } + + //timeout add because "No devices found" wasn't handled correct and doesn't throw Unavailable + connectivityManager.requestNetwork(networkRequest, networkCallback, 30000) + } + + + + fun connectApiLessThen29(){ + val wifi = WifiConfiguration() + wifi.SSID = """"$SSID"""" + wifi.status = WifiConfiguration.Status.ENABLED + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) + + wifi.networkId = ssidToNetworkId(wifi.SSID) + if (wifi.networkId == -1) { + wifiManager?.addNetwork(wifi) + } else { + Log.v(TAG, "WiFi found - updating it.\n") + wifiManager?.updateNetwork(wifi) + } + + Log.v(TAG, "saving config.\n") + wifiManager?.saveConfiguration() + + wifi.networkId = ssidToNetworkId(wifi.SSID) + + Log.v(TAG, "wifi ID in device = " + wifi.networkId) + + var supState: SupplicantState + val networkIdToConnect = wifi.networkId + if (networkIdToConnect >= 0) { + Log.v(TAG, "Start connecting...\n") + + // We disable the network before connecting, because if this was the last connection before + // a disconnect(), this will not reconnect. + wifiManager?.disableNetwork(networkIdToConnect) + wifiManager?.enableNetwork(networkIdToConnect, true) + + val wifiInfo: WifiInfo = wifiManager!!.connectionInfo + + HMGUtils.timer(5000,false){ + supState = wifiInfo.supplicantState + Log.i(TAG, "Done connect to network : status = $supState") + val successStates = listOf(SupplicantState.COMPLETED, SupplicantState.ASSOCIATED) + if (successStates.contains(supState)) + completionListener(true,"Connected to internet Wifi") + else + completionListener(false,"errorConnectingHmgNetwork") + } + + } else { + Log.v(TAG, "WifiWizard: cannot connect to network") + completionListener(false,"errorConnectingHmgNetwork") + } + +// val wifi = WifiConfiguration() +// wifi.SSID = SSID +// wifi.status = WifiConfiguration.Status.ENABLED +// wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); +// +// wifi.networkId = ssidToNetworkId(SSID) +// +// // Set network to highest priority (deprecated in API >= 26) +// if(Build.VERSION.SDK_INT < 26) { +// wifi.priority = getMaxWifiPriority(wifiManager!!) + 1; +// } +// +// // After processing authentication types, add or update network +// if(wifi.networkId == -1) { // -1 means SSID configuration does not exist yet +// +// val newNetId = wifiManager?.addNetwork(wifi)!! +// if( newNetId > -1 ){ +// completionListener(true,"Success") +// } else { +// completionListener(false, "ERROR_ADDING_NETWORK" ) +// } +// +// } else { +// +// var updatedNetID = wifiManager?.updateNetwork(wifi) +// +// if(updatedNetID == -1) +// updatedNetID = wifiManager?.addNetwork(wifi) +// +// if(updatedNetID > -1) { +// callbackContext.success( updatedNetID ) +// } else { +// callbackContext.error("ERROR_UPDATING_NETWORK") +// } +// +// } +// +// // WifiManager configurations are presistent for API 26+ +// if(Build.VERSION.SDK_INT < 26) { +// wifiManager?.saveConfiguration(); // Call saveConfiguration for older < 26 API +// } + } + + + /** + * This method takes a given String, searches the current list of configured WiFi + * networks, and returns the networkId for the network if the SSID matches. If not, + * it returns -1. + */ + @SuppressLint("MissingPermission") + private fun ssidToNetworkId(ssid: String): Int { + val currentNetworks = wifiManager!!.configuredNetworks + var networkId = -1 + + // For each network in the list, compare the SSID with the given one + for (test in currentNetworks) { + if (test.SSID == ssid) { + networkId = test.networkId + break + } + } + return networkId + } + + companion object{ + + /** + * Figure out what the highest priority network in the network list is and return that priority + */ + @RequiresApi(Build.VERSION_CODES.S) + fun getMaxWifiPriority(wifiManager:WifiManager) : Int { + val configurations = wifiManager.callerConfiguredNetworks + var maxPriority = 0 + configurations.forEach { + if (it.priority > maxPriority) { + maxPriority = it.priority; + } + } + return maxPriority; + } + } +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Internet.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Internet.kt new file mode 100644 index 00000000..dcfa5488 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/HMG_Internet.kt @@ -0,0 +1,120 @@ +package com.ejada.hmg.hmgwifi + +import android.annotation.SuppressLint +import com.ejada.hmg.utils.API +import com.ejada.hmg.MainActivity +import com.github.kittinunf.fuel.core.extensions.jsonBody +import com.github.kittinunf.fuel.httpGet +import com.github.kittinunf.fuel.httpPost +import org.json.JSONObject +import java.util.* + + +@SuppressLint("MissingPermission") +class HMG_Internet(flutterMainActivity: MainActivity) { + private val TAG = "HMG_Wifi" + private val TEST = true + + private var context = flutterMainActivity; + + private lateinit var completionListener: ((status: Boolean, message: String) -> Unit) + + private var SSID = "GUEST-POC" + + fun completionOnUiThread(status: Boolean, message: String){ + completionListener(status, message) +// context.runOnUiThread { +// .with(message){localized -> +// completionListener(status, localized) +// } +// } + } + + /* + * Helpful: + * http://stackoverflow.com/questions/8818290/how-to-connect-to-a-specific-wifi-network-in-android-programmatically + */ + fun connectToHMGGuestNetwork(username: String, password: String, completion: (status: Boolean, message: String) -> Unit): HMG_Internet { + completionListener = completion + WpaEnterprise(context,SSID).connect(username,username) { status, message -> + completionOnUiThread(status,message) + } + return this + } + + private fun haveInternet(completion: ((status: Boolean) -> Unit)){ + if (TEST) + completion(true) + + "https://captive.apple.com".httpGet().response { request, response, result -> + result.fold(success = { + val html = String(it).toLowerCase(Locale.ENGLISH) + .replace(" ", "", true) + .replace("\n","",true) + val have = html.contains("success", true) + completion(have) + + },failure = { + completion(false) + }) + } + } + + private fun getWifiCredentials(patientId:String, success: ((String?,String?) -> Unit)){ + if (TEST){ + SSID = "GUEST-POC" + success("2300", "0000") + return + } + + val jsonBody = """{ + "PatientID":$patientId + "VersionID": 8.8, + "Channel": 3, + "LanguageID": 2, + "IPAdress": "10.20.10.20", + "generalid": "Cs2020@2016$2958", + "PatientOutSA": 0, + "SessionID": "@admin", + "isDentalAllowedBackend": false, + "DeviceTypeID": 2, + "TokenID": "@admin", + "PatientTypeID": 1, + "PatientType": 1 + }""".trimMargin() + API.WIFI_CREDENTIALS. + httpPost() + .jsonBody(jsonBody, Charsets.UTF_8) + .response { request, response, result -> + + result.fold(success = { data -> + val jsonString = String(data) + val jsonObject = JSONObject(jsonString) + if(!jsonObject.getString("ErrorMessage").equals("null")){ + val errorMsg = jsonObject.getString("ErrorMessage") + completionOnUiThread(false, errorMsg) + + }else{ + jsonObject.getJSONArray("Hmg_SMS_Get_By_ProjectID_And_PatientIDList").let { array -> + array.getJSONObject(0).let { object_ -> + if (object_.has("UserName") && object_.has("UserName")){ + try { + val userName = object_.getString("UserName") + val password = object_.getString("Password") + success(userName, password) + }catch (e:Exception){ + success(null, null) + } + }else{ + completionOnUiThread(false, "somethingWentWrong") + } + } + } + } + + },failure = { error -> + completionOnUiThread(false, "somethingWentWrong" ) + }) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WPA.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WPA.kt new file mode 100644 index 00000000..61cc4f9f --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WPA.kt @@ -0,0 +1,105 @@ +package com.ejada.hmg.hmgwifi + +import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.net.wifi.* +import android.net.wifi.SupplicantState.ASSOCIATED +import android.net.wifi.SupplicantState.COMPLETED +import android.util.Log +import com.ejada.hmg.MainActivity +import com.ejada.hmg.utils.HMGUtils + +class WPA(mainActivity: MainActivity, SSID:String) { + private var TAG = "WPA" + private var SSID = "GUEST-POC" + private var wifiManager_: WifiManager? = null + private var connectivityManager_: ConnectivityManager? = null + + init { + wifiManager_ = mainActivity.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager? + connectivityManager_ = mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? + } + + fun connect(identity:String, password:String, completion: (status: Boolean, message: String) -> Unit) { + if(wifiManager_ == null || connectivityManager_ == null){ + completion(false,"errorConnectingHmgNetwork") + return + } + + val wifiManager = wifiManager_!! + val connectivityManager = connectivityManager_!! + + // Initialize the WifiConfiguration object + val enterpriseConfig = WifiEnterpriseConfig() + val wifi = WifiConfiguration() + wifi.SSID = """"$SSID"""" + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP) + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X) + enterpriseConfig.eapMethod = WifiEnterpriseConfig.Eap.PEAP + enterpriseConfig.identity = identity + enterpriseConfig.password = password + wifi.enterpriseConfig = enterpriseConfig + wifi.networkId = ssidToNetworkId(wifi.SSID) + if (wifi.networkId == -1) { + wifiManager.addNetwork(wifi) + } else { + Log.v(TAG, "WiFi found - updating it.\n") + wifiManager.updateNetwork(wifi) + } + Log.v(TAG, "saving config.\n") + wifiManager.saveConfiguration() + wifi.networkId = ssidToNetworkId(wifi.SSID) + + Log.v(TAG, "wifi ID in device = " + wifi.networkId) + + var supState: SupplicantState + val networkIdToConnect = wifi.networkId + if (networkIdToConnect >= 0) { + Log.v(TAG, "Start connecting...\n") + + // We disable the network before connecting, because if this was the last connection before + // a disconnect(), this will not reconnect. + wifiManager.disableNetwork(networkIdToConnect) + wifiManager.enableNetwork(networkIdToConnect, true) + + val wifiInfo: WifiInfo = wifiManager.connectionInfo + + HMGUtils.timer(5000,false){ + supState = wifiInfo.supplicantState + Log.i(TAG, "WifiWizard: Done connect to network : status = $supState") + val successStates = listOf(COMPLETED, ASSOCIATED) + if (successStates.contains(COMPLETED /*supState*/)) + + completion(true,"Connected to internet Wifi") + + else + completion(false,"errorConnectingHmgNetwork") + } + + } else { + Log.v(TAG, "WifiWizard: cannot connect to network") + completion(false,"errorConnectingHmgNetwork") + } + } + + /** + * This method takes a given String, searches the current list of configured WiFi + * networks, and returns the networkId for the network if the SSID matches. If not, + * it returns -1. + */ + @SuppressLint("MissingPermission") + private fun ssidToNetworkId(ssid: String): Int { + val currentNetworks = wifiManager_!!.configuredNetworks + var networkId = -1 + + // For each network in the list, compare the SSID with the given one + for (test in currentNetworks) { + if (test.SSID == ssid) { + networkId = test.networkId + break + } + } + return networkId + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WpaEnterprise.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WpaEnterprise.kt new file mode 100644 index 00000000..e8e39344 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/hmgwifi/WpaEnterprise.kt @@ -0,0 +1,166 @@ +package com.ejada.hmg.hmgwifi + +import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.net.wifi.* +import android.net.wifi.SupplicantState.ASSOCIATED +import android.net.wifi.SupplicantState.COMPLETED +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import com.ejada.hmg.MainActivity +import com.ejada.hmg.utils.HMGUtils +import java.security.cert.X509Certificate + +class WpaEnterprise(private val mainActivity: MainActivity, private var SSID: String) { + private var TAG = "WpaEnterprise" + + private lateinit var identity:String + private lateinit var password:String + private lateinit var completion:((status: Boolean, message: String) -> Unit) + + fun connect(identity:String, password:String, completion: (status: Boolean, message: String) -> Unit) { + this.password = password + this.identity = identity + this.completion = completion + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){ + apiGreaterThen28() + }else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){ + apiLessThen29() + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun apiLessThen29(){ + val wifiManager = mainActivity.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + + val wifi = WifiConfiguration() + wifi.SSID = """"$SSID"""" + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP) + wifi.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X) + wifi.enterpriseConfig = enterpriseConfig() + wifi.networkId = ssidToNetworkId(wifi.SSID, wifiManager) + if (wifi.networkId == -1) { + wifiManager.addNetwork(wifi) + } else { + Log.v(TAG, "WiFi found - updating it.\n") + wifiManager.updateNetwork(wifi) + } + Log.v(TAG, "saving config.\n") + wifiManager.saveConfiguration() + wifi.networkId = ssidToNetworkId(wifi.SSID, wifiManager) + + Log.v(TAG, "wifi ID in device = " + wifi.networkId) + + var supState: SupplicantState + val networkIdToConnect = wifi.networkId + if (networkIdToConnect >= 0) { + Log.v(TAG, "Start connecting...\n") + + // We disable the network before connecting, because if this was the last connection before + // a disconnect(), this will not reconnect. + wifiManager.disableNetwork(networkIdToConnect) + wifiManager.enableNetwork(networkIdToConnect, true) + + val wifiInfo: WifiInfo = wifiManager.connectionInfo + + HMGUtils.timer(5000,false){ + supState = wifiInfo.supplicantState + Log.i(TAG, "Done connect to network : status = $supState") + val successStates = listOf(COMPLETED, ASSOCIATED) + if (successStates.contains(supState)) + completion(true,"Connected to internet Wifi") + else + completion(false,"errorConnectingHmgNetwork") + } + + } else { + Log.v(TAG, "WifiWizard: cannot connect to network") + completion(false,"errorConnectingHmgNetwork") + } + } + + /** + * This method takes a given String, searches the current list of configured WiFi + * networks, and returns the networkId for the network if the SSID matches. If not, + * it returns -1. + */ + @SuppressLint("MissingPermission") + private fun ssidToNetworkId(ssid: String, wifiManager: WifiManager): Int { + val currentNetworks = wifiManager.configuredNetworks + var networkId = -1 + // For each network in the list, compare the SSID with the given one + for (test in currentNetworks) { + if (test.SSID == ssid) { + networkId = test.networkId + break + } + } + return networkId + } + + + @RequiresApi(Build.VERSION_CODES.Q) + fun apiGreaterThen28(){ + val connectivityManager = mainActivity.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + + Log.e(TAG, "connection wifi with Android Q+") + val wifiNetworkSpecifier: WifiNetworkSpecifier = WifiNetworkSpecifier.Builder() + .setSsid(SSID) + .setWpa2EnterpriseConfig(enterpriseConfig()) + .build() + + val networkRequest: NetworkRequest = NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .setNetworkSpecifier(wifiNetworkSpecifier) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) //removeCapability added for hotspots without internet + .build() + + val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + connectivityManager.bindProcessToNetwork(network) + completion(true, "200") + Log.e(TAG, "onAvailable") + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + Log.e(TAG, "onLosing") + } + + override fun onLost(network: Network) { + super.onLost(network) + Log.e(TAG, "onLosing") + Log.e(TAG, "losing active connection") + } + + override fun onUnavailable() { + super.onUnavailable() + completion(false, "401") + Log.e(TAG, "onUnavailable") + } + + } + + //timeout add because "No devices found" wasn't handled correct and doesn't throw Unavailable + connectivityManager.requestNetwork(networkRequest, networkCallback, 30000) + } + + fun enterpriseConfig() : WifiEnterpriseConfig{ + // Initialize the WifiConfiguration object + val enterpriseConfig = WifiEnterpriseConfig() + enterpriseConfig.eapMethod = WifiEnterpriseConfig.Eap.PEAP + enterpriseConfig.identity = identity + enterpriseConfig.password = password + enterpriseConfig.phase2Method = WifiEnterpriseConfig.Phase2.NONE +// enterpriseConfig.caCertificates = WifiEnterpriseConfig.Phase2. + return enterpriseConfig; + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/LocalVideo.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/LocalVideo.kt new file mode 100644 index 00000000..cfebfe65 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/LocalVideo.kt @@ -0,0 +1,58 @@ +package com.ejada.hmg.opentok + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import com.ejada.hmg.R +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +class LocalVideoFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + companion object { + private lateinit var view: LocalVideoPlatformView + + fun getViewInstance(context: Context): LocalVideoPlatformView { + if(!this::view.isInitialized) { + view = LocalVideoPlatformView(context) + } + + return view + } + } + + override fun create(context: Context, viewId: Int, args: Any?): PlatformView { + return getViewInstance(context) + } +} + +class LocalVideoPlatformView(context: Context) : PlatformView { + private val videoContainer: LocalVideoContainer = LocalVideoContainer(context) + + val container get() = videoContainer.publisherContainer + + override fun getView(): View { + return videoContainer + } + + override fun dispose() {} +} + +class LocalVideoContainer @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attrs, defStyle, defStyleRes) { + + var publisherContainer: FrameLayout private set + + init { + val view = LayoutInflater.from(context).inflate(R.layout.local_video, this, true) + publisherContainer = view.findViewById(R.id.publisher_container) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/OpenTok.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/OpenTok.kt new file mode 100644 index 00000000..5142702a --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/OpenTok.kt @@ -0,0 +1,181 @@ +package com.ejada.hmg.opentok + +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.ViewGroup +import com.facebook.stetho.urlconnection.StethoURLConnectionManager +import com.opentok.android.* +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + + +enum class OpenTokSDKState { + LOGGED_OUT, + LOGGED_IN, + WAIT, + ERROR +} + +class OpenTok(private var context: Context, private var flutterEngine: FlutterEngine){ + private lateinit var remoteVideoPlatformView: RemoteVideoPlatformView + private lateinit var localVideoPlatformView: LocalVideoPlatformView + + init { + remoteVideoPlatformView = RemoteVideoFactory.getViewInstance(context) + flutterEngine + .platformViewsController + .registry + .registerViewFactory("remote-video-container", RemoteVideoFactory()) + + localVideoPlatformView = LocalVideoFactory.getViewInstance(context) + flutterEngine + .platformViewsController + .registry + .registerViewFactory("local-video-container", LocalVideoFactory()) + } + + private var session: Session? = null + private var publisher: Publisher? = null + private var subscriber: Subscriber? = null + + + + private val sessionListener: Session.SessionListener = object: Session.SessionListener { + override fun onConnected(session: Session) { + // Connected to session + Log.d("MainActivity", "Connected to session ${session.sessionId}") + + publisher = Publisher.Builder(context).build().apply { + setPublisherListener(publisherListener) + renderer?.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL) + + view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + localVideoPlatformView.container.addView(view) + } + + notifyFlutter(OpenTokSDKState.LOGGED_IN) + session.publish(publisher) + } + + override fun onDisconnected(session: Session) { + notifyFlutter(OpenTokSDKState.LOGGED_OUT) + } + + override fun onStreamReceived(session: Session, stream: Stream) { + Log.d( + "MainActivity", + "onStreamReceived: New Stream Received " + stream.streamId + " in session: " + session.sessionId + ) + if (subscriber == null) { + subscriber = Subscriber.Builder(context, stream).build().apply { + renderer?.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL) + setSubscriberListener(subscriberListener) + session.subscribe(this) + + remoteVideoPlatformView.container.addView(view) + } + } + } + + override fun onStreamDropped(session: Session, stream: Stream) { + Log.d( + "MainActivity", + "onStreamDropped: Stream Dropped: " + stream.streamId + " in session: " + session.sessionId + ) + + if (subscriber != null) { + subscriber = null + + remoteVideoPlatformView.container.removeAllViews() + } + } + + override fun onError(session: Session, opentokError: OpentokError) { + Log.d("MainActivity", "Session error: " + opentokError.message) + notifyFlutter(OpenTokSDKState.ERROR) + } + } + + private val publisherListener: PublisherKit.PublisherListener = object : PublisherKit.PublisherListener { + override fun onStreamCreated(publisherKit: PublisherKit, stream: Stream) { + Log.d("MainActivity", "onStreamCreated: Publisher Stream Created. Own stream " + stream.streamId) + } + + override fun onStreamDestroyed(publisherKit: PublisherKit, stream: Stream) { + Log.d("MainActivity", "onStreamDestroyed: Publisher Stream Destroyed. Own stream " + stream.streamId) + } + + override fun onError(publisherKit: PublisherKit, opentokError: OpentokError) { + Log.d("MainActivity", "PublisherKit onError: " + opentokError.message) + notifyFlutter(OpenTokSDKState.ERROR) + } + } + + var subscriberListener: SubscriberKit.SubscriberListener = object : SubscriberKit.SubscriberListener { + override fun onConnected(subscriberKit: SubscriberKit) { + Log.d("MainActivity", "onConnected: Subscriber connected. Stream: " + subscriberKit.stream.streamId) + } + + override fun onDisconnected(subscriberKit: SubscriberKit) { + Log.d("MainActivity", "onDisconnected: Subscriber disconnected. Stream: " + subscriberKit.stream.streamId) + notifyFlutter(OpenTokSDKState.LOGGED_OUT) + } + + override fun onError(subscriberKit: SubscriberKit, opentokError: OpentokError) { + Log.d("MainActivity", "SubscriberKit onError: " + opentokError.message) + notifyFlutter(OpenTokSDKState.ERROR) + } + } + + fun initSession(call: MethodCall, result: MethodChannel.Result) { + + val apiKey = requireNotNull(call.argument("apiKey")) + val sessionId = requireNotNull(call.argument("sessionId")) + val token = requireNotNull(call.argument("token")) + + notifyFlutter(OpenTokSDKState.WAIT) + session = Session.Builder(context, apiKey, sessionId).build() + session?.setSessionListener(sessionListener) + session?.connect(token) + result.success("") + } + + fun swapCamera(call: MethodCall, result: MethodChannel.Result) { + publisher?.cycleCamera() + result.success(true) + } + + fun toggleAudio(call: MethodCall, result: MethodChannel.Result) { + if (publisher != null) { + publisher?.publishAudio = !(publisher!!.publishAudio) + result.success(true) + }else{ + result.success(false) + } + } + + fun toggleVideo(call: MethodCall, result: MethodChannel.Result) { + if (publisher != null) { + publisher?.publishVideo = !(publisher!!.publishVideo) + result.success(true) + }else{ + result.success(false) + } + } + + fun hangupCall(call: MethodCall, result: MethodChannel.Result) { + session?.disconnect() + result.success(true) + } + + private fun notifyFlutter(state: OpenTokSDKState) { + Handler(Looper.getMainLooper()).post { + MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "OpenTok-Platform-Bridge") + .invokeMethod("updateState", state.toString()) + } + } +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/RemoteVideo.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/RemoteVideo.kt new file mode 100644 index 00000000..9116a1e8 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/opentok/RemoteVideo.kt @@ -0,0 +1,58 @@ +package com.ejada.hmg.opentok + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import com.ejada.hmg.R +import io.flutter.plugin.common.StandardMessageCodec +import io.flutter.plugin.platform.PlatformView +import io.flutter.plugin.platform.PlatformViewFactory + +class RemoteVideoFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) { + + companion object { + private lateinit var view: RemoteVideoPlatformView + + fun getViewInstance(context: Context): RemoteVideoPlatformView { + if(!this::view.isInitialized) { + view = RemoteVideoPlatformView(context) + } + + return view + } + } + + override fun create(context: Context, viewId: Int, args: Any?): PlatformView { + return getViewInstance(context) + } +} + +class RemoteVideoPlatformView(context: Context) : PlatformView { + private val videoContainer: RemoteVideoContainer = RemoteVideoContainer(context) + + val container get() = videoContainer.subscriberContainer + + override fun getView(): View { + return videoContainer + } + + override fun dispose() {} +} + +class RemoteVideoContainer @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attrs, defStyle, defStyleRes) { + + var subscriberContainer: FrameLayout private set + + init { + val view = LayoutInflater.from(context).inflate(R.layout.remote_video, this, true) + subscriberContainer = view.findViewById(R.id.subscriber_container) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinMethod.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinMethod.kt new file mode 100644 index 00000000..c76f9784 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinMethod.kt @@ -0,0 +1,13 @@ +package com.cloud.diplomaticquarterapp.penguin + +enum class PenguinMethod { + // initializePenguin("initializePenguin"), + // configurePenguin("configurePenguin"), + // showPenguinUI("showPenguinUI"), + // onPenNavUIDismiss("onPenNavUIDismiss"), + // onReportIssue("onReportIssue"), + // onPenNavSuccess("onPenNavSuccess"), + onPenNavInitializationError // onLocationOffCampus("onLocationOffCampus"), + // navigateToPOI("navigateToPOI"), + // openSharedLocation("openSharedLocation"); +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinNavigator.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinNavigator.kt new file mode 100644 index 00000000..98245cc9 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinNavigator.kt @@ -0,0 +1,97 @@ +package com.cloud.diplomaticquarterapp.penguin + +import android.content.Context +import com.google.gson.Gson +import com.peng.pennavmap.PlugAndPlaySDK +import com.peng.pennavmap.connections.ApiController +import com.peng.pennavmap.interfaces.RefIdDelegate +import com.peng.pennavmap.models.TokenModel +import com.peng.pennavmap.models.postmodels.PostToken +import com.peng.pennavmap.utils.AppSharedData +import okhttp3.ResponseBody +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response +import android.util.Log + + +class PenguinNavigator() { + + fun navigateTo(mContext: Context, refID: String, delegate: RefIdDelegate,clientID : String,clientKey : String ) { + val postToken = PostToken(clientID, clientKey) + getToken(mContext, postToken, object : RefIdDelegate { + override fun onRefByIDSuccess(PoiId: String?) { + Log.e("navigateTo", "PoiId is+++++++ $PoiId") + + PlugAndPlaySDK.navigateTo(mContext, refID, object : RefIdDelegate { + override fun onRefByIDSuccess(PoiId: String?) { + Log.e("navigateTo", "PoiId 2is+++++++ $PoiId") + + delegate.onRefByIDSuccess(refID) + + } + + override fun onGetByRefIDError(error: String?) { + delegate.onRefByIDSuccess(error) + } + + }) + + + } + + override fun onGetByRefIDError(error: String?) { + delegate.onRefByIDSuccess(error) + } + + }) + + } + + fun getToken(mContext: Context, postToken: PostToken?, apiTokenCallBack: RefIdDelegate) { + try { + // Create the API call + val purposesCall: Call = ApiController.getInstance(mContext) + .apiMethods + .getToken(postToken) + + // Enqueue the call for asynchronous execution + purposesCall.enqueue(object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful() && response.body() != null) { + try { + response.body()?.use { responseBody -> + val responseBodyString: String = responseBody.string() // Use `string()` to get the actual response content + if (responseBodyString.isNotEmpty()) { + val tokenModel = Gson().fromJson(responseBodyString, TokenModel::class.java) + if (tokenModel != null && tokenModel.token != null) { + AppSharedData.apiToken = tokenModel.token + apiTokenCallBack.onRefByIDSuccess(tokenModel.token) + } else { + apiTokenCallBack.onGetByRefIDError("Failed to parse token model") + } + } else { + apiTokenCallBack.onGetByRefIDError("Response body is empty") + } + } + } catch (e: Exception) { + apiTokenCallBack.onGetByRefIDError("An error occurred: ${e.message}") + } + } else { + apiTokenCallBack.onGetByRefIDError("Unsuccessful response: " + response.code()) + } + } + + override fun onFailure(call: Call, t: Throwable) { + apiTokenCallBack.onGetByRefIDError(t.message) + } + }) + } catch (error: Exception) { + apiTokenCallBack.onGetByRefIDError("Exception during API call: $error") + } + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinView.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinView.kt new file mode 100644 index 00000000..9f74103f --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/penguin/PenguinView.kt @@ -0,0 +1,321 @@ +package com.cloud.diplomaticquarterapp.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.cloud.diplomaticquarterapp.PermissionManager.PermissionHelper +import com.cloud.diplomaticquarterapp.PermissionManager.PermissionManager +import com.cloud.diplomaticquarterapp.PermissionManager.PermissionResultReceiver +import com.ejada.hmg.MainActivity +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.cloud.diplomaticquarterapp.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.PIReportIssue +/** + * Custom PlatformView for displaying Penguin UI components within a Flutter app. + * Implements `PlatformView` for rendering the view, `MethodChannel.MethodCallHandler` for handling method calls, + * and `PenNavUIDelegate` for handling SDK events. + */ +@RequiresApi(Build.VERSION_CODES.O) +internal class PenguinView( + context: Context, + id: Int, + val creationParams: Map, + messenger: BinaryMessenger, + activity: MainActivity, + val channel: MethodChannel +) : PlatformView, MethodChannel.MethodCallHandler, PenNavUIDelegate { + // 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() + ) + + 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 + } + } + Log.d( + "TAG", + "initPenguin: ${Languages.getLanguageEnum(creationParams["languageCode"] as String)}" + ) + 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) + .build() + + // 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.start(mContext, this) + } + + + /** + * Navigates to the specified reference ID. + * + * @param refID The reference ID to navigate to. + */ + fun navigateTo(refID: String) { + try { + if (refID.isBlank()) { + Log.e("navigateTo", "Invalid refID: The reference ID is blank.") + } +// referenceId = refID + navigator.navigateTo(mContext, refID,object : RefIdDelegate { + override fun onRefByIDSuccess(PoiId: String?) { + Log.e("navigateTo", "PoiId is penguin view+++++++ $PoiId") + +// channelFlutter.invokeMethod( +// PenguinMethod.navigateToPOI.name, +// "navigateTo Success" +// ) + } + + override fun onGetByRefIDError(error: String?) { + Log.e("navigateTo", "error is penguin view+++++++ $error") + +// channelFlutter.invokeMethod( +// PenguinMethod.navigateToPOI.name, +// "navigateTo Failed: Invalid refID" +// ) + } + } , creationParams["clientID"] as String, creationParams["clientKey"] as String ) + + } catch (e: Exception) { + Log.e("navigateTo", "Exception occurred during navigation: ${e.message}", e) +// channelFlutter.invokeMethod( +// PenguinMethod.navigateToPOI.name, +// "Failed: Exception - ${e.message}" +// ) + } + } + + /** + * Called when Penguin UI setup is successful. + * + * @param warningCode Optional warning code received from the SDK. + */ + override fun onPenNavSuccess(warningCode: String?) { + val clinicId = creationParams["clinicID"] as String + + if(clinicId.isEmpty()) return + + navigateTo(clinicId) + } + + /** + * Called when there is an initialization error with Penguin UI. + * + * @param description Description of the error. + * @param errorType Type of initialization error. + */ + override fun onPenNavInitializationError( + description: String?, + errorType: InitializationErrorType? + ) { + val arguments: Map = mapOf( + "description" to description, + "type" to errorType?.name + ) + + 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") + } + } +} + diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/API.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/API.kt new file mode 100644 index 00000000..18338e23 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/API.kt @@ -0,0 +1,11 @@ +package com.ejada.hmg.utils + +class API { + companion object{ + private val BASE = "https://hmgwebservices.com" + private val SERVICE = "Services/Patients.svc/REST" + + val WIFI_CREDENTIALS = "$BASE/$SERVICE/Hmg_SMS_Get_By_ProjectID_And_PatientID" + val LOG_GEOFENCE = "$BASE/$SERVICE/GeoF_InsertPatientFileInfo" + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Constants.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Constants.kt new file mode 100644 index 00000000..86df85f4 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Constants.kt @@ -0,0 +1,8 @@ +package com.ejada.hmg.utils + + +const val PREFS_STORAGE = "FlutterSharedPreferences" +const val PREF_KEY_SUCCESS = "HMG_GEOFENCE_SUCCESS" +const val PREF_KEY_FAILED = "HMG_GEOFENCE_FAILED" +const val PREF_KEY_HMG_ZONES = "flutter.hmg-geo-fences" +const val PREF_KEY_LANGUAGE = "flutter.language" \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/FlutterText.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/FlutterText.kt new file mode 100644 index 00000000..1a650074 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/FlutterText.kt @@ -0,0 +1,36 @@ +package com.ejada.hmg.utils + +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.Result + +class FlutterText{ + + companion object{ + fun with(key:String, completion:(String)->Unit){ + HMGUtils.getPlatformChannel().invokeMethod("localizedValue",key, object:MethodChannel.Result{ + override fun success(result: Any?) { + val localized = result as String? + if (localized != null){ + completion(localized) + }else{ + completion(key) + } + } + + override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + completion(key) + require(false){ + "'localizedValue' $errorMessage" + } + } + + override fun notImplemented() { + require(false){ + "'localizedValue' method not implemented at flutter" + } + } + + }) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMGUtils.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMGUtils.kt new file mode 100644 index 00000000..57da2eb8 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMGUtils.kt @@ -0,0 +1,224 @@ +package com.ejada.hmg.utils + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.job.JobInfo +import android.app.job.JobScheduler +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.Build +import android.widget.Toast +import androidx.annotation.Nullable +import androidx.core.app.NotificationCompat +import androidx.core.app.TaskStackBuilder +import com.ejada.hmg.BuildConfig +import com.ejada.hmg.MainActivity +import com.ejada.hmg.R +import com.ejada.hmg.geofence.GeoZoneModel +import com.github.kittinunf.fuel.core.extensions.jsonBody +import com.github.kittinunf.fuel.httpPost +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import io.flutter.plugin.common.MethodChannel +//import org.jetbrains.anko.doAsyncResult +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.text.SimpleDateFormat +import java.util.* +import kotlin.concurrent.timerTask + + +class HMGUtils { + + companion object{ + private lateinit var platformChannel: MethodChannel + fun getPlatformChannel():MethodChannel{ + return platformChannel + } + fun setPlatformChannel(channel: MethodChannel){ + platformChannel = channel + } + + fun timer(delay: Long, repeat: Boolean, tick: (Timer) -> Unit) : Timer{ + val timer = Timer() + if(repeat) + timer.schedule(timerTask { + tick(timer) + }, delay, delay) + else + timer.schedule(timerTask { + tick(timer) + }, delay) + + return timer + } + + fun popMessage(context: MainActivity, message: String){ + context.runOnUiThread { + Toast.makeText(context, message, Toast.LENGTH_LONG).show() + } + } + + fun popFlutterText(context: MainActivity, key: String){ + context.runOnUiThread { + FlutterText.with(key){ + Toast.makeText(context, it, Toast.LENGTH_LONG).show() + } + } + } + + fun getLanguageCode(context: Context) : Int { + val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + val lang = pref.getString(PREF_KEY_LANGUAGE, "ar") + return if (lang == "ar") 2 else 1 + } + + fun defaultHTTPParams(context: Context) : Map{ + return mapOf( + "ZipCode" to "966", + "VersionID" to 5.8, + "Channel" to 3, + "LanguageID" to getLanguageCode(context), + "IPAdress" to "10.20.10.20", + "generalid" to "Cs2020@2016$2958", + "PatientOutSA" to 0, + "SessionID" to null, + "isDentalAllowedBackend" to false, + "DeviceTypeID" to 2) + } + + + fun scheduleJob(context: Context, pendingIntentClassType:Class, jobId:Int, intervalDuration:String, deadlineMillis:Long = (30 * 1000)) { // default deadline: 30 Seconds + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + val jobScheduler: JobScheduler = context.getSystemService(JobScheduler::class.java) + + val serviceComponent = ComponentName(context, pendingIntentClassType) + val builder = JobInfo.Builder(jobId, serviceComponent) + builder.setPersisted(true) + builder.setBackoffCriteria(30000, JobInfo.BACKOFF_POLICY_LINEAR) + + val intervalMillis = timeToMillis(intervalDuration,"HH:mm:ss") + builder.setMinimumLatency(intervalMillis) // wait at least + builder.setOverrideDeadline((intervalMillis + deadlineMillis)) // maximum delay + if (jobScheduler.schedule(builder.build()) == JobScheduler.RESULT_SUCCESS){ + Logs.save(context,"ScheduleJob", "${pendingIntentClassType.simpleName}: Job scheduled to trigger after duration $intervalDuration >> HH:mm:ss --('MinimumLatency:$intervalMillis Deadline:${(intervalMillis + deadlineMillis)}')--",Logs.STATUS.SUCCESS) + }else{ + Logs.save(context,"ScheduleJob", "${pendingIntentClassType.simpleName}: Failed to scheduled Job",Logs.STATUS.ERROR) + } + + } else { + Logs.save(context,"ScheduleJob", "${pendingIntentClassType.simpleName}: Failed to scheduled Job on VERSION.SDK_INT < ${android.os.Build.VERSION_CODES.M}",Logs.STATUS.ERROR) + } + } + + } + +} + + +private const val NOTIFICATION_CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel" + + +fun timeToMillis(time:String, format:String):Long{ + val sdf = SimpleDateFormat(format, Locale.US) + val millis = sdf.parse(time).time + TimeZone.getDefault().rawOffset + return millis +} + +fun sendNotification(context: Context, title: String, @Nullable subtitle: String?, message: String?) { + val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O + && notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null) { + val name = context.getString(R.string.app_name) + val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, + name, + NotificationManager.IMPORTANCE_DEFAULT) + + notificationManager.createNotificationChannel(channel) + } + + val intent = Intent(context, MainActivity::class.java) + + val stackBuilder = TaskStackBuilder.create(context) + .addParentStack(MainActivity::class.java) + .addNextIntent(intent) + val notificationPendingIntent = stackBuilder.getPendingIntent(getUniqueId(), PendingIntent.FLAG_UPDATE_CURRENT) + + val notification = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.mipmap.ic_launcher_local) + .setContentIntent(notificationPendingIntent) + .setAutoCancel(true) + .setContentTitle(title) + + subtitle.let { notification.setContentText(it) } + message.let { notification.setSubText(it) } + + notificationManager.notify(getUniqueId(), notification.build()) +} + +//------------------------- +// Open Helper Methods +//------------------------- +fun getUniqueId() = ((System.currentTimeMillis() % 10000).toInt()) + +object DateUtils { + @JvmStatic + fun dateTimeNow() : String { + val format = SimpleDateFormat("dd-MMM-yyy hh:mm:ss") + return format.format(Date()) + } +} + +fun isJSONValid(jsonString: String?): Boolean { + try { JSONObject(jsonString) } catch (ex: JSONException) { + try { JSONArray(jsonString) } catch (ex1: JSONException) { + return false + } + } + return true +} + +fun saveLog(context: Context, tag: String, message: String){ + val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + var logs = pref.getString("LOGS", "") + logs += "$tag -> $message \n" + pref.edit().putString("LOGS", logs).apply(); +} + +fun getLogs(context: Context) : String?{ + val pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + return pref.getString("LOGS", "") +} + +class HTTPResponse(data: T){ + final var data:T = data +} + +fun httpPost(url: String, body: Map, onSuccess: (response: HTTPResponse) -> Unit, onError: (error: Exception) -> Unit){ + val gson = Gson() + val type = object : TypeToken() {}.type + val jsonBody = gson.toJson(body) + url.httpPost() + .jsonBody(jsonBody, Charsets.UTF_8) + .timeout(10000) + .header("Content-Type", "application/json") + .header("Allow", "*/*") + .response { request, response, result -> + result.fold({ data -> + val dataString = String(data) + if (isJSONValid(dataString)) { + val responseData = gson.fromJson(dataString, type) + onSuccess(HTTPResponse(responseData)) + } else { + onError(Exception("Invalid response from server (Not a valid JSON)")) + } + }, { + onError(it) + }) + + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMG_Wifi_.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMG_Wifi_.kt new file mode 100644 index 00000000..c01bb96a --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/HMG_Wifi_.kt @@ -0,0 +1,351 @@ +//package com.ejada.hmg.utils +// +//import android.annotation.SuppressLint +//import android.content.Context +//import android.net.ConnectivityManager +//import android.net.Network +//import android.net.NetworkCapabilities +//import android.net.NetworkRequest +//import android.net.wifi.ScanResult +//import android.net.wifi.WifiConfiguration +//import android.net.wifi.WifiManager +//import android.util.Log +//import com.ejada.hmg.utils.API +//import com.ejada.hmg.FlutterMainActivity +//import com.github.kittinunf.fuel.core.extensions.jsonBody +//import com.github.kittinunf.fuel.httpGet +//import com.github.kittinunf.fuel.httpPost +//import org.json.JSONObject +//import java.util.* +// +// +//@SuppressLint("MissingPermission") +//class HMG_Wifi_(flutterMainActivity: FlutterMainActivity) { +// val TAG = "WIFI" +// val TEST = true +// +// var context = flutterMainActivity; +// var completionListener: ((status: Boolean, message: String) -> Unit)? = null +// +// +// private var SSID = "HMG-GUEST" +// private var USER_NAME = "" +// private var PASSWORD = "" +// var NETWORK_ID = -1 // HMG-GUEST Assigned Network ID by Android +// private lateinit var PATIENT_ID:String +// /* +// * Helpful: +// * http://stackoverflow.com/questions/5452940/how-can-i-get-android-wifi-scan-results-into-a-list +// */ +// fun triggerWifiScan(context: Context) { +// val wifi = context.getSystemService(Context.WIFI_SERVICE) as WifiManager +// wifi.startScan() +// } +// +// /* +// * Helpful: +// * http://stackoverflow.com/questions/8818290/how-to-connect-to-a-specific-wifi-network-in-android-programmatically +// */ +// fun connectToWifiNetworkWith(patientId: String): HMG_Wifi_ { +// +// val connectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager +// +// PATIENT_ID = patientId +// +// val security = "OPEN" +// val networkPass = "" +// Log.d(TAG, "Connecting to SSID \"$SSID\" with password \"$networkPass\" and with security \"$security\" ...") +// +// // You need to create WifiConfiguration instance like this: +// val conf = WifiConfiguration() +// conf.SSID = "\"" + SSID + "\"" +// +// if (security == "OPEN") { +// conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) +// } else if (security == "WEP") { +// conf.wepKeys[0] = "\"" + networkPass + "\"" +// conf.wepTxKeyIndex = 0 +// conf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) +// conf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40) +// } else { +// conf.preSharedKey = "\"" + networkPass + "\"" +// } +// +// // Then, you need to add it to Android wifi manager settings: +// val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// +// NETWORK_ID = wifiManager.addNetwork(conf) +// Log.d(TAG, "Network ID: $NETWORK_ID") +// +// //wifiManager.disconnect(); +// val result = wifiManager.enableNetwork(NETWORK_ID, true) +// //wifiManager.reconnect(); +// wifiManager.saveConfiguration() +// +// if(result == true){ +// authNetworkConnection(NETWORK_ID); +// }else{ +// completionListener?.let { it(false, "Error connecting to HMG network") } +// } +// return this +// } +// +// private var authTimer:Timer? = null +// fun authNetworkConnection(networkId: Int){ +// authTimer = Timer() +// authTimer?.scheduleAtFixedRate(object : TimerTask() { +// override fun run() { +// if (connectedNetworkId() == networkId && connectedNetworkIPAddress() > 0) { +// authServerCall() +// authTimer?.cancel() +// } +// } +// +// }, 2000, 1000) +// +// // If wifi not connected in 5 sec terminate with fail status +// Timer().schedule(object : TimerTask() { +// override fun run() { +// if (null != authTimer) { +// authTimer?.cancel() +// completionListener?.let { it(false, "Error connecting to HMG network") } +// } +// } +// }, 5000) +// +// } +// +// fun authServerCall(){ +// +// fun call(){ +// +// forceNetworkCallOverWifi() +// +// val params = listOf("cmd" to "authenticate", "password" to PASSWORD, "user" to USER_NAME) +// val serverUrl = "https://captiveportal-login.hmg.com/cgi-bin/login" +//// val serverUrl = "http://192.168.102.223/cgi-bin/login" +// serverUrl +// .httpPost(params) +// .timeout(10000) +// .response { request, response, result -> +// Log.v(TAG, response.statusCode.toString()) +// +// haveInternet { have -> +// if(have){ +// Log.v(TAG, "Connected to internet via $SSID network at HMG") +// completionListener?.let { it(true, "Successfully connected to the internet") } +// }else{ +// Log.e(TAG, "failed to connect to internet via $SSID network at HMG") +// completionListener?.let { it(false, "Authentication failed or you are already using your credentials on another device") } +// } +// } +// } +// } +// +// haveInternet { has -> +// if (has){ +// getAuthCredentials { +// call() +// } +// }else{ +// completionListener?.let { it(false, "You must have active internet connection to connect with HMG Network") } +// } +// } +// } +// +// fun haveInternet(completion: ((status: Boolean) -> Unit)){ +// if (TEST) +// completion(true) +// +// "https://captive.apple.com".httpGet().response { request, response, result -> +// val have = response.statusCode == 200 && String(response.data).contains("Success", true) +// completion(have) +// } +// } +// +// fun getAuthCredentials(completion: (() -> Unit)){ +// if (TEST){ +// USER_NAME = "2300" +// PASSWORD = "1820" +// completion() +// return +// } +// +// val jsonBody = """{"PatientID":$PATIENT_ID}""" +// API.WIFI_CREDENTIALS +// .httpPost() +// .jsonBody(jsonBody, Charsets.UTF_8) +// .response { request, response, result -> +// val jsonString = String(response.data) +// Log.d(TAG, "JSON $jsonString") +// +// if (response.statusCode == 200){ +// +// val jsonObject = JSONObject(jsonString) +// if(!jsonObject.getString("ErrorMessage").equals("null")){ +// val errorMsg = jsonObject.getString("ErrorMessage") +// completionListener?.let { it(false, errorMsg) } +// +// }else{ +// jsonObject.getJSONArray("Hmg_SMS_Get_By_ProjectID_And_PatientIDList").let { array -> +// array.getJSONObject(0).let { object_ -> +// if (object_.has("UserName") && object_.has("UserName")){ +// USER_NAME = object_.getString("UserName") +// PASSWORD = object_.getString("Password") +// completion() +// }else{ +// completionListener?.let { it(false, "Failed to get your internet credentials") } +// } +// } +// } +// } +// +// }else{ +// completionListener?.let { it(false, "Failed to get your internet credentials") } +// } +// } +// } +// +// fun forceNetworkCallOverWifi(){ +// val connectivityManager = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager +//// val network = Network +//// connectivityManager.activeNetwork +// // Exit app if Network disappears. +// // Exit app if Network disappears. +//// val networkCapabilities: NetworkCapabilities = ConnectivityManager.from(context).getNetworkCapabilities(network) +//// val networkCapabilities: NetworkCapabilities = connectivityManager.getNetworkCapabilities(network) +// +//// if (networkCapabilities == null) { +//// return +//// } +// +// val mNetworkCallback = object : ConnectivityManager.NetworkCallback() { +// override fun onLost(lostNetwork: Network?) { +//// if (network.equals(lostNetwork)){ +//// //GlyphLayout.done(false) +//// } +// } +// } +// val builder: NetworkRequest.Builder = NetworkRequest.Builder() +//// for (transportType in networkCapabilities.getTransportTypes()) { +//// builder.addTransportType(transportType) +//// } +// connectivityManager.registerNetworkCallback(builder.build(), mNetworkCallback) +// } +// +// /* +// * Helpful: +// * http://stackoverflow.com/questions/6517314/android-wifi-connection-programmatically +// */ +// fun getScanResultSecurity(result: ScanResult): String? { +// val capabilities: String = result.capabilities +// val securityModes = arrayOf("WEP", "PSK", "EAP") +// for (securityMode in securityModes) { +// if (capabilities.contains(securityMode)) { +// return securityMode +// } +// } +// return "OPEN" +// } +// +// //connects to the given ssid +// fun connectToWPAWiFi(ssid: String, password: String){ +// +// WifiUtils.withContext(context) +// .connectWith(ssid, "") +// .setTimeout(40000) +// .onConnectionResult(object : ConnectionSuccessListener { +// override fun success() { +// Log.v(TAG,"Success") +// } +// +// override fun failed(@NonNull errorCode: ConnectionErrorCode) { +// Log.v(TAG,"Failed") +// } +// }) +// .start() +// if(isConnectedTo(ssid)){ //see if we are already connected to the given ssid +// return +// } +// +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { +// Log.e(TAG, "connection wifi Q") +// +// val wifiNetworkSpecifier: WifiNetworkSpecifier = WifiNetworkSpecifier.Builder() +// .setSsid(ssid) +// .setWpa2Passphrase(password) +// .build() +// +// val networkRequest: NetworkRequest = NetworkRequest.Builder() +// .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) +// .setNetworkSpecifier(wifiNetworkSpecifier) +// .build() +// +// var connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager +// var networkCallback = object : ConnectivityManager.NetworkCallback() { +// override fun onAvailable(network: Network) { +// super.onAvailable(network) +// connectivityManager.bindProcessToNetwork(network) +// Log.e(TAG, "onAvailable") +// } +// +// override fun onLosing(network: Network, maxMsToLive: Int) { +// super.onLosing(network, maxMsToLive) +// Log.e(TAG, "onLosing") +// } +// +// override fun onLost(network: Network) { +// super.onLost(network) +// Log.e(TAG, "onLosing") +// Log.e(TAG, "losing active connection") +// } +// +// override fun onUnavailable() { +// super.onUnavailable() +// Log.e(TAG, "onUnavailable") +// } +// } +// connectivityManager.requestNetwork(networkRequest, networkCallback) +// +// }else{ +// +// try { +// val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// +// Log.e(TAG, "connection wifi pre Q") +// +// var netId: Int = wm.addNetwork(getWifiConfig(ssid)) +// if (netId == -1) netId = getExistingNetworkId(ssid); +// wm.saveConfiguration() +// if(wm.enableNetwork(netId, true)){ +// Log.v(TAG,"HMG-GUEST Connected") +// }else{ +// Log.v(TAG,"HMG-GUEST failed to connect") +// } +// } catch (e: Exception) { +// e.printStackTrace() +// Log.v(TAG,"HMG-GUEST failed to connect") +// } +// } +// +// } +// +// fun connectedNetworkId():Int{ +// val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// return wm.connectionInfo.networkId +// } +// +// fun connectedNetworkIPAddress():Int{ +// val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// return wm.connectionInfo.ipAddress +// } +// +// fun isConnectedTo(bssid: String):Boolean{ +// val wm:WifiManager= context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager +// if(wm.connectionInfo.bssid == bssid){ +// return true +// } +// return false +// } +// +//} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Logs.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Logs.kt new file mode 100644 index 00000000..d9dc209e --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/Logs.kt @@ -0,0 +1,145 @@ +package com.ejada.hmg.utils + +import android.content.Context +import android.content.SharedPreferences +import android.os.Build +import com.ejada.hmg.BuildConfig +import com.google.gson.Gson + +class Logs { + + enum class STATUS{ + SUCCESS, + ERROR; + } + class GeofenceEvent{ + companion object{ + fun save(context: Context, tag:String, message:String, status:Logs.STATUS = STATUS.SUCCESS){ + Logs.Common.save(context,"GeofenceEvent", tag, message, status) + } + + fun list(context: Context, tag:String? = null, status:Logs.STATUS? = null):List{ + return Logs.Common.list(context,"GeofenceEvent", tag, status) + } + + fun raw(context: Context):String{ + return Logs.Common.raw(context,"GeofenceEvent") + } + } + } + + class RegisterGeofence{ + companion object{ + fun save(context: Context, tag:String, message:String, status:Logs.STATUS = STATUS.SUCCESS){ + Logs.Common.save(context,"RegisterGeofence", tag, message, status) + } + + fun list(context: Context, tag:String? = null, status:Logs.STATUS? = null):List{ + return Logs.Common.list(context,"RegisterGeofence", tag, status) + } + + fun raw(context: Context):String{ + return Logs.Common.raw(context,"RegisterGeofence"); + } + } + } + + + companion object{ + private var pref:SharedPreferences? = null + fun save(context: Context, tag:String, message:String, status:Logs.STATUS = STATUS.SUCCESS){ + Logs.Common.save(context,"Logs", tag, message, status) + } + + fun list(context: Context, tag:String? = null, status:Logs.STATUS? = null):List{ + return Logs.Common.list(context,"Logs", tag, status) + } + + fun raw(context: Context):String{ + return Logs.Common.raw(context,"Logs"); + } + + private fun storage(context: Context):SharedPreferences{ + if(pref == null) { + pref = context.getSharedPreferences(PREFS_STORAGE, Context.MODE_PRIVATE) + } + return pref!! + } + } + + private class Common{ + companion object{ + private val gson = Gson() + + fun save(context: Context, key:String, tag:String, message:String, status:Logs.STATUS = STATUS.SUCCESS){ + if(!BuildConfig.DEBUG) + return + + val pref = Logs.storage(context) + + val string = pref.getString(key,"{}") + val json = gson.fromJson(string,LogsContainerModel::class.java) + json.add( + LogModel().apply { + this.TAG = tag + this.MESSAGE = message + this.STATUS = status.name + this.DATE = DateUtils.dateTimeNow() + } + ) + + pref.edit().putString(key,gson.toJson(json)).apply() + } + + fun list(context: Context, key:String, tag:String? = null, status:Logs.STATUS? = null):List{ + val pref = Logs.storage(context) + val string = pref.getString(key,"{}") + val json = gson.fromJson(string,LogsContainerModel::class.java) + if(tag == null && status == null) { + return json.LOGS + }else if(tag != null && status != null){ + return json.LOGS.filter { (it.TAG == tag && it.STATUS == status.name) } + }else if(tag != null){ + return json.LOGS.filter { (it.TAG == tag) } + }else if(status != null){ + return json.LOGS.filter { (it.STATUS == status.name) } + } + return listOf() + } + + fun raw(context: Context, key:String):String{ + val pref = Logs.storage(context) + val string = pref.getString(key,"{}") + return string!! + } + + } + } + + class LogModel{ + lateinit var TAG:String + lateinit var MESSAGE:String + lateinit var STATUS:String + lateinit var DATE:String + + companion object{ + fun with(tag:String, message:String, status:String):LogModel{ + return LogModel().apply { + this.TAG = tag + this.MESSAGE = message + this.STATUS = status + this.DATE = DateUtils.dateTimeNow() + } + } + } + } + + class LogsContainerModel{ + var LOGS = mutableListOf() + fun add(log:LogModel){ + LOGS.add(log) + } + } + + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/OpenTokPlatformBridge.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/OpenTokPlatformBridge.kt new file mode 100644 index 00000000..ebb04456 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/OpenTokPlatformBridge.kt @@ -0,0 +1,45 @@ +package com.ejada.hmg.utils + +import com.ejada.hmg.MainActivity +import com.ejada.hmg.opentok.OpenTok +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class OpenTokPlatformBridge(private var flutterEngine: FlutterEngine, private var mainActivity: MainActivity) { + + private lateinit var channel: MethodChannel + private lateinit var openTok: OpenTok + + companion object { + private const val CHANNEL = "OpenTok-Platform-Bridge" + } + + fun create(){ + openTok = OpenTok(mainActivity, flutterEngine) + channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result -> + when (call.method) { + "initSession" -> { + openTok.initSession(call, result) + } + "swapCamera" -> { + openTok.swapCamera(call, result) + } + "toggleAudio" -> { + openTok.toggleAudio(call, result) + } + "toggleVideo" -> { + openTok.toggleVideo(call, result) + } + "hangupCall" -> { + openTok.hangupCall(call, result) + } + else -> { + result.notImplemented() + } + } + } + } + +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/PlatformBridge.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/PlatformBridge.kt new file mode 100644 index 00000000..9a66f4b3 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/utils/PlatformBridge.kt @@ -0,0 +1,199 @@ +package com.ejada.hmg.utils + +import android.content.Context +import android.content.Intent +import android.content.Intent.getIntent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.Settings +import android.widget.Toast +import androidx.core.app.ActivityCompat.startActivityForResult +import android.net.wifi.WifiManager +import android.util.Log +import com.ejada.hmg.MainActivity +import com.ejada.hmg.hmgwifi.HMG_Guest +import com.ejada.hmg.geofence.GeoZoneModel +import com.ejada.hmg.geofence.HMG_Geofence +import com.ejada.hmg.hmgwifi.WpaEnterprise +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel + +class PlatformBridge(private var flutterEngine: FlutterEngine, private var mainActivity: MainActivity) { + + private lateinit var channel: MethodChannel + + companion object { + private const val CHANNEL = "HMG-Platform-Bridge" + private const val HMG_INTERNET_WIFI_CONNECT_METHOD = "connectHMGInternetWifi" + private const val HMG_GUEST_WIFI_CONNECT_METHOD = "connectHMGGuestWifi" + private const val ENABLE_WIFI_IF_NOT = "enableWifiIfNot" + private const val REGISTER_HMG_GEOFENCES = "registerHmgGeofences" + private const val UN_REGISTER_HMG_GEOFENCES = "unRegisterHmgGeofences" + private const val IS_DRAW_OVER_APPS_PERMISSION_ALLOWED = "isDrawOverAppsPermissionAllowed" + private const val ASK_DRAW_OVER_APPS_PERMISSION = "askDrawOverAppsPermission" + private const val GET_INTENT = "getIntent" + } + + fun create() { + channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + HMGUtils.setPlatformChannel(channel) + channel.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result -> + + if (methodCall.method == HMG_INTERNET_WIFI_CONNECT_METHOD) { + connectHMGInternetWifi(methodCall, result) + + } else if (methodCall.method == HMG_GUEST_WIFI_CONNECT_METHOD) { + connectHMGGuestWifi(methodCall, result) + + } else if (methodCall.method == ENABLE_WIFI_IF_NOT) { + enableWifiIfNot(methodCall, result) + } else if (methodCall.method == REGISTER_HMG_GEOFENCES) { + registerHmgGeofences(methodCall, result) + } else if (methodCall.method == UN_REGISTER_HMG_GEOFENCES) { + unRegisterHmgGeofences(methodCall, result) + } else if (methodCall.method == IS_DRAW_OVER_APPS_PERMISSION_ALLOWED) { + isDrawOverAppsPermissionAllowed(methodCall, result) + } else if (methodCall.method == ASK_DRAW_OVER_APPS_PERMISSION) { + askDrawOverAppsPermission(methodCall, result) + } else if (methodCall.method == GET_INTENT) { + getIntentData(methodCall, result) + } else { + result.notImplemented() + } + + } + + val res = channel.invokeMethod("localizedValue", "errorConnectingHmgNetwork") + + } + + private fun connectHMGInternetWifi(methodCall: MethodCall, result: MethodChannel.Result) { + (methodCall.arguments as ArrayList<*>).let { + require(it.size == 3 && (it[0] is String) && (it[1] is String), lazyMessage = { + "Missing or invalid arguments (Must have three argument of 'String'" + }) + + val ssid = it[0].toString() + val username = it[1].toString() + val password = it[2].toString() + + WpaEnterprise(mainActivity,ssid).connect(username,password) { status, message -> + HMGUtils.timer(2000,false){ + mainActivity.runOnUiThread { + if(status) + result.success(if (status) 1 else 0) + else + result.error(message, null, null) + } + } + } + +// HMG_Internet(mainActivity) +// .connectToHMGGuestNetwork(username, password) { status, message -> +// mainActivity.runOnUiThread { +// result.success(if (status) 1 else 0) +// +// HMGUtils.popFlutterText(mainActivity, message) +// Log.v(this.javaClass.simpleName, "$status | $message") +// } +// +// } + } + } + + + private fun connectHMGGuestWifi(methodCall: MethodCall, result: MethodChannel.Result) { + (methodCall.arguments as ArrayList<*>).let { + require(it.size == 1 && (it[0] is String), lazyMessage = { + "Missing or invalid arguments (Must have one argument 'String at 0'" + }) + + val ssid = it[0].toString() + HMG_Guest(mainActivity, ssid).connectToHMGGuestNetwork { status, message -> + mainActivity.runOnUiThread { + result.success(if (status) 1 else 0) + + HMGUtils.popFlutterText(mainActivity, message) + Log.v(this.javaClass.simpleName, "$status | $message") + } + } + } + } + + private fun enableWifiIfNot(methodCall: MethodCall, result: MethodChannel.Result) { + val wm = mainActivity.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager? + if (wm != null) { + if (!wm.isWifiEnabled) + wm.isWifiEnabled = true + result.success(true) + } else + result.error("101", "Error while opening wifi, Please try to open wifi yourself and try again", "'WifiManager' service failed"); + } + + + private fun registerHmgGeofences(methodCall: MethodCall, result: MethodChannel.Result) { + + channel.invokeMethod("getGeoZones", null, object : MethodChannel.Result { + override fun success(result: Any?) { + if (result is String) { + val geoZones = GeoZoneModel().listFrom(result) + HMG_Geofence.shared(mainActivity).register() { s, e -> } + } + } + + override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {} + override fun notImplemented() {} + }) + + } + + private fun unRegisterHmgGeofences(methodCall: MethodCall, result: MethodChannel.Result) { + HMG_Geofence.shared(mainActivity).unRegisterAll { status, exception -> + if (status) + result.success(true) + else + result.error("101", exception?.localizedMessage, exception); + } + } + + private fun isDrawOverAppsPermissionAllowed(methodCall: MethodCall, result: MethodChannel.Result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if ( + Settings.canDrawOverlays(mainActivity) + ) { + result.success(true) + } else { + result.success(false) + } + } else { + result.success(false) + } + } + + private fun askDrawOverAppsPermission(methodCall: MethodCall, result: MethodChannel.Result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) + val uri = Uri.parse("package:" + mainActivity.getPackageName()) + intent.setData(uri) + startActivityForResult(mainActivity, intent, 102, null) + result.success(true) + } else { + result.success(false) + } + } + + private fun getIntentData(methodCall: MethodCall, result: MethodChannel.Result) { + + val bundle: Bundle? = getIntent("").extras + if (bundle != null) { + val message = bundle.getString("notification") // 1 + System.out.println("BundleExtra:" + message) + Toast.makeText(this.mainActivity, message + "", Toast.LENGTH_SHORT).show() + } else { + Toast.makeText(this.mainActivity, "Bundle Null", Toast.LENGTH_SHORT).show(); + } + result.success(true); + } +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/AppSignatureRetriever.java b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/AppSignatureRetriever.java new file mode 100644 index 00000000..dc65e990 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/AppSignatureRetriever.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.cloud.hmg_patient_app.whatsapp; + +import static java.sql.DriverManager.println; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.util.Base64; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +public class AppSignatureRetriever { + + private static final String HASH_TYPE = "SHA-256"; + public static final int NUM_HASHED_BYTES = 9; + public static final int NUM_BASE64_CHAR = 11; + + + public void logSignatures(Context context) { + Collection appSignatures = getAppSignatures(context); + appSignatures.forEach(signature -> println("Signature: " + signature)); + } + + /** + * Get all the app signatures for the current package. + * + * @return signatures for current app + */ + public Collection getAppSignatures(Context context) { + try { + // Get all package signatures for the current package + String packageName = context.getPackageName(); + println("Package name: " + packageName); + PackageManager packageManager = context.getPackageManager(); + Signature[] signatures = packageManager.getPackageInfo(packageName, + PackageManager.GET_SIGNATURES).signatures; + + // For each signature create a compatible hash + Collection appCodes = Arrays.stream(signatures) + .map(signature -> hash(packageName, signature.toCharsString())) + .collect(Collectors.toList()); + return appCodes; + } catch (PackageManager.NameNotFoundException e) { + println("Unable to find package to obtain hash."); + throw new RuntimeException("Unable to find package to obtain hash.", e); + } + + } + + private String hash(String packageName, String signature) { + String appInfo = packageName + " " + signature; + try { + MessageDigest messageDigest = MessageDigest.getInstance(HASH_TYPE); + messageDigest.update(appInfo.getBytes(StandardCharsets.UTF_8)); + byte[] hashSignature = messageDigest.digest(); + + // truncated into NUM_HASHED_BYTES + hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES); + // encode into Base64 + String base64Hash = Base64.encodeToString(hashSignature, Base64.NO_PADDING | Base64.NO_WRAP); + base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR); + + println(String.format("pkg: %s -- hash: %s", packageName, base64Hash)); + return base64Hash; + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("Unable to generate hash for application", e); + } + } +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsApp.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsApp.kt new file mode 100644 index 00000000..4c74a02d --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsApp.kt @@ -0,0 +1,31 @@ + +package com.cloud.hmg_patient_app.whatsapp + +import android.content.Context +import android.content.Intent +import com.whatsapp.otp.android.sdk.WhatsAppOtpHandler +import com.whatsapp.otp.android.sdk.WhatsAppOtpIncomingIntentHandler +import java.lang.ref.WeakReference + +object WhatsApp { + val whatsAppOtpHandler = WhatsAppOtpHandler() + inline fun handleOTP ( intent: Intent, crossinline validateOTP:(code: String )-> Unit) = + WhatsAppOtpIncomingIntentHandler().processOtpCode( + intent, + // call your function to validate + {code -> validateOTP(code) }, + {error,exception-> + println("the error is ${error.name}") + println("the exception stacktrace is ${exception.message}") + println("the exception is cause ${exception.cause}") + }) + + + fun performHandShake(context : WeakReference) = whatsAppOtpHandler.sendOtpIntentToWhatsApp(context.get()!!) + + + + fun isWhatsAppInstalled(context : WeakReference) : Boolean = whatsAppOtpHandler.isWhatsAppInstalled(context.get()!!) + + +} diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppCodeActivity.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppCodeActivity.kt new file mode 100644 index 00000000..c1024dd6 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppCodeActivity.kt @@ -0,0 +1,17 @@ +package com.cloud.hmg_patient_app.whatsapp +import android.app.PendingIntent +import android.content.Intent +import android.os.Bundle +import com.cloud.diplomaticquarterapp.whatsapp.WhatsApp +import com.cloud.diplomaticquarterapp.whatsapp.WhatsAppOtpPlatformBridge +import io.flutter.embedding.android.FlutterFragmentActivity + +class WhatsAppCodeActivity : FlutterFragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WhatsApp.handleOTP(intent){code -> + WhatsAppOtpPlatformBridge.result?.success(code); + finish() + } + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppOtpPlatformBridge.kt b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppOtpPlatformBridge.kt new file mode 100644 index 00000000..843b63f2 --- /dev/null +++ b/android/app/src/main/kotlin/com/cloud/hmg_patient_app/whatsapp/WhatsAppOtpPlatformBridge.kt @@ -0,0 +1,48 @@ +package com.cloud.hmg_patient_app.whatsapp + +import com.ejada.hmg.MainActivity +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import java.lang.ref.WeakReference + +class WhatsAppOtpPlatformBridge( + private var flutterEngine: FlutterEngine, + private var mainActivity: MainActivity +) { + + + private lateinit var channel: MethodChannel + + companion object { + private const val CHANNEL = "whats_app_otp" + var result: MethodChannel.Result? = null + } + + fun invoke() { + channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) + channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result -> + when (call.method) { + "isWhatsAppInstalled" -> { + val isAppInstalled = + WhatsApp.isWhatsAppInstalled(WeakReference(mainActivity)) + result.success(isAppInstalled) + } + + "performHandShake" -> { + WhatsApp.performHandShake(WeakReference(mainActivity)) + } + + + "startListening" -> { + WhatsAppOtpPlatformBridge.result = result + } + + else -> { + result.notImplemented() + } + + } + } + } +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index cf1f86a3..1849765a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,7 +1,5 @@ buildscript { - ext.kotlin_version = '1.9.23' - ext.anko_version='0.10.8' - + ext.kotlin_version = '1.8.0' // if (System.properties['os.arch'] == "aarch64") { // // For M1 Users we need to use the NDK 24 which added support for aarch64 @@ -50,7 +48,6 @@ buildscript { } } - allprojects { repositories { google() @@ -80,18 +77,6 @@ allprojects { } } } - subprojects { - afterEvaluate { project -> - if (project.hasProperty('android')) { - project.android { - if (namespace == null) { - namespace project.group - } - } - } - } - } - } rootProject.buildDir = '../build' @@ -102,14 +87,10 @@ subprojects { project.evaluationDependsOn(':app') } - - tasks.register("clean", Delete) { delete rootProject.buildDir } - - configurations.all { resolutionStrategy { force 'androidx.core:core-ktx:1.6.0' diff --git a/assets/images/new-design/passport.svg b/assets/images/new-design/passport.svg index a3a060f0..1e45f593 100644 --- a/assets/images/new-design/passport.svg +++ b/assets/images/new-design/passport.svg @@ -1,134 +1,134 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/new/download_1.svg b/assets/images/new/download_1.svg index 6bf54066..ea1aabff 100644 --- a/assets/images/new/download_1.svg +++ b/assets/images/new/download_1.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/assets/images/new/medical_instructions.svg b/assets/images/new/medical_instructions.svg new file mode 100644 index 00000000..dc060b34 --- /dev/null +++ b/assets/images/new/medical_instructions.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/new/red-star.svg b/assets/images/new/red-star.svg new file mode 100644 index 00000000..12631b05 --- /dev/null +++ b/assets/images/new/red-star.svg @@ -0,0 +1,153 @@ + + + + + + + + + + + image/svg+xml + + + + + Openclipart + + + red star + 2010-12-31T17:15:10 + + https://openclipart.org/detail/102793/red-star-by-worker + + + worker + + + + + red + revolution + socialism + star + + + + + + + + + + + diff --git a/assets/images/new/services/medical_instructions.svg b/assets/images/new/services/medical_instructions.svg new file mode 100644 index 00000000..dc060b34 --- /dev/null +++ b/assets/images/new/services/medical_instructions.svg @@ -0,0 +1,3 @@ + + + diff --git a/ios/Flutter/ephemeral/flutter_lldb_helper.py b/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 00000000..a88caf99 --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/ios/Flutter/ephemeral/flutter_lldbinit b/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 00000000..e3ba6fbe --- /dev/null +++ b/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/ios/Runner/ar.lproj/LaunchScreen.strings b/ios/Runner/ar.lproj/LaunchScreen.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ios/Runner/ar.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/ar.lproj/Main.strings b/ios/Runner/ar.lproj/Main.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ios/Runner/ar.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/ar.lproj/Main_Custom.strings b/ios/Runner/ar.lproj/Main_Custom.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/ios/Runner/ar.lproj/Main_Custom.strings @@ -0,0 +1 @@ + diff --git a/lib/uitl/app_toast.dart b/lib/uitl/app_toast.dart index abcf78ac..8eb68f54 100644 --- a/lib/uitl/app_toast.dart +++ b/lib/uitl/app_toast.dart @@ -70,10 +70,9 @@ class AppToast { child: toast, gravity: ToastGravity.TOP, toastDuration: Duration(seconds: timeInSeconds), - positionedToastBuilder: (context, child, _) { + positionedToastBuilder: (context, child, gravity) { return Positioned(top: 50, left: 10, right: 10, child: child); }); - // Fluttertoast.showToast(msg: message, toastLength: toastLength, gravity: toastGravity, timeInSecForIosWeb: timeInSeconds, backgroundColor: Colors.red, textColor: textColor, fontSize: fontSize); } void cancelToast() { diff --git a/lib/widgets/data_display/medical/LabResult/FlowChartPage.dart b/lib/widgets/data_display/medical/LabResult/FlowChartPage.dart index 8f73b25d..d0a9d621 100644 --- a/lib/widgets/data_display/medical/LabResult/FlowChartPage.dart +++ b/lib/widgets/data_display/medical/LabResult/FlowChartPage.dart @@ -1,13 +1,14 @@ -import 'package:hmg_patient_app/core/enum/viewstate.dart'; import 'package:hmg_patient_app/core/model/labs/patient_lab_orders.dart'; import 'package:hmg_patient_app/core/viewModels/medical/labs_view_model.dart'; import 'package:hmg_patient_app/core/viewModels/project_view_model.dart'; import 'package:hmg_patient_app/pages/base/base_view.dart'; +import 'package:hmg_patient_app/theme/colors.dart'; import 'package:hmg_patient_app/uitl/utils.dart'; import 'package:hmg_patient_app/uitl/utils_new.dart'; -import 'package:hmg_patient_app/widgets/Loader/gif_loader_container.dart'; import 'package:hmg_patient_app/widgets/charts/show_chart.dart'; -import 'package:hmg_patient_app/widgets/data_display/medical/LabResult/lab_result_graph.dart'; +import 'package:hmg_patient_app/widgets/data_display/text.dart'; +import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart';