Manos Chorianopoulos

Add webview Location Permissions

...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 <key>Pods-SwiftWarplyFramework.xcscheme_^#shared#^_</key> 7 <key>Pods-SwiftWarplyFramework.xcscheme_^#shared#^_</key>
8 <dict> 8 <dict>
9 <key>orderHint</key> 9 <key>orderHint</key>
10 - <integer>0</integer> 10 + <integer>1</integer>
11 </dict> 11 </dict>
12 </dict> 12 </dict>
13 </dict> 13 </dict>
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
7 <key>SwiftWarplyFramework.xcscheme_^#shared#^_</key> 7 <key>SwiftWarplyFramework.xcscheme_^#shared#^_</key>
8 <dict> 8 <dict>
9 <key>orderHint</key> 9 <key>orderHint</key>
10 - <integer>1</integer> 10 + <integer>0</integer>
11 </dict> 11 </dict>
12 </dict> 12 </dict>
13 </dict> 13 </dict>
......
...@@ -10,15 +10,73 @@ import UIKit ...@@ -10,15 +10,73 @@ import UIKit
10 import WebKit 10 import WebKit
11 import SwiftEventBus 11 import SwiftEventBus
12 12
13 +// Location Permissions
14 +import CoreLocation
15 +
16 +
13 var timer2: DispatchSourceTimer? 17 var timer2: DispatchSourceTimer?
14 18
15 -@objc public class CampaignViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler { 19 +//@objc public class CampaignViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
20 +@objc public class CampaignViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, CLLocationManagerDelegate, WKUIDelegate, UIScrollViewDelegate {
16 @IBOutlet weak var webview: WKWebView! 21 @IBOutlet weak var webview: WKWebView!
17 22
18 public var campaignUrl: String = "" 23 public var campaignUrl: String = ""
19 public var params: String = "" 24 public var params: String = ""
20 public var showHeader: Bool = false 25 public var showHeader: Bool = false
21 26
27 + // Location Permissions
28 + var webView: WKWebView!
29 + var locationManager: CLLocationManager!
30 + var listenersCount = 0;
31 +
32 + public override func loadView() {
33 + super.loadView();
34 +
35 + locationManager = CLLocationManager()
36 + locationManager.delegate = self
37 + locationManager.desiredAccuracy = kCLLocationAccuracyBest
38 +
39 + let contentController = WKUserContentController()
40 + contentController.add(self, name: "Cosmote")
41 + contentController.add(self, name: "listenerAdded");
42 + contentController.add(self, name: "listenerRemoved");
43 +
44 + let config = WKWebViewConfiguration()
45 + config.userContentController = contentController
46 +
47 + let script = WKUserScript(source: getJavaScripToEvaluate(), injectionTime: .atDocumentEnd, forMainFrameOnly: true)
48 + contentController.addUserScript(script)
49 +
50 + self.webView = WKWebView(frame: self.view.bounds, configuration: config)
51 +
52 + webView?.uiDelegate = self
53 + webView?.navigationDelegate = self
54 + webView?.scrollView.delegate = self
55 + webView?.scrollView.bounces = false
56 + webView?.scrollView.bouncesZoom = false
57 + view.addSubview(webView!)
58 +
59 + }
60 +
61 + public override func viewDidLoad() {
62 + super.viewDidLoad()
63 +
64 + let campaignUrl = campaignUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
65 + print("Webview url: " + (campaignUrl ?? ""))
66 +
67 + if let url = URL(string: campaignUrl ?? "") {
68 + webView.load(URLRequest(url: url))
69 + webView.allowsBackForwardNavigationGestures = true
70 + }
71 +
72 + self.hidesBottomBarWhenPushed = true
73 +
74 + if (showHeader) {
75 + setBackButton()
76 + }
77 +
78 + }
79 +
22 public override func viewWillAppear(_ animated: Bool) { 80 public override func viewWillAppear(_ animated: Bool) {
23 super.viewWillAppear(animated) 81 super.viewWillAppear(animated)
24 if (!showHeader) { 82 if (!showHeader) {
...@@ -61,26 +119,27 @@ var timer2: DispatchSourceTimer? ...@@ -61,26 +119,27 @@ var timer2: DispatchSourceTimer?
61 } 119 }
62 } 120 }
63 121
64 - public override func viewDidLoad() { 122 +// public override func viewDidLoad() {
65 - super.viewDidLoad() 123 +// super.viewDidLoad()
66 - 124 +//
67 - var campaignUrl = campaignUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) 125 +// var campaignUrl = campaignUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
68 - print("Webview url: " + (campaignUrl ?? "")) 126 +// print("Webview url: " + (campaignUrl ?? ""))
69 - 127 +//
70 - self.hidesBottomBarWhenPushed = true 128 +// self.hidesBottomBarWhenPushed = true
71 - 129 +//
72 - if (showHeader) { 130 +// if (showHeader) {
73 - setBackButton() 131 +// setBackButton()
74 - } 132 +// }
75 - 133 +//
76 - webview.navigationDelegate = self 134 +// webview.navigationDelegate = self
77 - if let url = URL(string: campaignUrl ?? "") { 135 +//
78 - webview.load(URLRequest(url: url)) 136 +// if let url = URL(string: campaignUrl ?? "") {
79 - webview.allowsBackForwardNavigationGestures = true 137 +// webview.load(URLRequest(url: url))
80 - } 138 +// webview.allowsBackForwardNavigationGestures = true
81 - 139 +// }
82 - webview.configuration.userContentController.add(self, name: "Cosmote") 140 +//
83 - } 141 +// webview.configuration.userContentController.add(self, name: "Cosmote")
142 +// }
84 143
85 // MARK: - Functions 144 // MARK: - Functions
86 func startTimer() { 145 func startTimer() {
...@@ -122,6 +181,149 @@ var timer2: DispatchSourceTimer? ...@@ -122,6 +181,149 @@ var timer2: DispatchSourceTimer?
122 timer2 = nil 181 timer2 = nil
123 } 182 }
124 183
184 + // Location Permissions
185 + func locationServicesIsEnabled() -> Bool {
186 + return (CLLocationManager.locationServicesEnabled()) ? true : false;
187 + }
188 +
189 + func authorizationStatusNeedRequest(status: CLAuthorizationStatus) -> Bool {
190 + return (status == .notDetermined) ? true : false;
191 + }
192 +
193 + func authorizationStatusIsGranted(status: CLAuthorizationStatus) -> Bool {
194 + return (status == .authorizedAlways || status == .authorizedWhenInUse) ? true : false;
195 + }
196 +
197 + func authorizationStatusIsDenied(status: CLAuthorizationStatus) -> Bool {
198 + return (status == .restricted || status == .denied) ? true : false;
199 + }
200 +
201 + func onLocationServicesIsDisabled() {
202 +// webView.evaluateJavaScript("navigator.geolocation.helper.error(2, 'Location services disabled');");
203 + locationManager.requestWhenInUseAuthorization();
204 + }
205 +
206 + func onAuthorizationStatusNeedRequest() {
207 + locationManager.requestWhenInUseAuthorization();
208 + }
209 +
210 + func onAuthorizationStatusIsGranted() {
211 + locationManager.startUpdatingLocation();
212 + }
213 +
214 + func onAuthorizationStatusIsDenied() {
215 + webView.evaluateJavaScript("navigator.geolocation.helper.error(1, 'App does not have location permission');");
216 + }
217 +
218 + public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
219 + print("=== locationManager didChangeAuthorization: ",status)
220 +
221 + // didChangeAuthorization is also called at app startup, so this condition checks listeners
222 + // count before doing anything otherwise app will start location service without reason
223 + if (listenersCount > 0) {
224 + if (authorizationStatusIsDenied(status: status)) {
225 + onAuthorizationStatusIsDenied();
226 + }
227 + else if (authorizationStatusIsGranted(status: status)) {
228 + onAuthorizationStatusIsGranted();
229 + }
230 + }
231 + }
232 +
233 + public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
234 + if let location = locations.last {
235 + print("=== locationManager didUpdateLocations latitude: ",location.coordinate.latitude)
236 + print("=== locationManager didUpdateLocations longitude: ",location.coordinate.longitude)
237 +
238 + webView.evaluateJavaScript("navigator.geolocation.helper.success('\(location.timestamp)', \(location.coordinate.latitude), \(location.coordinate.longitude), \(location.altitude), \(location.horizontalAccuracy), \(location.verticalAccuracy), \(location.course), \(location.speed));");
239 + }
240 + }
241 +
242 + public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
243 + print("=== locationManager didFailWithError: ", error)
244 + webView.evaluateJavaScript("navigator.geolocation.helper.error(2, 'Failed to get position (\(error.localizedDescription))');");
245 + }
246 +
247 + func getJavaScripToEvaluate() -> String {
248 + let javaScripToEvaluate = """
249 + // management for success and error listeners and its calling
250 + navigator.geolocation.helper = {
251 + listeners: {},
252 + noop: function() {},
253 + id: function() {
254 + var min = 1, max = 1000;
255 + return Math.floor(Math.random() * (max - min + 1)) + min;
256 + },
257 + clear: function(isError) {
258 + for (var id in this.listeners) {
259 + if (isError || this.listeners[id].onetime) {
260 + navigator.geolocation.clearWatch(id);
261 + }
262 + }
263 + },
264 + success: function(timestamp, latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed) {
265 + var position = {
266 + timestamp: new Date(timestamp).getTime() || new Date().getTime(), // safari can not parse date format returned by swift e.g. 2019-12-27 15:46:59 +0000 (fallback used because we trust that safari will learn it in future because chrome knows that format)
267 + coords: {
268 + latitude: latitude,
269 + longitude: longitude,
270 + altitude: altitude,
271 + accuracy: accuracy,
272 + altitudeAccuracy: altitudeAccuracy,
273 + heading: (heading > 0) ? heading : null,
274 + speed: (speed > 0) ? speed : null
275 + }
276 + };
277 + for (var id in this.listeners) {
278 + this.listeners[id].success(position);
279 + }
280 + this.clear(false);
281 + },
282 + error: function(code, message) {
283 + var error = {
284 + PERMISSION_DENIED: 1,
285 + POSITION_UNAVAILABLE: 2,
286 + TIMEOUT: 3,
287 + code: code,
288 + message: message
289 + };
290 + for (var id in this.listeners) {
291 + this.listeners[id].error(error);
292 + }
293 + this.clear(true);
294 + }
295 + };
296 +
297 + // @override getCurrentPosition()
298 + navigator.geolocation.getCurrentPosition = function(success, error, options) {
299 + var id = this.helper.id();
300 + this.helper.listeners[id] = { onetime: true, success: success || this.noop, error: error || this.noop };
301 + window.webkit.messageHandlers.listenerAdded.postMessage("");
302 + };
303 +
304 + // @override watchPosition()
305 + navigator.geolocation.watchPosition = function(success, error, options) {
306 + var id = this.helper.id();
307 + this.helper.listeners[id] = { onetime: false, success: success || this.noop, error: error || this.noop };
308 + window.webkit.messageHandlers.listenerAdded.postMessage("");
309 + return id;
310 + };
311 +
312 + // @override clearWatch()
313 + navigator.geolocation.clearWatch = function(id) {
314 + var idExists = (this.helper.listeners[id]) ? true : false;
315 + if (idExists) {
316 + this.helper.listeners[id] = null;
317 + delete this.helper.listeners[id];
318 + window.webkit.messageHandlers.listenerRemoved.postMessage("");
319 + }
320 + };
321 + """;
322 +
323 + return javaScripToEvaluate;
324 + }
325 + // <===
326 +
125 // MARK: - API Calls 327 // MARK: - API Calls
126 func startTrackingSteps() { 328 func startTrackingSteps() {
127 swiftApi().startTrackingSteps(startTrackingStepsCallback) 329 swiftApi().startTrackingSteps(startTrackingStepsCallback)
...@@ -147,11 +349,39 @@ var timer2: DispatchSourceTimer? ...@@ -147,11 +349,39 @@ var timer2: DispatchSourceTimer?
147 self.startTimer() 349 self.startTimer()
148 } 350 }
149 }) 351 })
352 +
150 } 353 }
151 354
152 // MARK: - WKScriptMessageHandler 355 // MARK: - WKScriptMessageHandler
153 public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 356 public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
154 357
358 + // Location Permissions
359 + if (message.name == "listenerAdded") {
360 + listenersCount += 1;
361 +
362 + if (!locationServicesIsEnabled()) {
363 + onLocationServicesIsDisabled();
364 + }
365 + else if (authorizationStatusIsDenied(status: CLLocationManager.authorizationStatus())) {
366 + onAuthorizationStatusIsDenied();
367 + }
368 + else if (authorizationStatusNeedRequest(status: CLLocationManager.authorizationStatus())) {
369 + onAuthorizationStatusNeedRequest();
370 + }
371 + else if (authorizationStatusIsGranted(status: CLLocationManager.authorizationStatus())) {
372 + onAuthorizationStatusIsGranted();
373 + }
374 + }
375 + else if (message.name == "listenerRemoved") {
376 + listenersCount -= 1;
377 +
378 + // no listener left in web view to wait for position
379 + if (listenersCount == 0) {
380 + locationManager.stopUpdatingLocation();
381 + }
382 + }
383 + // <==
384 + else if (message.name == "Cosmote") {
155 if let event = message.body as? String { 385 if let event = message.body as? String {
156 let eventArray = event.split(separator: ":") 386 let eventArray = event.split(separator: ":")
157 387
...@@ -383,5 +613,6 @@ var timer2: DispatchSourceTimer? ...@@ -383,5 +613,6 @@ var timer2: DispatchSourceTimer?
383 } 613 }
384 } 614 }
385 } 615 }
616 + }
386 617
387 } 618 }
......
...@@ -403,6 +403,11 @@ ...@@ -403,6 +403,11 @@
403 <constraint firstItem="bHn-Kz-pbS" firstAttribute="leading" secondItem="iPT-gj-hEL" secondAttribute="leading" id="UTz-nY-JvS"/> 403 <constraint firstItem="bHn-Kz-pbS" firstAttribute="leading" secondItem="iPT-gj-hEL" secondAttribute="leading" id="UTz-nY-JvS"/>
404 <constraint firstItem="bHn-Kz-pbS" firstAttribute="top" secondItem="iPT-gj-hEL" secondAttribute="top" id="WKD-3C-3kF"/> 404 <constraint firstItem="bHn-Kz-pbS" firstAttribute="top" secondItem="iPT-gj-hEL" secondAttribute="top" id="WKD-3C-3kF"/>
405 </constraints> 405 </constraints>
406 + <variation key="default">
407 + <mask key="subviews">
408 + <exclude reference="bHn-Kz-pbS"/>
409 + </mask>
410 + </variation>
406 </view> 411 </view>
407 </subviews> 412 </subviews>
408 <viewLayoutGuide key="safeArea" id="xUc-yV-Y8f"/> 413 <viewLayoutGuide key="safeArea" id="xUc-yV-Y8f"/>
......