From 67f4b4e167aeb0a02188fb5d6d4c9b448bd90161 Mon Sep 17 00:00:00 2001 From: Zohaib Kambrani Date: Mon, 14 Dec 2020 14:11:47 +0300 Subject: [PATCH] iOS Geofence in progress --- ios/Flutter/.last_build_id | 2 +- ios/Runner/AppDelegate.swift | 33 ++++- ios/Runner/Controllers/MainFlutterVC.swift | 44 +++--- ios/Runner/Helper/Extensions.swift | 4 + ios/Runner/Helper/GlobalHelper.swift | 2 + ios/Runner/Helper/HMGPlatformBridge.swift | 128 ++++++++++++++++++ ios/Runner/Helper/HMG_Geofence.swift | 116 ++++++++++------ ios/Runner/Info.plist | 22 +-- .../requests/GeoZonesRequestModel.dart | 3 +- lib/core/service/client/base_app_client.dart | 21 +++ .../geofencing/GeofencingServices.dart | 10 ++ lib/uitl/PlatformBridge.dart | 15 +- 12 files changed, 318 insertions(+), 82 deletions(-) create mode 100644 ios/Runner/Helper/HMGPlatformBridge.swift diff --git a/ios/Flutter/.last_build_id b/ios/Flutter/.last_build_id index 3aa2cd1e..9694297e 100644 --- a/ios/Flutter/.last_build_id +++ b/ios/Flutter/.last_build_id @@ -1 +1 @@ -3f3d14a0ae775b56806906c2cb14a1f0 \ No newline at end of file +067d482a9455eae7d109c3ac5a36de46 \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 1b784ba3..793c05d7 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,16 +2,47 @@ import UIKit import Flutter import GoogleMaps + @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { + let locationManager = CLLocationManager() + override func application( _ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { +// initLocationManager() + GMSServices.provideAPIKey("AIzaSyCiiJiHkocPbcziHt9O8rGWavDrxHRQys8") GeneratedPluginRegistrant.register(with: self) + if let mainViewController = window.rootViewController as? MainFlutterVC{ + HMGPlatformBridge.initialize(flutterViewController: mainViewController) + } + + HMG_Geofence.initGeofencing() if let _ = launchOptions?[.location] { - HMG_Geofence().wakeup() + } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } + +extension AppDelegate: CLLocationManagerDelegate { + + func initLocationManager(){ + locationManager.allowsBackgroundLocationUpdates = true + locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters + locationManager.activityType = .other + locationManager.delegate = self + locationManager.requestAlwaysAuthorization() + } + + func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { + if region is CLCircularRegion { + } + } + + func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { + if region is CLCircularRegion { + } + } +} diff --git a/ios/Runner/Controllers/MainFlutterVC.swift b/ios/Runner/Controllers/MainFlutterVC.swift index 9470e472..4f91d052 100644 --- a/ios/Runner/Controllers/MainFlutterVC.swift +++ b/ios/Runner/Controllers/MainFlutterVC.swift @@ -10,34 +10,32 @@ import Flutter import NetworkExtension import SystemConfiguration.CaptiveNetwork -var flutterMethodChannel:FlutterMethodChannel? = nil class MainFlutterVC: FlutterViewController { override func viewDidLoad() { super.viewDidLoad() - flutterMethodChannel = FlutterMethodChannel(name: "HMG-Platform-Bridge",binaryMessenger: binaryMessenger) - flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in - - if methodCall.method == "connectHMGInternetWifi"{ - self.connectHMGInternetWifi(methodCall:methodCall, result: result) - - }else if methodCall.method == "connectHMGGuestWifi"{ - self.connectHMGGuestWifi(methodCall:methodCall, result: result) - - }else if methodCall.method == "isHMGNetworkAvailable"{ - self.isHMGNetworkAvailable(methodCall:methodCall, result: result) - - }else if methodCall.method == "registerHmgGeofences"{ - self.registerHmgGeofences(result: result) - } - - print("") - } - - FlutterText.with(key: "errorConnectingHmgNetwork") { (localized) in - print(localized) - } +// flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in +// +// if methodCall.method == "connectHMGInternetWifi"{ +// self.connectHMGInternetWifi(methodCall:methodCall, result: result) +// +// }else if methodCall.method == "connectHMGGuestWifi"{ +// self.connectHMGGuestWifi(methodCall:methodCall, result: result) +// +// }else if methodCall.method == "isHMGNetworkAvailable"{ +// self.isHMGNetworkAvailable(methodCall:methodCall, result: result) +// +// }else if methodCall.method == "registerHmgGeofences"{ +// self.registerHmgGeofences(result: result) +// } +// +// print("") +// } +// +// FlutterText.with(key: "errorConnectingHmgNetwork") { (localized) in +// print(localized) +// } } diff --git a/ios/Runner/Helper/Extensions.swift b/ios/Runner/Helper/Extensions.swift index 6da82ce3..f65b2fa3 100644 --- a/ios/Runner/Helper/Extensions.swift +++ b/ios/Runner/Helper/Extensions.swift @@ -12,6 +12,10 @@ extension String{ func toUrl() -> URL?{ return URL(string: self) } + + func removeSpace() -> String?{ + return self.replacingOccurrences(of: " ", with: "") + } } extension Bundle { diff --git a/ios/Runner/Helper/GlobalHelper.swift b/ios/Runner/Helper/GlobalHelper.swift index 1c5b3916..0e767a96 100644 --- a/ios/Runner/Helper/GlobalHelper.swift +++ b/ios/Runner/Helper/GlobalHelper.swift @@ -27,6 +27,8 @@ func httpPostRequest(urlString:String, jsonBody:[String:Any], completion:((Bool, // create post request let url = URL(string: urlString)! var request = URLRequest(url: url) + request.addValue("application/json", forHTTPHeaderField: "Content-Type") + request.addValue("*/*", forHTTPHeaderField: "Accept") request.httpMethod = "POST" request.httpBody = jsonData diff --git a/ios/Runner/Helper/HMGPlatformBridge.swift b/ios/Runner/Helper/HMGPlatformBridge.swift new file mode 100644 index 00000000..1e8ee735 --- /dev/null +++ b/ios/Runner/Helper/HMGPlatformBridge.swift @@ -0,0 +1,128 @@ +// +// HMGPlatformBridge.swift +// Runner +// +// Created by ZiKambrani on 14/12/2020. +// + +import UIKit +import NetworkExtension +import SystemConfiguration.CaptiveNetwork + +var flutterMethodChannel:FlutterMethodChannel? = nil +var mainViewController:MainFlutterVC! + +class HMGPlatformBridge{ + private let channelName = "HMG-Platform-Bridge" + private static var shared_:HMGPlatformBridge? + + class func initialize(flutterViewController:MainFlutterVC){ + shared_ = HMGPlatformBridge() + mainViewController = flutterViewController + shared_?.openChannel() + } + + func shared() -> HMGPlatformBridge{ + assert((HMGPlatformBridge.shared_ != nil), "HMGPlatformBridge is not initialized, call initialize(mainViewController:MainFlutterVC) function first.") + return HMGPlatformBridge.shared_! + } + + private func openChannel(){ + flutterMethodChannel = FlutterMethodChannel(name: channelName, binaryMessenger: mainViewController.binaryMessenger) + flutterMethodChannel?.setMethodCallHandler { (methodCall, result) in + print("Called function \(methodCall.method)") + if methodCall.method == "connectHMGInternetWifi"{ + self.connectHMGInternetWifi(methodCall:methodCall, result: result) + + }else if methodCall.method == "connectHMGGuestWifi"{ + self.connectHMGGuestWifi(methodCall:methodCall, result: result) + + }else if methodCall.method == "isHMGNetworkAvailable"{ + self.isHMGNetworkAvailable(methodCall:methodCall, result: result) + + }else if methodCall.method == "registerHmgGeofences"{ + self.registerHmgGeofences(result: result) + } + + print("") + } + } + + + + // Connect HMG Wifi and Internet + func connectHMGInternetWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){ + + guard let pateintId = (methodCall.arguments as? [Any])?.first as? String + else { return assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") } + + + HMG_Internet.shared.connect(patientId: pateintId) { (status, message) in + result(status ? 1 : 0) + if status{ + self.showMessage(title:"Congratulations", message:message) + }else{ + self.showMessage(title:"Ooops,", message:message) + } + } + } + + // Connect HMG-Guest for App Access + func connectHMGGuestWifi(methodCall:FlutterMethodCall ,result: @escaping FlutterResult){ + HMG_GUEST.shared.connect() { (status, message) in + result(status ? 1 : 0) + if status{ + self.showMessage(title:"Congratulations", message:message) + }else{ + self.showMessage(title:"Ooops,", message:message) + } + } + } + + func isHMGNetworkAvailable(methodCall:FlutterMethodCall ,result: @escaping FlutterResult) -> Bool{ + guard let ssid = methodCall.arguments as? String else { + assert(true, "Missing or invalid arguments (Must have one argument 'String at 0'") + return false + } + + let queue = DispatchQueue.init(label: "com.hmg.wifilist") + NEHotspotHelper.register(options: nil, queue: queue) { (command) in + print(command) + + if(command.commandType == NEHotspotHelperCommandType.filterScanList) { + if let networkList = command.networkList{ + for network in networkList{ + print(network.ssid) + } + } + } + } + return false + + } + + + // Message Dailog + func showMessage(title:String, message:String){ + DispatchQueue.main.async { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert ) + alert.addAction(UIAlertAction(title: "OK", style: .destructive, handler: nil)) + mainViewController.present(alert, animated: true) { + + } + } + } + + // Register Geofence + func registerHmgGeofences(result: @escaping FlutterResult){ + flutterMethodChannel?.invokeMethod("getGeoZones", arguments: nil){ geoFencesJsonString in + if let jsonString = geoFencesJsonString as? String{ + let allZones = GeoZoneModel.list(from: jsonString) + HMG_Geofence.shared().register(geoZones: allZones) + + }else{ + } + } + } + +} diff --git a/ios/Runner/Helper/HMG_Geofence.swift b/ios/Runner/Helper/HMG_Geofence.swift index 20c05333..7f20d489 100644 --- a/ios/Runner/Helper/HMG_Geofence.swift +++ b/ios/Runner/Helper/HMG_Geofence.swift @@ -14,41 +14,58 @@ fileprivate var transition = "" enum Transition:Int { case entry = 1 case exit = 2 + func name() -> String{ + return self.rawValue == 1 ? "Enter" : "Exit" + } } class HMG_Geofence:NSObject{ + var geoZones:[GeoZoneModel]? - var locationManager = CLLocationManager() + var locationManager:CLLocationManager!{ + didSet{ + // https://developer.apple.com/documentation/corelocation/cllocationmanager/1423531-startmonitoringsignificantlocati + + locationManager.allowsBackgroundLocationUpdates = true + locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters + locationManager.activityType = .other + locationManager.delegate = self + locationManager.requestAlwaysAuthorization() + // locationManager.distanceFilter = 500 + // locationManager.startMonitoringSignificantLocationChanges() + } + } - func initLocationManager(){ - locationManager.delegate = self - locationManager.allowsBackgroundLocationUpdates = true - locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters - locationManager.activityType = .other - locationManager.requestAlwaysAuthorization() + private static var shared_:HMG_Geofence? + class func shared() -> HMG_Geofence{ + assert(shared_ != nil, "HMG_Geofence is not initialized, call initGeofencing() function first.") + return shared_! + } + + class func initGeofencing(){ + shared_ = HMG_Geofence() + shared_?.locationManager = CLLocationManager() } func register(geoZones:[GeoZoneModel]){ self.geoZones = geoZones - self.geoZones?.forEach({ (zone) in - startMonitoring(zone: zone) - }) + // self.geoZones?.forEach({ (zone) in + // startMonitoring(zone: zone) + // }) + let z = geoZones[14] + if monitoredRegions().filter { $0.identifier == z.identifier()}.isEmpty{ + startMonitoring(zone: z) + } } - func wakeup(){ - initLocationManager() - } - func monitoredRegions() -> Set{ return locationManager.monitoredRegions } } - - // CLLocationManager Delegates extension HMG_Geofence : CLLocationManagerDelegate{ @@ -68,21 +85,34 @@ extension HMG_Geofence : CLLocationManagerDelegate{ if let fenceRegion = region(with: zone){ locationManager.startMonitoring(for: fenceRegion) locationManager.requestState(for: fenceRegion) + debugPrint("Monitering region: \(fenceRegion.center) | \(fenceRegion.identifier)") + }else{ + debugPrint("Invalid region: \(zone.latitude ?? ""),\(zone.longitude ?? ""),r\(zone.radius ?? 0) | \(zone.identifier())") } } func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) { + debugPrint("didEnterRegion: \(region)") if region is CLCircularRegion { handleEvent(for: region,transition: .entry, location: manager.location) } } func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) { + debugPrint("didExitRegion: \(region)") if region is CLCircularRegion { handleEvent(for: region,transition: .exit, location: manager.location) } } + func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) { + debugPrint("didDetermineState: \(state)") + } + + func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { + debugPrint("didUpdateLocations: \(locations)") + } + } @@ -91,15 +121,15 @@ extension HMG_Geofence{ func handleEvent(for region: CLRegion!, transition:Transition, location:CLLocation?) { if let zone = geoZone(by: region.identifier){ - notifyUser(forZone: zone, transiotion: transition, location: locationManager.location) - notifyServer(forZone: zone, transiotion: transition, location: locationManager.location) + notifyUser(forZone: zone, transition: transition, location: locationManager.location) + notifyServer(forZone: zone, transition: transition, location: locationManager.location) } } func region(with geoZone: GeoZoneModel) -> CLCircularRegion? { if !geoZone.identifier().isEmpty, - let radius = geoZone.radius, let lat = geoZone.latitude, let long = geoZone.longitude, + let radius = geoZone.radius, let lat = geoZone.latitude?.removeSpace(), let long = geoZone.longitude?.removeSpace(), let radius_d = Double("\(radius)"), let lat_d = Double(lat), let long_d = Double(long){ let coordinate = CLLocationCoordinate2D(latitude: lat_d, longitude: long_d) @@ -119,32 +149,36 @@ extension HMG_Geofence{ } - func notifyUser(forZone:GeoZoneModel, transiotion:Transition, location:CLLocation?){ - + func notifyUser(forZone:GeoZoneModel, transition:Transition, location:CLLocation?){ + if UIApplication.shared.applicationState == .active { + mainViewController.showAlert(withTitle: transition.name(), message: forZone.identifier()) + } else { + let notificationContent = UNMutableNotificationContent() + notificationContent.title = transition.name() + notificationContent.body = forZone.identifier() + notificationContent.sound = UNNotificationSound.default + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false) + let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSinceNow)", + content: notificationContent, + trigger: trigger) + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("Error: \(error)") + } + } + } } - func notifyServer(forZone:GeoZoneModel, transiotion:Transition, location:CLLocation?){ + func notifyServer(forZone:GeoZoneModel, transition:Transition, location:CLLocation?){ flutterMethodChannel?.invokeMethod("getLogGeofenceFullUrl", arguments: nil){ fullUrlString in if let url = fullUrlString as? String{ - let body:[String : Any?] = [ - "PointsID":forZone.geofenceId, - "GeoType":transiotion.rawValue, - "PatientID":"1231755", - "ZipCode": "966", - "VersionID": 5.6, - "Channel": 3, - "LanguageID": UserDefaults.standard.string(forKey: "language") ?? "ar", - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016$2958", - "PatientOutSA": 0, - "isDentalAllowedBackend": false, - "TokenID": "27v/qqXC/UGS2bgJfRBHYw==", - "DeviceTypeID": 2 - ] - httpPostRequest(urlString: url, jsonBody: body){ (status,json) in - if let json_ = json , status{ - - }else{ + flutterMethodChannel?.invokeMethod("getDefaultHttpParameters", arguments: nil){ params in + if let body = params as? [String : Any]{ + httpPostRequest(urlString: url, jsonBody: body){ (status,json) in + if let json_ = json , status{ + }else{ + } + } } } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index fbf54435..ae084167 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,10 +2,6 @@ - NSCalendarsUsageDescription - We need access to record you event in to calender. - NSLocationAlwaysUsageDescription - This app will use your location to show cool stuffs near you. CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable @@ -26,10 +22,14 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSCalendarsUsageDescription + We need access to record you event in to calender. NSCameraUsageDescription Need camera access for uploading images NSLocationAlwaysAndWhenInUseUsageDescription This app will use your location to show cool stuffs near you. + NSLocationAlwaysUsageDescription + This app will use your location to show cool stuffs near you. NSLocationUsageDescription Need location access for updating nearby friends NSLocationWhenInUseUsageDescription @@ -40,12 +40,20 @@ Need photo library access for uploading images UIBackgroundModes + fetch location + processing UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main + UIRequiredDeviceCapabilities + + location-services + gps + armv7 + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -63,11 +71,5 @@ io.flutter.embedded_views_preview - UIRequiredDeviceCapabilities - - location-services - gps - armv7 - diff --git a/lib/core/model/geofencing/requests/GeoZonesRequestModel.dart b/lib/core/model/geofencing/requests/GeoZonesRequestModel.dart index e63a0fb2..219c46ae 100644 --- a/lib/core/model/geofencing/requests/GeoZonesRequestModel.dart +++ b/lib/core/model/geofencing/requests/GeoZonesRequestModel.dart @@ -4,7 +4,8 @@ class GeoZonesRequestModel { GeoZonesRequestModel({this.PatientID}); Map toFlatMap() { - if() + if (PatientID == null) return {}; + return {"PatientID": PatientID.toString()}; } } diff --git a/lib/core/service/client/base_app_client.dart b/lib/core/service/client/base_app_client.dart index 5b7b0901..54be167f 100644 --- a/lib/core/service/client/base_app_client.dart +++ b/lib/core/service/client/base_app_client.dart @@ -210,4 +210,25 @@ class BaseAppClient { ///return id.replaceAll(RegExp('/[^\w\s]/'), ''); // return id.replaceAll(RegExp('/[^a-zA-Z ]'), ''); } + + static defaultHttpParameters() async { + String token = await sharedPref.getString(TOKEN); + var languageID = await sharedPref.getStringWithDefaultValue(APP_LANGUAGE, 'ar'); + var user = await sharedPref.getObject(USER_PROFILE); + var params = {}; + if (user != null) { + params['TokenID'] = token; + params['PatientID'] = user['PatientID']; + params['PatientOutSA'] = user['OutSA']; + params['SessionID'] = SESSION_ID; //getSessionId(token); + } + + params['IPAdress'] = IP_ADDRESS; + params['generalid'] = GENERAL_ID; + params['VersionID'] = VERSION_ID; + params['Channel'] = CHANNEL; + params['LanguageID'] = languageID == 'ar' ? 1 : 2; + + return params; + } } diff --git a/lib/core/service/geofencing/GeofencingServices.dart b/lib/core/service/geofencing/GeofencingServices.dart index 1b39c778..f3c2081f 100644 --- a/lib/core/service/geofencing/GeofencingServices.dart +++ b/lib/core/service/geofencing/GeofencingServices.dart @@ -10,11 +10,13 @@ import 'package:diplomaticquarterapp/core/model/geofencing/responses/LogGeoZoneR import 'package:diplomaticquarterapp/core/service/base_service.dart'; import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import '../../../locator.dart'; class GeofencingServices extends BaseService { List geoZones = List(); + bool testZones = true; Future> getAllGeoZones(GeoZonesRequestModel request) async { hasError = false; @@ -24,6 +26,8 @@ class GeofencingServices extends BaseService { geoZones.add(GeoZonesResponseModel().fromJson(json)); }); + if (kDebugMode || testZones) addTestingGeoZones(zones); + var zonesJsonString = json.encode(zones); AppSharedPreferences().setString(HMG_GEOFENCES, zonesJsonString); debugPrint("GEO ZONES saved to AppPreferences with key '$HMG_GEOFENCES'"); @@ -45,4 +49,10 @@ class GeofencingServices extends BaseService { }, body: request.toFlatMap()); return logResponse; } + + addTestingGeoZones(List zones) { + zones.add({"GEOF_ID": 100, "zkH": "", "Latitude": "24.691136", "Longitude": "46.650116", "Radius": 100, "Type": 1}); + zones.add({"GEOF_ID": 101, "csO": "", "Latitude": "24.7087913", "Longitude": "46.6656461", "Radius": 100, "Type": 1}); + zones.add({"GEOF_ID": 102, "msH": "", "Latitude": "24.777577", "Longitude": "46.652675", "Radius": 100, "Type": 1}); + } } diff --git a/lib/uitl/PlatformBridge.dart b/lib/uitl/PlatformBridge.dart index 65e924c5..ca410435 100644 --- a/lib/uitl/PlatformBridge.dart +++ b/lib/uitl/PlatformBridge.dart @@ -37,14 +37,15 @@ class PlatformBridge { String key = methodCall.arguments.toString(); return localizedValue(key); - case 'getGeofencePreferenceKey': - return getGeofencePreferenceKey(); + case 'getGeoZones': + return getGeoZones(); case 'getLogGeofenceFullUrl': return getLogGeofenceFullUrl(); - case 'test': - return 123.0; + case 'getDefaultHttpParameters': + return getDefaultHttpParameters(); + default: throw MissingPluginException('notImplemented'); } @@ -62,7 +63,7 @@ class PlatformBridge { return (localized != null || (localized is String)) ? localized : forKey; } - static Future getGeofencePreferenceKey() async { + static Future getGeoZones() async { var res = await sharedPref.getStringWithDefaultValue(HMG_GEOFENCES, "[]"); return res; } @@ -72,6 +73,10 @@ class PlatformBridge { return res; } + static Future getDefaultHttpParameters() async { + return BaseAppClient.defaultHttpParameters(); + } + //--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--// //--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//--//