@capawesome/capacitor-maps-launcher 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CapawesomeCapacitorMapsLauncher.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +292 -0
  5. package/android/build.gradle +58 -0
  6. package/android/src/main/AndroidManifest.xml +10 -0
  7. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/MapsLauncher.java +200 -0
  8. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/MapsLauncherPlugin.java +117 -0
  9. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/classes/CustomException.java +20 -0
  10. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/classes/CustomExceptions.java +15 -0
  11. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/classes/options/Destination.java +57 -0
  12. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/classes/options/NavigateOptions.java +54 -0
  13. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/classes/results/GetAvailableAppsResult.java +29 -0
  14. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/classes/results/GetDefaultAppResult.java +25 -0
  15. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/interfaces/Callback.java +5 -0
  16. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/interfaces/EmptyCallback.java +5 -0
  17. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/interfaces/NonEmptyResultCallback.java +7 -0
  18. package/android/src/main/java/io/capawesome/capacitorjs/plugins/mapslauncher/interfaces/Result.java +7 -0
  19. package/android/src/main/res/.gitkeep +0 -0
  20. package/dist/docs.json +329 -0
  21. package/dist/esm/definitions.d.ts +195 -0
  22. package/dist/esm/definitions.js +47 -0
  23. package/dist/esm/definitions.js.map +1 -0
  24. package/dist/esm/index.d.ts +4 -0
  25. package/dist/esm/index.js +7 -0
  26. package/dist/esm/index.js.map +1 -0
  27. package/dist/esm/web.d.ts +7 -0
  28. package/dist/esm/web.js +13 -0
  29. package/dist/esm/web.js.map +1 -0
  30. package/dist/plugin.cjs.js +74 -0
  31. package/dist/plugin.cjs.js.map +1 -0
  32. package/dist/plugin.js +77 -0
  33. package/dist/plugin.js.map +1 -0
  34. package/ios/Plugin/Classes/Options/Destination.swift +36 -0
  35. package/ios/Plugin/Classes/Options/NavigateOptions.swift +23 -0
  36. package/ios/Plugin/Classes/Results/GetAvailableAppsResult.swift +16 -0
  37. package/ios/Plugin/Enums/CustomError.swift +36 -0
  38. package/ios/Plugin/Info.plist +24 -0
  39. package/ios/Plugin/MapsLauncher.swift +182 -0
  40. package/ios/Plugin/MapsLauncherPlugin.swift +71 -0
  41. package/ios/Plugin/Protocols/Result.swift +5 -0
  42. package/package.json +94 -0
