diff --git a/android/app/build.gradle b/android/app/build.gradle
index 91c15cde..99f2b204 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -86,7 +86,8 @@ dependencies {
// Native Dependency
implementation "org.jetbrains.anko:anko-commons:0.10.4"
implementation 'com.github.kittinunf.fuel:fuel:2.3.0' //for JVM
- implementation 'com.github.kittinunf.fuel:fuel-android:2.3.0' //for Android
+ implementation 'com.github.kittinunf.fuel:fuel-android:2.3.0'
+ implementation 'com.google.android.gms:play-services-location:17.1.0'//for Android
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index b2132a0d..0b8b7998 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -63,8 +63,6 @@
-
@@ -74,20 +72,20 @@
-
-
+
+
+
+
+
-
-
diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeoZoneModel.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeoZoneModel.kt
new file mode 100644
index 00000000..8cf31a76
--- /dev/null
+++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeoZoneModel.kt
@@ -0,0 +1,56 @@
+package com.cloud.diplomaticquarterapp.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){
+
+ return Geofence.Builder()
+ .setRequestId(identifier())
+ .setCircularRegion(
+ lat,
+ long,
+ rad
+ )
+ .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
+// .setNotificationResponsiveness(0)
+ .setExpirationDuration(Geofence.NEVER_EXPIRE)
+ .build()
+ }
+ }
+ return null
+ }
+
+}
diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceBroadcastReceiver.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceBroadcastReceiver.kt
new file mode 100644
index 00000000..e9accb04
--- /dev/null
+++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceBroadcastReceiver.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.cloud.diplomaticquarterapp.geofence
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+
+class GeofenceBroadcastReceiver : 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/diplomaticquarterapp/geofence/GeofenceErrorMessages.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceErrorMessages.kt
new file mode 100755
index 00000000..e85c7ea3
--- /dev/null
+++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceErrorMessages.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.cloud.diplomaticquarterapp.geofence
+
+import android.content.Context
+import com.cloud.diplomaticquarterapp.R
+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
+ return 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)
+
+ else -> resources.getString(R.string.geofence_unknown_error)
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceTransitionsJobIntentService.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceTransitionsJobIntentService.kt
new file mode 100755
index 00000000..0da35b6b
--- /dev/null
+++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/GeofenceTransitionsJobIntentService.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.cloud.diplomaticquarterapp.geofence
+
+import android.content.Context
+import android.content.Intent
+import android.location.Location
+import android.util.Log
+import androidx.core.app.JobIntentService
+import com.cloud.diplomaticquarterapp.utils.API
+import com.github.kittinunf.fuel.core.extensions.jsonBody
+import com.github.kittinunf.fuel.httpPost
+import com.google.android.gms.location.Geofence
+import com.google.android.gms.location.GeofencingEvent
+import com.google.gson.Gson
+
+class GeofenceTransitionsJobIntentService : JobIntentService() {
+
+ companion object {
+ private const val LOG_TAG = "GeoTrIntentService"
+
+ private const val JOB_ID = 573
+
+ fun enqueueWork(context: Context, intent: Intent) {
+ enqueueWork(
+ context,
+ GeofenceTransitionsJobIntentService::class.java, JOB_ID,
+ intent)
+ }
+ }
+
+ override fun onHandleWork(intent: Intent) {
+ val geofencingEvent = GeofencingEvent.fromIntent(intent)
+ if (geofencingEvent.hasError()) {
+ val errorMessage = GeofenceErrorMessages.getErrorString(this, geofencingEvent.errorCode)
+ Log.e(LOG_TAG, errorMessage)
+ return
+ }
+
+ if (geofencingEvent.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofencingEvent.geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
+ handleEvent(geofencingEvent.triggeringGeofences,geofencingEvent.triggeringLocation, GeofenceTransition.fromInt(geofencingEvent.geofenceTransition));
+ }
+ }
+
+ private fun handleEvent(triggerGeofences: List, location:Location, transition:GeofenceTransition) {
+ val hmg = HMG_Geofence.shared(this)
+ if (hmg.getPatientID() != null){
+
+ hmg.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 jsonBody = Gson().toJson(
+ mapOf("PointsID" to pointID.toIntOrNull(),
+ "GeoType" to transition.value,
+ "PatientID" to "")
+ )
+
+ API.LOG_GEOFENCE
+ .httpPost()
+ .jsonBody(jsonBody,Charsets.UTF_8)
+ .timeout(10000)
+ .response { request, response, result ->
+
+ }
+
+ }
+ }
+
+ },null)
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/HMG_Geofence.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/HMG_Geofence.kt
new file mode 100644
index 00000000..11af61a5
--- /dev/null
+++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/geofence/HMG_Geofence.kt
@@ -0,0 +1,143 @@
+package com.cloud.diplomaticquarterapp.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 androidx.core.content.ContextCompat
+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);
+
+ companion object {
+ fun fromInt(value: Int) = GeofenceTransition.values().first { it.value == value }
+ }
+}
+
+const val PREFS_STORAGE = "FlutterSharedPreferences"
+const val PREF_KEY_SUCCESS = "HMG_GEOFENCE_SUCCESS"
+const val PREF_KEY_FAILED = "HMG_GEOFENCE_FAILED"
+
+class HMG_Geofence {
+
+ private lateinit var context: Context
+ private lateinit var preferences:SharedPreferences
+
+ 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_UPDATE_CURRENT)
+ }
+
+ 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!!
+ }
+ }
+
+ fun register(geoZones: List){
+
+ fun buildGeofencingRequest(geofences: List): GeofencingRequest {
+ return GeofencingRequest.Builder()
+ .setInitialTrigger(0)
+ .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 {
+ saveGeofenceResults(geofences.map { it.requestId }, listOf())
+ }
+ .addOnFailureListener {
+ print(it.localizedMessage)
+ }
+ }
+ },null);
+ }
+
+ fun unRegisterAll(completion: (status: Boolean, message: String?) -> Unit){
+ getActiveGeofences({ success ->
+ geofencingClient
+ .removeGeofences(success)
+ .addOnSuccessListener {
+ completion(true, null)
+ }
+ .addOnFailureListener {
+ completion(false, it.localizedMessage)
+ }
+ }, { failed ->
+ // Nothing to do with failed geofences.
+ })
+ }
+
+ fun saveGeofenceResults(success: List, failed: List){
+ val gson = Gson()
+ 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 getActiveGeofences(success: (success: List) -> Unit, failure: ((failed: List) -> Unit)?){
+ val gson = Gson()
+ 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?{
+ val profileJson = preferences.getString("flutter.user-profile", "{}")
+ val type = object : TypeToken