surveysparrow-ionic-plugin 0.0.1 → 0.0.2
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.
- package/Package.swift +1 -3
- package/ios/Sources/SurveySparrowIonicPluginPlugin/SsSurveyDelegate.swift +16 -0
- package/ios/Sources/SurveySparrowIonicPluginPlugin/SsSurveyHelper.swift +179 -0
- package/ios/Sources/SurveySparrowIonicPluginPlugin/SsSurveyView.swift +314 -0
- package/ios/Sources/SurveySparrowIonicPluginPlugin/SsSurveyViewController.swift +81 -0
- package/ios/Sources/SurveySparrowIonicPluginPlugin/SurveySparrow.swift +158 -0
- package/ios/Sources/SurveySparrowIonicPluginPlugin/SurveySparrowIonicPlugin.swift +2 -3
- package/ios/Tests/SurveySparrowIonicPluginPluginTests/SurveySparrowIonicPluginPluginTests.swift +9 -4
- package/package.json +1 -1
package/Package.swift
CHANGED
|
@@ -10,7 +10,6 @@ let package = Package(
|
|
|
10
10
|
targets: ["SurveySparrowIonicPluginPlugin"])
|
|
11
11
|
],
|
|
12
12
|
dependencies: [
|
|
13
|
-
.package(url: "https://github.com/surveysparrow/surveysparrow-ios-sdk.git", exact: "1.0.6"),
|
|
14
13
|
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
|
|
15
14
|
],
|
|
16
15
|
targets: [
|
|
@@ -18,8 +17,7 @@ let package = Package(
|
|
|
18
17
|
name: "SurveySparrowIonicPluginPlugin",
|
|
19
18
|
dependencies: [
|
|
20
19
|
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
21
|
-
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
22
|
-
.product(name: "SurveySparrowSdk", package: "surveysparrow-ios-sdk")
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
23
21
|
],
|
|
24
22
|
path: "ios/Sources/SurveySparrowIonicPluginPlugin"),
|
|
25
23
|
.testTarget(
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SsSurveyDelegate.swift
|
|
3
|
+
// SurveySparrowSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Gokulkrishna raju on 09/02/24.
|
|
6
|
+
// Copyright © 2020 SurveySparrow. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
public protocol SsSurveyDelegate {
|
|
12
|
+
func handleSurveyResponse(response: [String: AnyObject])
|
|
13
|
+
func handleSurveyLoaded(response: [String: AnyObject])
|
|
14
|
+
func handleSurveyValidation(response: [String: AnyObject])
|
|
15
|
+
func handleCloseButtonTap()
|
|
16
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SsSurveyHelper.swift
|
|
3
|
+
// SurveySparrowSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Gokulkrishna raju on 09/02/24.
|
|
6
|
+
// Copyright © 2020 SurveySparrow. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import Foundation
|
|
10
|
+
|
|
11
|
+
func validateSurvey(
|
|
12
|
+
domain: String? = nil,
|
|
13
|
+
token: String? = nil,
|
|
14
|
+
params: [String: String]? = [:],
|
|
15
|
+
group: DispatchGroup,
|
|
16
|
+
completion: @escaping ([String: Any]) -> Void
|
|
17
|
+
) {
|
|
18
|
+
struct User: Codable {
|
|
19
|
+
let email: String
|
|
20
|
+
}
|
|
21
|
+
struct ValidationResponse: Codable {
|
|
22
|
+
let active: Bool
|
|
23
|
+
let reason: String
|
|
24
|
+
let widgetContactId: Int64?
|
|
25
|
+
}
|
|
26
|
+
var email: String = ""
|
|
27
|
+
var active: Bool = false
|
|
28
|
+
if let unwrappedParams = params {
|
|
29
|
+
for (key, value) in unwrappedParams {
|
|
30
|
+
if key == "emailaddress" {
|
|
31
|
+
email = value
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
DispatchQueue.global().async {
|
|
37
|
+
let parameters = User(email: email)
|
|
38
|
+
var urlComponent = URLComponents()
|
|
39
|
+
urlComponent.scheme = "https"
|
|
40
|
+
urlComponent.host = domain!.trimmingCharacters(in: CharacterSet.whitespaces)
|
|
41
|
+
urlComponent.path =
|
|
42
|
+
"/sdk/validate-survey/\(token!.trimmingCharacters(in: CharacterSet.whitespaces))"
|
|
43
|
+
var validationUrl: URLRequest
|
|
44
|
+
|
|
45
|
+
if let url = urlComponent.url {
|
|
46
|
+
validationUrl = URLRequest(url: url)
|
|
47
|
+
makeHTTPRequest(urlString: validationUrl.description, parameters: parameters, method: "POST")
|
|
48
|
+
{ result in
|
|
49
|
+
defer {
|
|
50
|
+
group.leave()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
switch result {
|
|
54
|
+
case .success(let data):
|
|
55
|
+
if let data = data, let stringResponse = String(data: data, encoding: .utf8) {
|
|
56
|
+
guard let jsonData = stringResponse.data(using: .utf8) else {
|
|
57
|
+
print("Failed to convert string to data")
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
do {
|
|
61
|
+
let responseData = try JSONDecoder().decode(ValidationResponse.self, from: jsonData)
|
|
62
|
+
active = responseData.active
|
|
63
|
+
let result = [
|
|
64
|
+
"active": responseData.active,
|
|
65
|
+
"reason": responseData.reason,
|
|
66
|
+
"widgetContactId": responseData.widgetContactId ?? 0,
|
|
67
|
+
]
|
|
68
|
+
completion(result)
|
|
69
|
+
return
|
|
70
|
+
} catch {
|
|
71
|
+
print("Error decoding JSON: \(error)")
|
|
72
|
+
}
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
case .failure(let error):
|
|
76
|
+
print("Error: \(error)")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func closeSurvey(
|
|
84
|
+
domain: String? = nil, widgetContactId: Int64? = nil,
|
|
85
|
+
params: [String: String]? = [:],
|
|
86
|
+
group: DispatchGroup,
|
|
87
|
+
completion: @escaping ([String: Any]) -> Void
|
|
88
|
+
) {
|
|
89
|
+
struct ThrottleData: Codable {
|
|
90
|
+
let throttledOn: String
|
|
91
|
+
}
|
|
92
|
+
struct ThrottelingResponse: Codable {
|
|
93
|
+
let id: Int64
|
|
94
|
+
let contact_id: Int64
|
|
95
|
+
}
|
|
96
|
+
DispatchQueue.global().async {
|
|
97
|
+
let dateFormatter = DateFormatter()
|
|
98
|
+
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssxxx"
|
|
99
|
+
dateFormatter.timeZone = TimeZone(secondsFromGMT: 19800) // Adjust timezone offset as needed
|
|
100
|
+
let currentDateString = dateFormatter.string(from: Date())
|
|
101
|
+
let parameters = ThrottleData(throttledOn: currentDateString)
|
|
102
|
+
var urlComponent = URLComponents()
|
|
103
|
+
urlComponent.scheme = "https"
|
|
104
|
+
urlComponent.host = domain!.trimmingCharacters(in: CharacterSet.whitespaces)
|
|
105
|
+
if let unwrappedId = widgetContactId, unwrappedId != 0 {
|
|
106
|
+
urlComponent.path = "/nps/widget/contact/\(unwrappedId)"
|
|
107
|
+
} else {
|
|
108
|
+
let result = [
|
|
109
|
+
"success": true
|
|
110
|
+
]
|
|
111
|
+
completion(result)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
var validationUrl: URLRequest
|
|
115
|
+
|
|
116
|
+
if let url = urlComponent.url {
|
|
117
|
+
validationUrl = URLRequest(url: url)
|
|
118
|
+
makeHTTPRequest(urlString: validationUrl.description, parameters: parameters, method: "PUT") {
|
|
119
|
+
result in
|
|
120
|
+
defer {
|
|
121
|
+
group.leave()
|
|
122
|
+
}
|
|
123
|
+
switch result {
|
|
124
|
+
case .success(let data):
|
|
125
|
+
if let data = data, let stringResponse = String(data: data, encoding: .utf8) {
|
|
126
|
+
guard let jsonData = stringResponse.data(using: .utf8) else {
|
|
127
|
+
print("Failed to convert string to data")
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
do {
|
|
131
|
+
_ = try JSONDecoder().decode(ThrottelingResponse.self, from: jsonData)
|
|
132
|
+
let result = [
|
|
133
|
+
"success": true
|
|
134
|
+
]
|
|
135
|
+
completion(result)
|
|
136
|
+
return
|
|
137
|
+
} catch {
|
|
138
|
+
print("Error decoding JSON: \(error)")
|
|
139
|
+
}
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
case .failure(let error):
|
|
143
|
+
print("Error: \(error)")
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func makeHTTPRequest(
|
|
151
|
+
urlString: String, parameters: Encodable, method: String,
|
|
152
|
+
completion: @escaping (Result<Data?, Error>) -> Void
|
|
153
|
+
) {
|
|
154
|
+
// Create the URL
|
|
155
|
+
guard let url = URL(string: urlString) else {
|
|
156
|
+
completion(
|
|
157
|
+
.failure(
|
|
158
|
+
NSError(domain: urlString, code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
var request = URLRequest(url: url)
|
|
162
|
+
request.httpMethod = method
|
|
163
|
+
do {
|
|
164
|
+
let jsonData = try JSONEncoder().encode(parameters)
|
|
165
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
166
|
+
request.httpBody = jsonData
|
|
167
|
+
} catch {
|
|
168
|
+
completion(.failure(error))
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
172
|
+
if let error = error {
|
|
173
|
+
completion(.failure(error))
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
completion(.success(data))
|
|
177
|
+
}
|
|
178
|
+
task.resume()
|
|
179
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SsSurveyView.swift
|
|
3
|
+
// SurveySparrowSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Gokulkrishna raju on 09/02/24.
|
|
6
|
+
// Copyright © 2020 SurveySparrow. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#if canImport(UIKit)
|
|
10
|
+
import UIKit
|
|
11
|
+
import WebKit
|
|
12
|
+
|
|
13
|
+
@available(iOS 13.0, *)
|
|
14
|
+
@IBDesignable public class SsSurveyView: UIView, WKScriptMessageHandler, WKNavigationDelegate {
|
|
15
|
+
|
|
16
|
+
// MARK: Properties
|
|
17
|
+
private var ssWebView: WKWebView = WKWebView()
|
|
18
|
+
private let surveyResponseHandler = WKUserContentController()
|
|
19
|
+
private let loader: UIActivityIndicatorView = UIActivityIndicatorView()
|
|
20
|
+
private var surveyLoaded: String = "surveyLoadStarted"
|
|
21
|
+
private var surveyCompleted: String = "surveyCompleted"
|
|
22
|
+
private static var _widgetContactId: Int64?
|
|
23
|
+
|
|
24
|
+
public static var widgetContactId: Int64? {
|
|
25
|
+
get {
|
|
26
|
+
return _widgetContactId
|
|
27
|
+
}
|
|
28
|
+
set {
|
|
29
|
+
_widgetContactId = newValue
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
public var params: [String: String] = [:]
|
|
33
|
+
public var surveyType: SurveySparrow.SurveyType = .CLASSIC
|
|
34
|
+
public var getSurveyLoadedResponse: Bool = false
|
|
35
|
+
|
|
36
|
+
@IBInspectable public var domain: String?
|
|
37
|
+
@IBInspectable public var token: String?
|
|
38
|
+
|
|
39
|
+
public var surveyDelegate: SsSurveyDelegate!
|
|
40
|
+
|
|
41
|
+
var properties: [String: Any]?
|
|
42
|
+
|
|
43
|
+
// MARK: Initialization
|
|
44
|
+
public init(properties: [String: Any]) {
|
|
45
|
+
super.init(frame: .zero)
|
|
46
|
+
self.properties = properties
|
|
47
|
+
addFeedbackView()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
required init?(coder: NSCoder) {
|
|
51
|
+
super.init(coder: coder)
|
|
52
|
+
addFeedbackView()
|
|
53
|
+
}
|
|
54
|
+
var closeButton = UIButton(type: .system)
|
|
55
|
+
// MARK: Private methods
|
|
56
|
+
private func addFeedbackView() {
|
|
57
|
+
let config = WKWebViewConfiguration()
|
|
58
|
+
config.preferences.javaScriptEnabled = true
|
|
59
|
+
config.userContentController = surveyResponseHandler
|
|
60
|
+
ssWebView = WKWebView(frame: bounds, configuration: config)
|
|
61
|
+
surveyResponseHandler.add(self, name: "surveyResponse")
|
|
62
|
+
ssWebView.navigationDelegate = self
|
|
63
|
+
ssWebView.backgroundColor = .gray
|
|
64
|
+
ssWebView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
65
|
+
addSubview(ssWebView)
|
|
66
|
+
|
|
67
|
+
let isCloseButtonEnabled = properties?["isCloseButtonEnabled"] as? Bool
|
|
68
|
+
|
|
69
|
+
if isCloseButtonEnabled ?? true == true {
|
|
70
|
+
|
|
71
|
+
let closeButtonWrapper = UIView()
|
|
72
|
+
ssWebView.addSubview(closeButtonWrapper)
|
|
73
|
+
|
|
74
|
+
closeButton.setImage(UIImage(systemName: "xmark"), for: .normal)
|
|
75
|
+
closeButton.tintColor = .black
|
|
76
|
+
closeButton.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside)
|
|
77
|
+
|
|
78
|
+
closeButtonWrapper.addSubview(closeButton)
|
|
79
|
+
closeButtonWrapper.translatesAutoresizingMaskIntoConstraints = false
|
|
80
|
+
closeButtonWrapper.backgroundColor = .white
|
|
81
|
+
closeButtonWrapper.layer.cornerRadius = 4
|
|
82
|
+
closeButtonWrapper.clipsToBounds = true
|
|
83
|
+
|
|
84
|
+
NSLayoutConstraint.activate([
|
|
85
|
+
|
|
86
|
+
closeButtonWrapper.topAnchor.constraint(equalTo: ssWebView.safeAreaLayoutGuide.topAnchor, constant: 16),
|
|
87
|
+
closeButtonWrapper.trailingAnchor.constraint(equalTo: ssWebView.safeAreaLayoutGuide.trailingAnchor, constant: -16),
|
|
88
|
+
closeButtonWrapper.widthAnchor.constraint(equalToConstant: 35),
|
|
89
|
+
closeButtonWrapper.heightAnchor.constraint(equalToConstant: 35),
|
|
90
|
+
|
|
91
|
+
closeButton.centerXAnchor.constraint(equalTo: closeButtonWrapper.centerXAnchor),
|
|
92
|
+
closeButton.centerYAnchor.constraint(equalTo: closeButtonWrapper.centerYAnchor),
|
|
93
|
+
closeButton.widthAnchor.constraint(equalToConstant: 14),
|
|
94
|
+
closeButton.heightAnchor.constraint(equalToConstant: 14)
|
|
95
|
+
])
|
|
96
|
+
}
|
|
97
|
+
ssWebView.addSubview(loader)
|
|
98
|
+
ssWebView.navigationDelegate = self
|
|
99
|
+
loader.translatesAutoresizingMaskIntoConstraints = false
|
|
100
|
+
loader.centerXAnchor.constraint(equalTo: ssWebView.centerXAnchor).isActive = true
|
|
101
|
+
loader.centerYAnchor.constraint(equalTo: ssWebView.centerYAnchor).isActive = true
|
|
102
|
+
loader.hidesWhenStopped = true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@objc func closeButtonTapped() {
|
|
106
|
+
var isSuccess = false
|
|
107
|
+
// Check if widgetContactId is valid and not 0
|
|
108
|
+
if let unwrappedId = SsSurveyView.widgetContactId, unwrappedId != 0 {
|
|
109
|
+
let group = DispatchGroup()
|
|
110
|
+
group.enter()
|
|
111
|
+
let completion: ([String: Any]) -> Void = { result in
|
|
112
|
+
if let success = result["success"] as? Bool {
|
|
113
|
+
isSuccess = success
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
closeSurvey(
|
|
117
|
+
domain: domain, widgetContactId: unwrappedId, params: params, group: group,
|
|
118
|
+
completion: completion)
|
|
119
|
+
|
|
120
|
+
group.wait()
|
|
121
|
+
}
|
|
122
|
+
// Close the survey
|
|
123
|
+
closeSurveyUI(isSuccess: isSuccess)
|
|
124
|
+
|
|
125
|
+
if surveyDelegate != nil {
|
|
126
|
+
surveyDelegate.handleCloseButtonTap()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
func closeSurveyUI(isSuccess: Bool) {
|
|
131
|
+
let emptyHTML = "<html><body></body></html>"
|
|
132
|
+
ssWebView.loadHTMLString(emptyHTML, baseURL: nil)
|
|
133
|
+
closeButton.isHidden = true
|
|
134
|
+
|
|
135
|
+
if let parentViewController = findParentViewController() {
|
|
136
|
+
parentViewController.dismiss(animated: true, completion: nil)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private func findParentViewController() -> UIViewController? {
|
|
141
|
+
var responder: UIResponder? = self
|
|
142
|
+
while let currentResponder = responder {
|
|
143
|
+
if let viewController = currentResponder as? UIViewController {
|
|
144
|
+
return viewController
|
|
145
|
+
}
|
|
146
|
+
responder = currentResponder.next
|
|
147
|
+
}
|
|
148
|
+
return nil
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
|
152
|
+
if navigationAction.navigationType == .linkActivated {
|
|
153
|
+
if let url = navigationAction.request.url {
|
|
154
|
+
UIApplication.shared.open(url)
|
|
155
|
+
decisionHandler(.cancel)
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
decisionHandler(.allow)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error){
|
|
163
|
+
print("Failed to load web page: \(error.localizedDescription)")
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
|
167
|
+
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
|
168
|
+
loader.stopAnimating()
|
|
169
|
+
|
|
170
|
+
let isCloseButtonEnabled = properties?["isCloseButtonEnabled"] as? Bool
|
|
171
|
+
|
|
172
|
+
if isCloseButtonEnabled ?? true == true {
|
|
173
|
+
let jsCode = """
|
|
174
|
+
const styleTag = document.createElement("style");
|
|
175
|
+
styleTag.innerHTML = `.ss-language-selector--wrapper { margin-right: 45px; }`;
|
|
176
|
+
document.body.appendChild(styleTag);
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
webView.evaluateJavaScript(jsCode, completionHandler: { (result, error) in
|
|
180
|
+
if let error = error {
|
|
181
|
+
// print(error)
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
|
188
|
+
loader.stopAnimating()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
|
192
|
+
if surveyDelegate != nil {
|
|
193
|
+
let response = message.body as! [String: AnyObject]
|
|
194
|
+
let responseType = response["type"] as! String
|
|
195
|
+
if(responseType == surveyLoaded){
|
|
196
|
+
if surveyDelegate != nil {
|
|
197
|
+
surveyDelegate.handleSurveyLoaded(response: response)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if(responseType == surveyCompleted){
|
|
201
|
+
if surveyDelegate != nil {
|
|
202
|
+
surveyDelegate.handleSurveyResponse(response: response)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
public func loadFullscreenSurvey(parent: UIViewController,delegate:SsSurveyDelegate, domain: String? = nil, token: String? = nil, params: [String: String]? = [:] ) {
|
|
209
|
+
let ssSurveyViewController = SsSurveyViewController()
|
|
210
|
+
ssSurveyViewController.domain = domain
|
|
211
|
+
ssSurveyViewController.token = token
|
|
212
|
+
ssSurveyViewController.properties = self.properties ?? [:]
|
|
213
|
+
if(params != nil){
|
|
214
|
+
ssSurveyViewController.params = params ?? [:]
|
|
215
|
+
}
|
|
216
|
+
ssSurveyViewController.getSurveyLoadedResponse = true
|
|
217
|
+
if domain != nil && token != nil {
|
|
218
|
+
ssSurveyViewController.surveyDelegate = delegate
|
|
219
|
+
var isActive: Bool = false
|
|
220
|
+
var reason: String = ""
|
|
221
|
+
let group = DispatchGroup()
|
|
222
|
+
group.enter()
|
|
223
|
+
let completion: ([String: Any]) -> Void = { result in
|
|
224
|
+
if let active = result["active"] as? Bool {
|
|
225
|
+
isActive = active
|
|
226
|
+
}
|
|
227
|
+
if let reasonData = result["reason"] as? String {
|
|
228
|
+
reason = reasonData
|
|
229
|
+
}
|
|
230
|
+
if let widgetContactIdData = result["widgetContactId"] as? Int64 {
|
|
231
|
+
SsSurveyView.widgetContactId = widgetContactIdData
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
validateSurvey(domain:domain,token:token,params: params, group: group,completion:completion);
|
|
235
|
+
group.wait()
|
|
236
|
+
if isActive == true {
|
|
237
|
+
parent.present(ssSurveyViewController, animated: true)
|
|
238
|
+
} else {
|
|
239
|
+
ssSurveyViewController.surveyDelegate.handleSurveyValidation(response: [
|
|
240
|
+
"active": String(isActive),
|
|
241
|
+
"reason": reason,
|
|
242
|
+
] as [String: AnyObject])
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
public func loadEmbedSurvey(domain: String? = nil, token: String? = nil, params: [String: String]? = [:]) {
|
|
248
|
+
self.domain = domain != nil ? domain! : self.domain
|
|
249
|
+
self.token = token != nil ? token! : self.token
|
|
250
|
+
if self.domain != nil && self.token != nil {
|
|
251
|
+
var isActive: Bool = false
|
|
252
|
+
var reason: String = ""
|
|
253
|
+
let group = DispatchGroup()
|
|
254
|
+
group.enter()
|
|
255
|
+
let completion: ([String: Any]) -> Void = { result in
|
|
256
|
+
if let active = result["active"] as? Bool {
|
|
257
|
+
isActive = active
|
|
258
|
+
}
|
|
259
|
+
if let reasonData = result["reason"] as? String {
|
|
260
|
+
reason = reasonData
|
|
261
|
+
}
|
|
262
|
+
if let widgetContactIdData = result["widgetContactId"] as? Int64 {
|
|
263
|
+
SsSurveyView.widgetContactId = widgetContactIdData
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
validateSurvey(domain:domain,token:token,params: params,group: group,completion:completion);
|
|
267
|
+
group.wait()
|
|
268
|
+
if isActive == true {
|
|
269
|
+
if(params != nil){
|
|
270
|
+
self.params = params ?? [:]
|
|
271
|
+
}
|
|
272
|
+
loadSurvey(domain:domain,token:token)
|
|
273
|
+
closeButton.isHidden = false ;
|
|
274
|
+
} else {
|
|
275
|
+
self.handleSurveyValidation(response: [
|
|
276
|
+
"active": String(isActive),
|
|
277
|
+
"reason": reason,
|
|
278
|
+
] as [String: AnyObject])
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// MARK: Public method
|
|
284
|
+
public func loadSurvey(domain: String? = nil, token: String? = nil) {
|
|
285
|
+
self.domain = domain != nil ? domain! : self.domain
|
|
286
|
+
self.token = token != nil ? token! : self.token
|
|
287
|
+
if self.domain != nil && self.token != nil {
|
|
288
|
+
loader.startAnimating()
|
|
289
|
+
var urlComponent = URLComponents()
|
|
290
|
+
urlComponent.scheme = "https"
|
|
291
|
+
urlComponent.host = self.domain!.trimmingCharacters(in: CharacterSet.whitespaces)
|
|
292
|
+
urlComponent.path = "/\(surveyType == .NPS ? "n" : "s")/ios/\(self.token!.trimmingCharacters(in: CharacterSet.whitespaces))"
|
|
293
|
+
if(getSurveyLoadedResponse){
|
|
294
|
+
params["isSurveyLoaded"] = "true"
|
|
295
|
+
}
|
|
296
|
+
urlComponent.queryItems = params.map {
|
|
297
|
+
URLQueryItem(name: $0.key, value: $0.value)
|
|
298
|
+
}
|
|
299
|
+
urlComponent.queryItems?.append(URLQueryItem(name: "sparrowLang", value: properties?["sparrowLang"] as? String))
|
|
300
|
+
if let url = urlComponent.url {
|
|
301
|
+
let request = URLRequest(url: url)
|
|
302
|
+
ssWebView.load(request)
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
print("Error: Domain or token is nil")
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
func handleSurveyValidation(response: [String : AnyObject]) {
|
|
310
|
+
print(response)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#endif
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SsSurveyViewController.swift
|
|
3
|
+
// SurveySparrowSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Gokulkrishna raju on 09/02/24.
|
|
6
|
+
// Copyright © 2020 SurveySparrow. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#if canImport(UIKit)
|
|
10
|
+
import UIKit
|
|
11
|
+
|
|
12
|
+
@available(iOS 13.0, *)
|
|
13
|
+
@IBDesignable
|
|
14
|
+
public class SsSurveyViewController: UIViewController, SsSurveyDelegate {
|
|
15
|
+
// MARK: Properties
|
|
16
|
+
public var surveyDelegate: SsSurveyDelegate!
|
|
17
|
+
|
|
18
|
+
public var params: [String: String] = [:]
|
|
19
|
+
public var widgetContactId: Int64 = 0
|
|
20
|
+
public var surveyType: SurveySparrow.SurveyType = .CLASSIC
|
|
21
|
+
public var getSurveyLoadedResponse: Bool = false
|
|
22
|
+
|
|
23
|
+
@IBInspectable public var domain: String?
|
|
24
|
+
@IBInspectable public var token: String?
|
|
25
|
+
@IBInspectable public var properties: [String: Any] = [:]
|
|
26
|
+
@IBInspectable public var thankyouTimeout: Double = 3.0
|
|
27
|
+
|
|
28
|
+
// MARK: Initialize
|
|
29
|
+
public override func viewDidLoad() {
|
|
30
|
+
super.viewDidLoad()
|
|
31
|
+
|
|
32
|
+
view.backgroundColor = view.backgroundColor == nil ? .white : view.backgroundColor
|
|
33
|
+
if domain != nil && token != nil {
|
|
34
|
+
let ssSurveyView = SsSurveyView(properties: properties)
|
|
35
|
+
ssSurveyView.surveyDelegate = self
|
|
36
|
+
ssSurveyView.params = params
|
|
37
|
+
ssSurveyView.getSurveyLoadedResponse = getSurveyLoadedResponse
|
|
38
|
+
|
|
39
|
+
ssSurveyView.frame = view.bounds
|
|
40
|
+
ssSurveyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
41
|
+
|
|
42
|
+
ssSurveyView.loadSurvey(domain: domain, token: token)
|
|
43
|
+
view.addSubview(ssSurveyView)
|
|
44
|
+
} else {
|
|
45
|
+
print("Error: Domain or token is nil")
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// MARK: Delegate
|
|
50
|
+
public func handleSurveyResponse(response: [String : AnyObject]) {
|
|
51
|
+
if surveyDelegate != nil {
|
|
52
|
+
surveyDelegate.handleSurveyResponse(response: response)
|
|
53
|
+
}
|
|
54
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + thankyouTimeout) {
|
|
55
|
+
self.dismiss(animated: true, completion: nil)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public func handleSurveyLoaded(response: [String : AnyObject]){
|
|
60
|
+
if surveyDelegate != nil {
|
|
61
|
+
surveyDelegate.handleSurveyLoaded(response: response)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public func handleCloseButtonTap() {
|
|
66
|
+
if surveyDelegate != nil {
|
|
67
|
+
surveyDelegate.handleCloseButtonTap()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public func handleSurveyValidation(response: [String : AnyObject]) {
|
|
72
|
+
if surveyDelegate != nil {
|
|
73
|
+
surveyDelegate.handleSurveyValidation(response: response)
|
|
74
|
+
}
|
|
75
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + thankyouTimeout) {
|
|
76
|
+
self.dismiss(animated: true, completion: nil)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#endif
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SurveySparrow.swift
|
|
3
|
+
// SurveySparrowSdk
|
|
4
|
+
//
|
|
5
|
+
// Created by Gokulkrishna raju on 09/02/24.
|
|
6
|
+
// Copyright © 2020 SurveySparrow. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#if canImport(UIKit)
|
|
10
|
+
import UIKit
|
|
11
|
+
|
|
12
|
+
@available(iOS 13.0, *)
|
|
13
|
+
public class SurveySparrow: SsSurveyDelegate {
|
|
14
|
+
// MARK: Properties
|
|
15
|
+
private var dataStore = NSUbiquitousKeyValueStore()
|
|
16
|
+
private var domain: String
|
|
17
|
+
private var token: String
|
|
18
|
+
private var properties: [String: String] = [:]
|
|
19
|
+
|
|
20
|
+
public var surveyType: SurveyType = .CLASSIC
|
|
21
|
+
public var params: [String: String] = [:]
|
|
22
|
+
public var thankyouTimout: Double = 3.0
|
|
23
|
+
public var surveyDelegate: SsSurveyDelegate!
|
|
24
|
+
public var alertTitle: String = "Rate us"
|
|
25
|
+
public var alertMessage: String = "Share your feedback and let us know how we are doing"
|
|
26
|
+
public var alertPositiveButton: String = "Rate Now"
|
|
27
|
+
public var alertNegativeButton: String = "Later"
|
|
28
|
+
public var isConnectedToNetwork: Bool = true
|
|
29
|
+
public var startAfter: Int64 = 259200000 // 3 days
|
|
30
|
+
public var repeatInterval: Int64 = 432000000 // 5 days
|
|
31
|
+
public var incrementalRepeat: Bool = false
|
|
32
|
+
public var repeatSurvey: Bool = false
|
|
33
|
+
public var getSurveyLoadedResponse: Bool = false
|
|
34
|
+
|
|
35
|
+
private var isAlreadyTakenKey = "isAlreadyTaken_"
|
|
36
|
+
private var promptTimeKey = "promptTime_"
|
|
37
|
+
private var incrementMultiplierKey = "incrementMultiplier_"
|
|
38
|
+
|
|
39
|
+
// MARK: Initialization
|
|
40
|
+
public init(domain: String, token: String, properties: [String: String]) {
|
|
41
|
+
self.domain = domain
|
|
42
|
+
self.token = token
|
|
43
|
+
self.properties = properties
|
|
44
|
+
|
|
45
|
+
isAlreadyTakenKey += token
|
|
46
|
+
promptTimeKey += token
|
|
47
|
+
incrementMultiplierKey += token
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// MARK: Data Type
|
|
51
|
+
public enum SurveyType {
|
|
52
|
+
case CLASSIC
|
|
53
|
+
case CHAT
|
|
54
|
+
case NPS
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// MARK: Public methods
|
|
58
|
+
public func scheduleSurvey(parent: UIViewController) {
|
|
59
|
+
let currentTime = Int64(Date().timeIntervalSince1970 * 1000)
|
|
60
|
+
let isAlreadyTaken = UserDefaults.standard.bool(forKey: isAlreadyTakenKey)
|
|
61
|
+
let promptTime = UserDefaults.standard.integer(forKey: promptTimeKey)
|
|
62
|
+
var incrementMultiplier = UserDefaults.standard.integer(forKey: incrementMultiplierKey)
|
|
63
|
+
incrementMultiplier = incrementMultiplier == 0 ? 1 : incrementMultiplier
|
|
64
|
+
|
|
65
|
+
if promptTime == 0 {
|
|
66
|
+
let nextPrompt = currentTime + startAfter
|
|
67
|
+
UserDefaults.standard.set(nextPrompt, forKey: promptTimeKey)
|
|
68
|
+
dataStore.set(1, forKey: incrementMultiplierKey)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
if self.domain != nil && self.token != nil {
|
|
72
|
+
if isConnectedToNetwork && (!isAlreadyTaken || repeatSurvey) && (promptTime < currentTime) {
|
|
73
|
+
var isActive: Bool = false
|
|
74
|
+
var reason: String = ""
|
|
75
|
+
let group = DispatchGroup()
|
|
76
|
+
var widgetContactId: Int64 = 0 ;
|
|
77
|
+
group.enter()
|
|
78
|
+
let completion: ([String: Any]) -> Void = { result in
|
|
79
|
+
if let active = result["active"] as? Bool {
|
|
80
|
+
isActive = active
|
|
81
|
+
}
|
|
82
|
+
if let reasonData = result["reason"] as? String {
|
|
83
|
+
reason = reasonData
|
|
84
|
+
}
|
|
85
|
+
if let widgetContactIdData = result["widgetContactId"] as? Int64 {
|
|
86
|
+
widgetContactId = widgetContactIdData
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
validateSurvey(domain:domain,token:token,params:self.params, group: group,completion:completion);
|
|
90
|
+
group.wait()
|
|
91
|
+
if isActive == true {
|
|
92
|
+
|
|
93
|
+
let alertDialog = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert)
|
|
94
|
+
alertDialog.addAction(UIAlertAction(title: alertPositiveButton, style: UIAlertAction.Style.default, handler: {action in
|
|
95
|
+
let ssSurveyViewController = SsSurveyViewController()
|
|
96
|
+
ssSurveyViewController.domain = self.domain
|
|
97
|
+
ssSurveyViewController.token = self.token
|
|
98
|
+
ssSurveyViewController.params = self.params
|
|
99
|
+
ssSurveyViewController.properties = self.properties
|
|
100
|
+
ssSurveyViewController.widgetContactId = widgetContactId
|
|
101
|
+
ssSurveyViewController.getSurveyLoadedResponse = self.getSurveyLoadedResponse
|
|
102
|
+
ssSurveyViewController.thankyouTimeout = self.thankyouTimout
|
|
103
|
+
ssSurveyViewController.surveyDelegate = self
|
|
104
|
+
parent.present(ssSurveyViewController, animated: true, completion: nil)
|
|
105
|
+
}))
|
|
106
|
+
alertDialog.addAction(UIAlertAction(title: alertNegativeButton, style: UIAlertAction.Style.cancel, handler: nil))
|
|
107
|
+
parent.present(alertDialog, animated: true)
|
|
108
|
+
|
|
109
|
+
UserDefaults.standard.set(incrementalRepeat ? incrementMultiplier * 2 : 1, forKey: self.incrementMultiplierKey)
|
|
110
|
+
let timeTillNext = repeatInterval * Int64(incrementMultiplier)
|
|
111
|
+
let nextPrompt = currentTime + timeTillNext
|
|
112
|
+
UserDefaults.standard.set(nextPrompt, forKey: promptTimeKey)
|
|
113
|
+
} else {
|
|
114
|
+
self.handleSurveyValidation(response: [
|
|
115
|
+
"active": String(isActive),
|
|
116
|
+
"reason": reason,
|
|
117
|
+
] as [String: AnyObject])
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public func clearSchedule() {
|
|
124
|
+
UserDefaults.standard.removeObject(forKey: incrementMultiplierKey)
|
|
125
|
+
UserDefaults.standard.removeObject(forKey: isAlreadyTakenKey)
|
|
126
|
+
UserDefaults.standard.removeObject(forKey: promptTimeKey)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: Delegate
|
|
130
|
+
public func handleSurveyResponse(response: [String : AnyObject]) {
|
|
131
|
+
UserDefaults.standard.set(true, forKey: isAlreadyTakenKey)
|
|
132
|
+
if surveyDelegate != nil {
|
|
133
|
+
self.surveyDelegate.handleSurveyResponse(response: response)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public func handleSurveyLoaded(response: [String : AnyObject]){
|
|
138
|
+
if surveyDelegate != nil {
|
|
139
|
+
self.surveyDelegate.handleSurveyResponse(response: response)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public func handleSurveyValidation(response: [String : AnyObject]) {
|
|
144
|
+
UserDefaults.standard.set(true, forKey: isAlreadyTakenKey)
|
|
145
|
+
if surveyDelegate != nil {
|
|
146
|
+
self.surveyDelegate.handleSurveyResponse(response: response)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public func handleCloseButtonTap() {
|
|
151
|
+
if surveyDelegate != nil {
|
|
152
|
+
surveyDelegate.handleCloseButtonTap()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#endif
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import Foundation
|
|
3
3
|
import SwiftUI
|
|
4
|
-
import SurveySparrowSdk
|
|
5
4
|
|
|
6
5
|
@objc public class SurveySparrowIonicPlugin: NSObject {
|
|
7
6
|
|
|
@@ -30,7 +29,7 @@ struct FullScreenSurveyView: UIViewControllerRepresentable {
|
|
|
30
29
|
ssSurveyViewController.params = params
|
|
31
30
|
ssSurveyViewController.properties = properties
|
|
32
31
|
ssSurveyViewController.getSurveyLoadedResponse = true
|
|
33
|
-
ssSurveyViewController.surveyDelegate =
|
|
32
|
+
ssSurveyViewController.surveyDelegate = SsDelegate()
|
|
34
33
|
return ssSurveyViewController
|
|
35
34
|
}
|
|
36
35
|
|
|
@@ -55,7 +54,7 @@ struct FullScreenSurveyWithValidation {
|
|
|
55
54
|
let surveyView = SsSurveyView(properties: properties)
|
|
56
55
|
surveyView.loadFullscreenSurvey(
|
|
57
56
|
parent: rootViewController,
|
|
58
|
-
delegate:
|
|
57
|
+
delegate: SsDelegate(),
|
|
59
58
|
domain: domain,
|
|
60
59
|
token: token,
|
|
61
60
|
params: params
|
package/ios/Tests/SurveySparrowIonicPluginPluginTests/SurveySparrowIonicPluginPluginTests.swift
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import XCTest
|
|
2
|
+
import Capacitor
|
|
2
3
|
@testable import SurveySparrowIonicPluginPlugin
|
|
3
4
|
|
|
4
5
|
class SurveySparrowIonicPluginPluginTests: XCTestCase {
|
|
@@ -32,9 +33,11 @@ class SurveySparrowIonicPluginPluginTests: XCTestCase {
|
|
|
32
33
|
"token": token,
|
|
33
34
|
"params": params,
|
|
34
35
|
"properties": properties
|
|
35
|
-
]
|
|
36
|
+
], success: { result, _ in
|
|
36
37
|
XCTAssertNotNil(result)
|
|
37
|
-
}
|
|
38
|
+
}, error: { error in
|
|
39
|
+
XCTAssertNil(error)
|
|
40
|
+
})
|
|
38
41
|
|
|
39
42
|
plugin.loadFullScreenSurvey(mockCall)
|
|
40
43
|
|
|
@@ -56,9 +59,11 @@ class SurveySparrowIonicPluginPluginTests: XCTestCase {
|
|
|
56
59
|
"token": token,
|
|
57
60
|
"params": params,
|
|
58
61
|
"properties": properties
|
|
59
|
-
]
|
|
62
|
+
], success: { result, _ in
|
|
60
63
|
XCTAssertNotNil(result)
|
|
61
|
-
}
|
|
64
|
+
}, error: { error in
|
|
65
|
+
XCTAssertNil(error)
|
|
66
|
+
})
|
|
62
67
|
|
|
63
68
|
plugin.loadFullScreenSurveyWithValidation(mockCall)
|
|
64
69
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "surveysparrow-ionic-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "SurveySparrow SDK enables you to collect feedback from your mobile app. Embed the Classic, Chat & NPS surveys in your ionic application seamlessly with few lines of code.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|