@@ -0,0 +1,182 @@
1
+ import Foundation
2
+ import Capacitor
3
+ import CoreLocation
4
+ import MapKit
5
+ import UIKit
6
+
7
+ @objc public class MapsLauncher: NSObject {
8
+ static let appAppleMaps = "appleMaps"
9
+ static let appGoogleMaps = "googleMaps"
10
+ static let appWaze = "waze"
11
+ static let schemeGoogleMaps = "comgooglemaps"
12
+ static let schemeWaze = "waze"
13
+
14
+ private let plugin: MapsLauncherPlugin
15
+
16
+ init(plugin: MapsLauncherPlugin) {
17
+ self.plugin = plugin
18
+ }
19
+
20
+ @objc public func getAvailableApps(completion: @escaping (GetAvailableAppsResult?, Error?) -> Void) {
21
+ var apps = [Self.appAppleMaps]
22
+ if canOpen(Self.schemeGoogleMaps) {
23
+ apps.append(Self.appGoogleMaps)
24
+ }
25
+ if canOpen(Self.schemeWaze) {
26
+ apps.append(Self.appWaze)
27
+ }
28
+ completion(GetAvailableAppsResult(apps: apps), nil)
29
+ }
30
+
31
+ @objc public func navigate(_ options: NavigateOptions, completion: @escaping (Error?) -> Void) {
32
+ switch options.app {
33
+ case nil, Self.appAppleMaps:
34
+ openAppleMaps(options, completion: completion)
35
+ case Self.appGoogleMaps:
36
+ openGoogleMaps(options, completion: completion)
37
+ case Self.appWaze:
38
+ openWaze(options, completion: completion)
39
+ default:
40
+ completion(CustomError.appNotAvailable)
41
+ }
42
+ }
43
+
44
+ private func appleMapsDirectionsMode(_ travelMode: String?) -> String {
45
+ switch travelMode {
46
+ case "walking":
47
+ return MKLaunchOptionsDirectionsModeWalking
48
+ case "transit":
49
+ return MKLaunchOptionsDirectionsModeTransit
50
+ case "driving":
51
+ return MKLaunchOptionsDirectionsModeDriving
52
+ default:
53
+ return MKLaunchOptionsDirectionsModeDefault
54
+ }
55
+ }
56
+
57
+ private func appleMapsDirflg(_ travelMode: String?) -> String? {
58
+ switch travelMode {
59
+ case "walking":
60
+ return "w"
61
+ case "transit":
62
+ return "r"
63
+ case "driving":
64
+ return "d"
65
+ default:
66
+ return nil
67
+ }
68
+ }
69
+
70
+ private func buildAppleMapsUrl(_ options: NavigateOptions) -> URL? {
71
+ var components = URLComponents(string: "https://maps.apple.com/")
72
+ var queryItems = [URLQueryItem(name: "daddr", value: formatDestination(options.destination))]
73
+ if let start = options.start {
74
+ queryItems.append(URLQueryItem(name: "saddr", value: formatDestination(start)))
75
+ }
76
+ if let dirflg = appleMapsDirflg(options.travelMode) {
77
+ queryItems.append(URLQueryItem(name: "dirflg", value: dirflg))
78
+ }
79
+ components?.queryItems = queryItems
80
+ return components?.url
81
+ }
82
+
83
+ private func buildUrl(scheme: String, queryItems: [URLQueryItem]) -> URL? {
84
+ var components = URLComponents()
85
+ components.scheme = scheme
86
+ components.host = ""
87
+ components.queryItems = queryItems
88
+ return components.url
89
+ }
90
+
91
+ private func canOpen(_ scheme: String) -> Bool {
92
+ guard let url = URL(string: scheme + "://") else {
93
+ return false
94
+ }
95
+ return UIApplication.shared.canOpenURL(url)
96
+ }
97
+
98
+ private func formatDestination(_ destination: Destination) -> String {
99
+ if let coordinate = destination.coordinate {
100
+ return "\(coordinate.latitude),\(coordinate.longitude)"
101
+ }
102
+ return destination.address ?? ""
103
+ }
104
+
105
+ private func googleDirectionsMode(_ travelMode: String?) -> String {
106
+ switch travelMode {
107
+ case "walking", "bicycling", "transit":
108
+ return travelMode ?? "driving"
109
+ default:
110
+ return "driving"
111
+ }
112
+ }
113
+
114
+ private func open(_ url: URL, completion: @escaping (Error?) -> Void) {
115
+ DispatchQueue.main.async {
116
+ UIApplication.shared.open(url, options: [:]) { success in
117
+ completion(success ? nil : CustomError.launchFailed)
118
+ }
119
+ }
120
+ }
121
+
122
+ private func openAppleMaps(_ options: NavigateOptions, completion: @escaping (Error?) -> Void) {
123
+ let canUseMapItems = options.destination.coordinate != nil && (options.start == nil || options.start?.coordinate != nil)
124
+ if canUseMapItems, let destinationCoordinate = options.destination.coordinate {
125
+ let destinationItem = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinate))
126
+ var items: [MKMapItem] = []
127
+ if let startCoordinate = options.start?.coordinate {
128
+ items.append(MKMapItem(placemark: MKPlacemark(coordinate: startCoordinate)))
129
+ } else {
130
+ items.append(MKMapItem.forCurrentLocation())
131
+ }
132
+ items.append(destinationItem)
133
+ let launchOptions = [MKLaunchOptionsDirectionsModeKey: appleMapsDirectionsMode(options.travelMode)]
134
+ DispatchQueue.main.async {
135
+ MKMapItem.openMaps(with: items, launchOptions: launchOptions)
136
+ completion(nil)
137
+ }
138
+ } else {
139
+ guard let url = buildAppleMapsUrl(options) else {
140
+ completion(CustomError.launchFailed)
141
+ return
142
+ }
143
+ open(url, completion: completion)
144
+ }
145
+ }
146
+
147
+ private func openGoogleMaps(_ options: NavigateOptions, completion: @escaping (Error?) -> Void) {
148
+ guard canOpen(Self.schemeGoogleMaps) else {
149
+ completion(CustomError.appNotAvailable)
150
+ return
151
+ }
152
+ var queryItems = [URLQueryItem(name: "daddr", value: formatDestination(options.destination))]
153
+ if let start = options.start {
154
+ queryItems.append(URLQueryItem(name: "saddr", value: formatDestination(start)))
155
+ }
156
+ queryItems.append(URLQueryItem(name: "directionsmode", value: googleDirectionsMode(options.travelMode)))
157
+ guard let url = buildUrl(scheme: Self.schemeGoogleMaps, queryItems: queryItems) else {
158
+ completion(CustomError.launchFailed)
159
+ return
160
+ }
161
+ open(url, completion: completion)
162
+ }
163
+
164
+ private func openWaze(_ options: NavigateOptions, completion: @escaping (Error?) -> Void) {
165
+ guard canOpen(Self.schemeWaze) else {
166
+ completion(CustomError.appNotAvailable)
167
+ return
168
+ }
169
+ var queryItems: [URLQueryItem] = []
170
+ if let coordinate = options.destination.coordinate {
171
+ queryItems.append(URLQueryItem(name: "ll", value: "\(coordinate.latitude),\(coordinate.longitude)"))
172
+ } else if let address = options.destination.address {
173
+ queryItems.append(URLQueryItem(name: "q", value: address))
174
+ }
175
+ queryItems.append(URLQueryItem(name: "navigate", value: "yes"))
176
+ guard let url = buildUrl(scheme: Self.schemeWaze, queryItems: queryItems) else {
177
+ completion(CustomError.launchFailed)
178
+ return
179
+ }
180
+ open(url, completion: completion)
181
+ }
182
+ }
@@ -0,0 +1,71 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ @objc(MapsLauncherPlugin)
5
+ public class MapsLauncherPlugin: CAPPlugin, CAPBridgedPlugin {
6
+ public let identifier = "MapsLauncherPlugin"
7
+ public let jsName = "MapsLauncher"
8
+ public let pluginMethods: [CAPPluginMethod] = [
9
+ CAPPluginMethod(name: "getAvailableApps", returnType: CAPPluginReturnPromise),
10
+ CAPPluginMethod(name: "getDefaultApp", returnType: CAPPluginReturnPromise),
11
+ CAPPluginMethod(name: "navigate", returnType: CAPPluginReturnPromise)
12
+ ]
13
+ public static let tag = "MapsLauncherPlugin"
14
+
15
+ private var implementation: MapsLauncher?
16
+
17
+ override public func load() {
18
+ self.implementation = MapsLauncher(plugin: self)
19
+ }
20
+
21
+ @objc func getAvailableApps(_ call: CAPPluginCall) {
22
+ implementation?.getAvailableApps { result, error in
23
+ if let error = error {
24
+ self.rejectCall(call, error)
25
+ return
26
+ }
27
+ self.resolveCall(call, result)
28
+ }
29
+ }
30
+
31
+ @objc func getDefaultApp(_ call: CAPPluginCall) {
32
+ rejectCallAsUnimplemented(call)
33
+ }
34
+
35
+ @objc func navigate(_ call: CAPPluginCall) {
36
+ do {
37
+ let options = try NavigateOptions(call)
38
+ implementation?.navigate(options) { error in
39
+ if let error = error {
40
+ self.rejectCall(call, error)
41
+ return
42
+ }
43
+ self.resolveCall(call)
44
+ }
45
+ } catch {
46
+ self.rejectCall(call, error)
47
+ }
48
+ }
49
+
50
+ private func rejectCall(_ call: CAPPluginCall, _ error: Error) {
51
+ CAPLog.print("[", MapsLauncherPlugin.tag, "] ", error)
52
+ let code = (error as? CustomError)?.code
53
+ call.reject(error.localizedDescription, code)
54
+ }
55
+
56
+ private func rejectCallAsUnimplemented(_ call: CAPPluginCall) {
57
+ call.unimplemented("This method is not available on this platform.")
58
+ }
59
+
60
+ private func resolveCall(_ call: CAPPluginCall) {
61
+ call.resolve()
62
+ }
63
+
64
+ private func resolveCall(_ call: CAPPluginCall, _ result: Result?) {
65
+ if let result = result?.toJSObject() as? JSObject {
66
+ call.resolve(result)
67
+ } else {
68
+ call.resolve()
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,5 @@
1
+ import Capacitor
2
+
3
+ @objc public protocol Result {
4
+ @objc func toJSObject() -> AnyObject
5
+ }
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@capawesome/capacitor-maps-launcher",
3
+ "version": "0.0.1",
4
+ "description": "Capacitor plugin to launch navigation apps with turn-by-turn directions.",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Plugin/",
14
+ "CapawesomeCapacitorMapsLauncher.podspec",
15
+ "Package.swift"
16
+ ],
17
+ "author": "Robin Genz <mail@robingenz.dev>",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/capawesome-team/capacitor-plugins.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/capawesome-team/capacitor-plugins/issues"
25
+ },
26
+ "funding": [
27
+ {
28
+ "type": "github",
29
+ "url": "https://github.com/sponsors/capawesome-team/"
30
+ },
31
+ {
32
+ "type": "opencollective",
33
+ "url": "https://opencollective.com/capawesome"
34
+ }
35
+ ],
36
+ "homepage": "https://capawesome.io/docs/plugins/maps-launcher/",
37
+ "keywords": [
38
+ "capacitor",
39
+ "plugin",
40
+ "native",
41
+ "maps",
42
+ "navigation",
43
+ "launch navigator"
44
+ ],
45
+ "scripts": {
46
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
47
+ "verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin -destination generic/platform=iOS && cd ..",
48
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
49
+ "verify:web": "npm run build",
50
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
51
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
52
+ "eslint": "eslint . --ext ts",
53
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
54
+ "swiftlint": "node-swiftlint",
55
+ "docgen": "docgen --api MapsLauncherPlugin --output-readme README.md --output-json dist/docs.json",
56
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
57
+ "clean": "rimraf ./dist",
58
+ "watch": "tsc --watch",
59
+ "ios:pod:install": "cd ios && pod install --repo-update && cd ..",
60
+ "ios:spm:install": "cd ios && swift package resolve && cd ..",
61
+ "prepublishOnly": "npm run build"
62
+ },
63
+ "devDependencies": {
64
+ "@capacitor/android": "8.0.0",
65
+ "@capacitor/cli": "8.0.0",
66
+ "@capacitor/core": "8.0.0",
67
+ "@capacitor/docgen": "0.3.1",
68
+ "@capacitor/ios": "8.0.0",
69
+ "@ionic/eslint-config": "0.4.0",
70
+ "eslint": "8.57.0",
71
+ "prettier-plugin-java": "2.6.7",
72
+ "rimraf": "6.1.2",
73
+ "rollup": "4.53.3",
74
+ "swiftlint": "2.0.0",
75
+ "typescript": "5.9.3"
76
+ },
77
+ "peerDependencies": {
78
+ "@capacitor/core": ">=8.0.0"
79
+ },
80
+ "eslintConfig": {
81
+ "extends": "@ionic/eslint-config/recommended"
82
+ },
83
+ "capacitor": {
84
+ "ios": {
85
+ "src": "ios"
86
+ },
87
+ "android": {
88
+ "src": "android"
89
+ }
90
+ },
91
+ "publishConfig": {
92
+ "access": "public"
93
+ }
94
+ }