@tyrads.com/tyrads-sdk 3.1.0-beta.0 → 3.2.0
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/android/build.gradle +1 -1
- package/android/src/main/java/com/tyradssdk/TyradsSdkModule.kt +130 -46
- package/ios/Tyrads/AcmoAssets.swift +14 -0
- package/ios/Tyrads/ApiHeaders.swift +1 -0
- package/ios/Tyrads/Tyrads.swift +184 -57
- package/ios/Tyrads/WebViewController.swift +27 -3
- package/ios/Tyrads/core/utils/AcmoKeyNames.swift +29 -0
- package/ios/Tyrads/core/utils/ColorExtension.swift +55 -0
- package/ios/Tyrads/core/utils/Services/LocalizationService.swift +175 -0
- package/ios/Tyrads/helpers/device_details.swift +148 -46
- package/ios/Tyrads/legal/AcmoPrivacyPage.swift +353 -0
- package/ios/Tyrads/legal/PrivacyPageController.swift +31 -0
- package/ios/Tyrads/user/AcmoUserUpdatePage.swift +302 -0
- package/ios/Tyrads/user/AcmoUsersUpdateController.swift +26 -0
- package/ios/Tyrads/user/Repository.swift +89 -0
- package/ios/TyradsSdk.mm +15 -3
- package/ios/TyradsSdk.swift +101 -46
- package/lib/commonjs/acmo/core/helpers/native_methods.js +37 -0
- package/lib/commonjs/acmo/core/helpers/native_methods.js.map +1 -0
- package/lib/commonjs/acmo/core/helpers/numeral.js +19 -0
- package/lib/commonjs/acmo/core/helpers/numeral.js.map +1 -0
- package/lib/commonjs/acmo/core/services/localization_service.js +164 -0
- package/lib/commonjs/acmo/core/services/localization_service.js.map +1 -0
- package/lib/commonjs/acmo/core/storage/storage.js +15 -1
- package/lib/commonjs/acmo/core/storage/storage.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/active_offers_button.js +5 -2
- package/lib/commonjs/acmo/modules/dashboard/components/active_offers_button.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/custom_scroller.js +1 -2
- package/lib/commonjs/acmo/modules/dashboard/components/custom_scroller.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/custom_shimmer.js +1 -2
- package/lib/commonjs/acmo/modules/dashboard/components/custom_shimmer.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/offer_card.js +4 -6
- package/lib/commonjs/acmo/modules/dashboard/components/offer_card.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/offer_list_item.js +14 -9
- package/lib/commonjs/acmo/modules/dashboard/components/offer_list_item.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/premium_empty_widget.js +6 -2
- package/lib/commonjs/acmo/modules/dashboard/components/premium_empty_widget.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/premium_header.js +4 -4
- package/lib/commonjs/acmo/modules/dashboard/components/premium_header.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/components/premium_loading.js +4 -11
- package/lib/commonjs/acmo/modules/dashboard/components/premium_loading.js.map +1 -1
- package/lib/commonjs/acmo/modules/dashboard/repository.js +1 -1
- package/lib/commonjs/acmo/modules/dashboard/top_offers.js +16 -2
- package/lib/commonjs/acmo/modules/dashboard/top_offers.js.map +1 -1
- package/lib/commonjs/acmo/modules/localization/localization_context.js +55 -0
- package/lib/commonjs/acmo/modules/localization/localization_context.js.map +1 -0
- package/lib/commonjs/index.js +38 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/acmo/core/helpers/native_methods.js +33 -0
- package/lib/module/acmo/core/helpers/native_methods.js.map +1 -0
- package/lib/module/acmo/core/helpers/numeral.js +14 -0
- package/lib/module/acmo/core/helpers/numeral.js.map +1 -0
- package/lib/module/acmo/core/services/localization_service.js +159 -0
- package/lib/module/acmo/core/services/localization_service.js.map +1 -0
- package/lib/module/acmo/core/storage/storage.js +13 -0
- package/lib/module/acmo/core/storage/storage.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/components/active_offers_button.js +5 -2
- package/lib/module/acmo/modules/dashboard/components/active_offers_button.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/components/offer_card.js +3 -3
- package/lib/module/acmo/modules/dashboard/components/offer_card.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/components/offer_list_item.js +14 -9
- package/lib/module/acmo/modules/dashboard/components/offer_list_item.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/components/premium_empty_widget.js +6 -2
- package/lib/module/acmo/modules/dashboard/components/premium_empty_widget.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/components/premium_header.js +4 -4
- package/lib/module/acmo/modules/dashboard/components/premium_header.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/components/premium_loading.js +5 -12
- package/lib/module/acmo/modules/dashboard/components/premium_loading.js.map +1 -1
- package/lib/module/acmo/modules/dashboard/repository.js +1 -1
- package/lib/module/acmo/modules/dashboard/top_offers.js +15 -0
- package/lib/module/acmo/modules/dashboard/top_offers.js.map +1 -1
- package/lib/module/acmo/modules/localization/localization_context.js +45 -0
- package/lib/module/acmo/modules/localization/localization_context.js.map +1 -0
- package/lib/module/index.js +38 -6
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/acmo/core/helpers/native_methods.d.ts +6 -0
- package/lib/typescript/commonjs/src/acmo/core/helpers/native_methods.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/acmo/core/helpers/numeral.d.ts +2 -0
- package/lib/typescript/commonjs/src/acmo/core/helpers/numeral.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/acmo/core/services/localization_service.d.ts +18 -0
- package/lib/typescript/commonjs/src/acmo/core/services/localization_service.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/acmo/core/storage/storage.d.ts +1 -0
- package/lib/typescript/commonjs/src/acmo/core/storage/storage.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/active_offers_button.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/offer_card.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/offer_list_item.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_empty_widget.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_header.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_loading.d.ts +0 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_loading.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/repository.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/dashboard/top_offers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/acmo/modules/localization/localization_context.d.ts +14 -0
- package/lib/typescript/commonjs/src/acmo/modules/localization/localization_context.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +4 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/core/helpers/native_methods.d.ts +6 -0
- package/lib/typescript/module/src/acmo/core/helpers/native_methods.d.ts.map +1 -0
- package/lib/typescript/module/src/acmo/core/helpers/numeral.d.ts +2 -0
- package/lib/typescript/module/src/acmo/core/helpers/numeral.d.ts.map +1 -0
- package/lib/typescript/module/src/acmo/core/services/localization_service.d.ts +18 -0
- package/lib/typescript/module/src/acmo/core/services/localization_service.d.ts.map +1 -0
- package/lib/typescript/module/src/acmo/core/storage/storage.d.ts +1 -0
- package/lib/typescript/module/src/acmo/core/storage/storage.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/active_offers_button.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/offer_card.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/offer_list_item.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_empty_widget.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_header.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_loading.d.ts +0 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_loading.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/repository.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/dashboard/top_offers.d.ts.map +1 -1
- package/lib/typescript/module/src/acmo/modules/localization/localization_context.d.ts +14 -0
- package/lib/typescript/module/src/acmo/modules/localization/localization_context.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +4 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +7 -10
- package/src/acmo/core/helpers/native_methods.ts +43 -0
- package/src/acmo/core/helpers/numeral.ts +14 -0
- package/src/acmo/core/services/localization_service.ts +200 -0
- package/src/acmo/core/storage/storage.ts +14 -0
- package/src/acmo/modules/dashboard/components/active_offers_button.tsx +3 -2
- package/src/acmo/modules/dashboard/components/offer_card.tsx +3 -3
- package/src/acmo/modules/dashboard/components/offer_list_item.tsx +9 -7
- package/src/acmo/modules/dashboard/components/premium_empty_widget.tsx +5 -2
- package/src/acmo/modules/dashboard/components/premium_header.tsx +6 -5
- package/src/acmo/modules/dashboard/components/premium_loading.tsx +2 -8
- package/src/acmo/modules/dashboard/repository.ts +1 -1
- package/src/acmo/modules/dashboard/top_offers.tsx +18 -3
- package/src/acmo/modules/localization/localization_context.tsx +52 -0
- package/src/index.tsx +63 -18
- package/lib/commonjs/i18n.js +0 -112
- package/lib/commonjs/i18n.js.map +0 -1
- package/lib/module/i18n.js +0 -107
- package/lib/module/i18n.js.map +0 -1
- package/lib/typescript/commonjs/src/i18n.d.ts +0 -3
- package/lib/typescript/commonjs/src/i18n.d.ts.map +0 -1
- package/lib/typescript/module/src/i18n.d.ts +0 -3
- package/lib/typescript/module/src/i18n.d.ts.map +0 -1
- package/src/i18n.ts +0 -115
package/ios/Tyrads/Tyrads.swift
CHANGED
@@ -3,7 +3,7 @@ import UIKit
|
|
3
3
|
import AppTrackingTransparency
|
4
4
|
import AdSupport
|
5
5
|
import WebKit
|
6
|
-
|
6
|
+
import Combine
|
7
7
|
|
8
8
|
|
9
9
|
/// The TyradsSdk class provides methods for configuring the SDK and displaying offers.
|
@@ -11,13 +11,22 @@ public class Tyrads : NSObject {
|
|
11
11
|
/// Shared instance of the TyradsSdk.
|
12
12
|
public static let instance = Tyrads()
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
internal var apiKey: String = ""
|
15
|
+
internal var apiSecret: String = ""
|
16
|
+
internal var encKey: String?
|
17
|
+
internal var publisherUserID: String = ""
|
18
|
+
internal var mainColor: String?
|
18
19
|
private var token: String = ""
|
19
|
-
|
20
|
-
|
20
|
+
internal var currentLanguage: String = "en" {
|
21
|
+
didSet {
|
22
|
+
if oldValue != currentLanguage {
|
23
|
+
languagePublisher.send(currentLanguage)
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
public let languagePublisher = PassthroughSubject<String, Never>()
|
29
|
+
internal var newUser: Bool = false
|
21
30
|
private var loginData: AcmoInitModel?
|
22
31
|
var initializationWait = DispatchSemaphore(value: 0)
|
23
32
|
private var initializationContinuation: CheckedContinuation<Void, Never>?
|
@@ -30,7 +39,7 @@ public class Tyrads : NSObject {
|
|
30
39
|
}
|
31
40
|
}
|
32
41
|
|
33
|
-
|
42
|
+
internal func log(_ message: String) {
|
34
43
|
if debugMode {
|
35
44
|
NSLog(message)
|
36
45
|
}
|
@@ -49,13 +58,18 @@ public class Tyrads : NSObject {
|
|
49
58
|
/// - Parameters:
|
50
59
|
/// - apiKey: The API key provided by Tyrads.
|
51
60
|
/// - secretKey: The secret key provided by Tyrads.
|
52
|
-
@objc public func configure( apiKey: String, secretKey: String, encKey: String? = nil, debugMode: Bool = false) {
|
61
|
+
@objc public func configure( apiKey: String, secretKey: String, encKey: String? = nil, debugMode: Bool = false) async -> String {
|
53
62
|
self.apiKey = apiKey
|
54
63
|
self.apiSecret = secretKey
|
55
64
|
self.encKey = encKey
|
56
65
|
self._isSecure = (encKey != nil)
|
57
66
|
self.debugMode = debugMode
|
58
|
-
|
67
|
+
|
68
|
+
let savedLocale = UserDefaults.standard.string(forKey: "locale")
|
69
|
+
let deviceLocale = Locale.current.languageCode ?? "en"
|
70
|
+
self.currentLanguage = savedLocale ?? deviceLocale
|
71
|
+
await LocalizationService.shared.initialize(locale: self.currentLanguage)
|
72
|
+
return self.currentLanguage
|
59
73
|
}
|
60
74
|
|
61
75
|
/// Logs in the user with the provided user ID or retrieves the user ID from UserDefaults.
|
@@ -63,8 +77,9 @@ public class Tyrads : NSObject {
|
|
63
77
|
/// - Parameter userID: Optional. The user ID to log in with. If nil, the SDK will attempt to retrieve the user ID from UserDefaults.
|
64
78
|
public func loginUser(_ userID: String? = nil) async throws -> ApiHeaders? {
|
65
79
|
let userId = userID ?? UserDefaults.standard.string(forKey: "acmo-tyrads-sdk-user-id") ?? ""
|
66
|
-
|
80
|
+
var identifierType = "IDFA"
|
67
81
|
var advertisingId = ""
|
82
|
+
let deviceDetails = getDeviceDetails()
|
68
83
|
|
69
84
|
if #available(iOS 14, *) {
|
70
85
|
self.log("Requesting tracking authorization for iOS 14+")
|
@@ -74,7 +89,8 @@ public class Tyrads : NSObject {
|
|
74
89
|
advertisingId = ASIdentifierManager.shared().advertisingIdentifier.uuidString
|
75
90
|
self.log("Tracking authorized. Advertising ID: \(advertisingId)")
|
76
91
|
case .denied, .restricted, .notDetermined:
|
77
|
-
|
92
|
+
advertisingId = "\(deviceDetails["deviceId"] ?? UUID().uuidString)"
|
93
|
+
identifierType = "OTHER"
|
78
94
|
self.log("Tracking not authorized or restricted")
|
79
95
|
@unknown default:
|
80
96
|
self.log("Unknown tracking status")
|
@@ -84,7 +100,6 @@ public class Tyrads : NSObject {
|
|
84
100
|
self.log("iOS version < 14. Advertising ID: \(advertisingId)")
|
85
101
|
}
|
86
102
|
|
87
|
-
let deviceDetails = getDeviceDetails()
|
88
103
|
let fd: [String: Any] = [
|
89
104
|
"publisherUserId": userId,
|
90
105
|
"platform": "iOS",
|
@@ -115,7 +130,7 @@ public class Tyrads : NSObject {
|
|
115
130
|
self.log("Failed to serialize request body: \(error)")
|
116
131
|
throw error
|
117
132
|
}
|
118
|
-
|
133
|
+
let hasAccepted = UserDefaults.standard.bool(forKey: "\(AcmoKeyNames.PRIVACY_ACCEPTED_FOR_USER_ID)\(Tyrads.instance.publisherUserID)")
|
119
134
|
// Use the async URLSession API to get data
|
120
135
|
let (data, _) = try await URLSession.shared.data(for: request)
|
121
136
|
|
@@ -132,6 +147,8 @@ public class Tyrads : NSObject {
|
|
132
147
|
self.newUser = acmoInitModel.data.newRegisteredUser
|
133
148
|
self.token = acmoInitModel.data.token
|
134
149
|
self.log("Login successful. Publisher User ID: \(self.publisherUserID), New User: \(self.newUser)")
|
150
|
+
self.mainColor = acmoInitModel.data.publisherApp.mainColor
|
151
|
+
|
135
152
|
|
136
153
|
let headers = await ApiHeaders(
|
137
154
|
xApiKey: self.apiKey,
|
@@ -140,10 +157,11 @@ public class Tyrads : NSObject {
|
|
140
157
|
xSdkPlatform: AcmoConfig.SDK_PLATFORM,
|
141
158
|
xSdkVersion: AcmoConfig.SDK_VERSION,
|
142
159
|
userAgent: UIDevice.current.systemName + "/" + UIDevice.current.systemVersion,
|
143
|
-
languageCode:
|
160
|
+
languageCode: self.currentLanguage,
|
144
161
|
premiumColor: acmoInitModel.data.publisherApp.premiumColor,
|
145
162
|
headerColor: acmoInitModel.data.publisherApp.headerColor,
|
146
|
-
mainColor: acmoInitModel.data.publisherApp.mainColor
|
163
|
+
mainColor: acmoInitModel.data.publisherApp.mainColor,
|
164
|
+
privacyAccepted: hasAccepted
|
147
165
|
)
|
148
166
|
return headers
|
149
167
|
}
|
@@ -151,51 +169,160 @@ public class Tyrads : NSObject {
|
|
151
169
|
|
152
170
|
|
153
171
|
|
154
|
-
|
155
|
-
// self.initializationWait.wait()
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
172
|
+
public func showOffers(_ launchMode: Int = 3, route: String? = nil, campaignID: Int? = nil) async {
|
173
|
+
// self.initializationWait.wait()
|
174
|
+
let skipUserUpdate = getSkipUserUpdate()
|
175
|
+
await ensureInitialized()
|
176
|
+
var components = URLComponents()
|
177
|
+
components.scheme = "https"
|
178
|
+
components.host = "sdk.tyrads.com"
|
160
179
|
// components.path = "/\(route ?? "")"
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
180
|
+
components.queryItems = [
|
181
|
+
URLQueryItem(name: "token", value: self.token),
|
182
|
+
URLQueryItem(name: "to", value: campaignID != nil ? "\(route ?? "")/\(campaignID!)" : route),
|
183
|
+
URLQueryItem(name: "lang", value: self.currentLanguage),
|
184
|
+
URLQueryItem(name: "skipUserInfo", value: "\(skipUserUpdate)")
|
185
|
+
]
|
186
|
+
var urlString: String = ""
|
187
|
+
if let url = components.url {
|
188
|
+
urlString = url.absoluteString
|
189
|
+
print(urlString)
|
190
|
+
} else {
|
191
|
+
print("Failed to create URL with components: \(components)")
|
192
|
+
}
|
193
|
+
|
194
|
+
do {
|
195
|
+
guard let url = URL(string: urlString) else {
|
196
|
+
throw NSError(domain: "TyradsSdk", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
|
197
|
+
}
|
198
|
+
|
199
|
+
switch launchMode {
|
200
|
+
case 1, 2:
|
201
|
+
DispatchQueue.main.async {
|
202
|
+
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else { return }
|
203
|
+
|
204
|
+
let showWebView: () -> Void = {
|
205
|
+
let webVC = AcmoWebViewController(url: url)
|
206
|
+
webVC.modalPresentationStyle = .fullScreen
|
207
|
+
rootVC.present(webVC, animated: true)
|
208
|
+
}
|
209
|
+
|
210
|
+
let showUserUpdateOrWeb: () -> Void = {
|
211
|
+
if Tyrads.instance.newUser {
|
212
|
+
let userUpdateVC = AcmoUsersUpdateController(onSubmit: {
|
213
|
+
showWebView()
|
214
|
+
})
|
215
|
+
userUpdateVC.isModalInPresentation = false
|
216
|
+
rootVC.present(userUpdateVC, animated: true)
|
217
|
+
} else {
|
218
|
+
showWebView()
|
176
219
|
}
|
220
|
+
}
|
221
|
+
|
222
|
+
let hasAccepted = Tyrads.instance.isPrivacyAccepted()
|
223
|
+
|
224
|
+
if launchMode == 1 || launchMode == 2 {
|
225
|
+
if !hasAccepted {
|
226
|
+
let privacyController = AcmoPrivacyPolicyController(onAccept: {
|
227
|
+
showUserUpdateOrWeb()
|
228
|
+
})
|
229
|
+
privacyController.modalPresentationStyle = .fullScreen
|
230
|
+
rootVC.present(privacyController, animated: true)
|
231
|
+
} else {
|
232
|
+
showUserUpdateOrWeb()
|
233
|
+
}
|
234
|
+
} else {
|
235
|
+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
236
|
+
}
|
237
|
+
}
|
238
|
+
case 3:
|
239
|
+
DispatchQueue.main.async {
|
240
|
+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
241
|
+
}
|
242
|
+
default:
|
243
|
+
DispatchQueue.main.async {
|
244
|
+
UIApplication.shared.open(url, options: [:], completionHandler: nil)
|
245
|
+
}
|
246
|
+
}
|
247
|
+
} catch {
|
248
|
+
print("An error occurred: \(error)")
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
public func setNewUser(_ newValue: Bool){
|
253
|
+
self.newUser = newValue
|
254
|
+
}
|
255
|
+
|
256
|
+
public func setSkipUserUpdate(_ newValue: Bool){
|
257
|
+
let key = "\(AcmoKeyNames.SKIP_FOR_USER_ID)\(self.publisherUserID)"
|
258
|
+
UserDefaults.standard.set(newValue, forKey: key)
|
259
|
+
}
|
260
|
+
public func getSkipUserUpdate() -> Bool{
|
261
|
+
let key = "\(AcmoKeyNames.SKIP_FOR_USER_ID)\(self.publisherUserID)"
|
262
|
+
return (UserDefaults.standard.bool(forKey: key) || newUser == false)
|
263
|
+
}
|
177
264
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
265
|
+
public func changeLanguage(_ lang: String) async {
|
266
|
+
self.currentLanguage = lang
|
267
|
+
UserDefaults.standard.set(lang, forKey: "locale")
|
268
|
+
await LocalizationService.shared.changeLanguage(locale: lang)
|
269
|
+
}
|
270
|
+
|
271
|
+
public func isPrivacyAccepted() -> Bool {
|
272
|
+
let key = "\(AcmoKeyNames.PRIVACY_ACCEPTED_FOR_USER_ID)\(self.publisherUserID)"
|
273
|
+
return UserDefaults.standard.bool(forKey: key)
|
274
|
+
}
|
275
|
+
|
276
|
+
public func checkOnboardingProcess() async -> Bool {
|
277
|
+
await ensureInitialized()
|
278
|
+
|
279
|
+
return await withCheckedContinuation { continuation in
|
280
|
+
Task { @MainActor in
|
281
|
+
guard let rootVC = UIApplication.shared.windows.first?.rootViewController else {
|
282
|
+
continuation.resume(returning: false)
|
283
|
+
return
|
284
|
+
}
|
285
|
+
|
286
|
+
let finishFlow: () -> Void = {
|
287
|
+
continuation.resume(returning: true)
|
288
|
+
}
|
289
|
+
|
290
|
+
let showUserUpdate: () -> Void = {
|
291
|
+
let userUpdateVC = AcmoUsersUpdateController(onSubmit: {
|
292
|
+
rootVC.dismiss(animated: true) {
|
293
|
+
print("User Update submitted, view dismissed.")
|
294
|
+
finishFlow()
|
196
295
|
}
|
197
|
-
|
198
|
-
|
296
|
+
})
|
297
|
+
userUpdateVC.isModalInPresentation = false
|
298
|
+
rootVC.present(userUpdateVC, animated: true)
|
199
299
|
}
|
300
|
+
|
301
|
+
let hasAcceptedPrivacy = self.isPrivacyAccepted()
|
302
|
+
let isNewUser = Tyrads.instance.newUser
|
303
|
+
|
304
|
+
print("showPrivacyFlowAndDismissOnComplete: Privacy Accepted: \(hasAcceptedPrivacy), New User: \(isNewUser)")
|
305
|
+
|
306
|
+
if !hasAcceptedPrivacy {
|
307
|
+
let privacyController = AcmoPrivacyPolicyController(onAccept: {
|
308
|
+
if isNewUser {
|
309
|
+
showUserUpdate()
|
310
|
+
} else {
|
311
|
+
rootVC.dismiss(animated: true) {
|
312
|
+
print("Privacy accepted, user is not new, view dismissed.")
|
313
|
+
finishFlow()
|
314
|
+
}
|
315
|
+
}
|
316
|
+
})
|
317
|
+
privacyController.modalPresentationStyle = .fullScreen
|
318
|
+
rootVC.present(privacyController, animated: true)
|
319
|
+
} else if isNewUser {
|
320
|
+
showUserUpdate()
|
321
|
+
} else {
|
322
|
+
print("Privacy accepted and user not new. No flow presented.")
|
323
|
+
finishFlow()
|
324
|
+
}
|
325
|
+
}
|
200
326
|
}
|
327
|
+
}
|
201
328
|
}
|
@@ -5,6 +5,7 @@ class AcmoWebViewController: UIViewController, WKNavigationDelegate, WKScriptMes
|
|
5
5
|
|
6
6
|
var webView: WKWebView!
|
7
7
|
var initialURL: URL
|
8
|
+
private var hasLoadedWithSkipParameter = false
|
8
9
|
|
9
10
|
private let internalDomains: [String] = ["sdk.tyrads.com", "acmo.in"]
|
10
11
|
|
@@ -48,11 +49,30 @@ class AcmoWebViewController: UIViewController, WKNavigationDelegate, WKScriptMes
|
|
48
49
|
webView.isInspectable = true
|
49
50
|
}
|
50
51
|
|
51
|
-
webView.load(URLRequest(url: initialURL))
|
52
|
+
// webView.load(URLRequest(url: initialURL))
|
53
|
+
loadURL()
|
52
54
|
log("WebView loaded URL: \(initialURL.absoluteString)")
|
53
55
|
}
|
56
|
+
private func loadURL() {
|
57
|
+
var components = URLComponents(url: initialURL, resolvingAgainstBaseURL: false)
|
58
|
+
let skipUserUpdate = Tyrads.instance.getSkipUserUpdate()
|
59
|
+
let skipValue = skipUserUpdate ? "true" : "false"
|
60
|
+
|
61
|
+
components?.queryItems?.removeAll { $0.name == "skipUserInfo" }
|
62
|
+
components?.queryItems?.append(URLQueryItem(name: "skipUserInfo", value: skipValue))
|
63
|
+
|
64
|
+
if let updatedURL = components?.url {
|
65
|
+
log("Loading URL with skipUserInfo=\(skipValue): \(updatedURL.absoluteString)")
|
66
|
+
let request = URLRequest(url: updatedURL)
|
67
|
+
webView.load(request)
|
68
|
+
hasLoadedWithSkipParameter = true
|
69
|
+
} else {
|
70
|
+
log("Failed to create updated URL, loading original")
|
71
|
+
webView.load(URLRequest(url: initialURL))
|
72
|
+
}
|
73
|
+
}
|
54
74
|
|
55
|
-
|
75
|
+
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
56
76
|
guard message.name == "clickHandler",
|
57
77
|
let messageDict = message.body as? [String: Any] else {
|
58
78
|
log("Received unknown message or invalid format.")
|
@@ -72,8 +92,12 @@ class AcmoWebViewController: UIViewController, WKNavigationDelegate, WKScriptMes
|
|
72
92
|
}
|
73
93
|
|
74
94
|
case "changeLanguage":
|
75
|
-
if let langCode = messageDict["
|
95
|
+
if let langCode = messageDict["value"] as? String {
|
76
96
|
log("Action: changeLanguage received. Language Code: \(langCode)")
|
97
|
+
Task{
|
98
|
+
await Tyrads.instance.changeLanguage(langCode)
|
99
|
+
}
|
100
|
+
// let value = UserDefaults.standard.string(forKey: "locale")
|
77
101
|
}
|
78
102
|
|
79
103
|
default:
|
@@ -0,0 +1,29 @@
|
|
1
|
+
//
|
2
|
+
// AcmoKeyNames.swift
|
3
|
+
// Pods
|
4
|
+
//
|
5
|
+
// Created by Basharat Mehdi on 30/09/25.
|
6
|
+
//
|
7
|
+
|
8
|
+
struct AcmoKeyNames {
|
9
|
+
static let PREFIX = "acmo_tyrads_sdk_"
|
10
|
+
|
11
|
+
static let INTRODUCTION_COMPLETE = "\(PREFIX)introduction_complete"
|
12
|
+
static let LOGGED_IN = "\(PREFIX)logged_in"
|
13
|
+
static let USER_ID = "\(PREFIX)user_id"
|
14
|
+
static let API_KEY = "\(PREFIX)api_key"
|
15
|
+
static let API_SECRET = "\(PREFIX)api_secret"
|
16
|
+
static let ENCRYPTION_KEY = "\(PREFIX)encryption_key"
|
17
|
+
static let PLAY_INTEGRITY_TOKEN = "\(PREFIX)play_integrity_token"
|
18
|
+
static let USERNAME = "\(PREFIX)username"
|
19
|
+
static let FCM_TOKEN = "\(PREFIX)fcm_token"
|
20
|
+
static let USER_DATA = "\(PREFIX)user_data"
|
21
|
+
static let ADVERTISING_ID = "\(PREFIX)advertising_id"
|
22
|
+
static let TRACKED_CAMPAIGNS_FOR_USER_ID = "\(PREFIX)tracked_campaigns_for_user_id_"
|
23
|
+
static let PLAY_PER_MINUTE_PACKAGES = "\(PREFIX)play_per_minute_packages"
|
24
|
+
static let PRIVACY_ACCEPTED_FOR_USER_ID = "\(PREFIX)privacy_accepted_for_user_id_"
|
25
|
+
static let SKIP_FOR_USER_ID = "\(PREFIX)skip_for_user_id_"
|
26
|
+
static let CUSTOM_AD_ID = "\(PREFIX)custom_ad_id"
|
27
|
+
static let LANGUAGE = "\(PREFIX)language"
|
28
|
+
static let TOKEN = "\(PREFIX)token"
|
29
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
//
|
2
|
+
// ColorExtension.swift
|
3
|
+
// Pods
|
4
|
+
//
|
5
|
+
// Created by Basharat Mehdi on 01/10/25.
|
6
|
+
//
|
7
|
+
import SwiftUI
|
8
|
+
import UIKit
|
9
|
+
|
10
|
+
extension Color {
|
11
|
+
init(hex: String) {
|
12
|
+
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
|
13
|
+
var int: UInt64 = 0
|
14
|
+
Scanner(string: hex).scanHexInt64(&int)
|
15
|
+
let r, g, b: UInt64
|
16
|
+
switch hex.count {
|
17
|
+
case 3:
|
18
|
+
(r, g, b) = ((int >> 8) * 17, ((int >> 4) & 0xF) * 17, (int & 0xF) * 17)
|
19
|
+
case 6:
|
20
|
+
(r, g, b) = (int >> 16, (int >> 8) & 0xFF, int & 0xFF)
|
21
|
+
default:
|
22
|
+
(r, g, b) = (0, 0, 0)
|
23
|
+
}
|
24
|
+
self.init(
|
25
|
+
.sRGB,
|
26
|
+
red: Double(r) / 255,
|
27
|
+
green: Double(g) / 255,
|
28
|
+
blue: Double(b) / 255,
|
29
|
+
opacity: 1
|
30
|
+
)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
extension UIColor {
|
35
|
+
convenience init(hex: String, alpha: CGFloat = 1.0) {
|
36
|
+
var hexSanitized = hex.trimmingCharacters(in: .whitespacesAndNewlines)
|
37
|
+
hexSanitized = hexSanitized.hasPrefix("#") ? String(hexSanitized.dropFirst()) : hexSanitized
|
38
|
+
|
39
|
+
var rgb: UInt64 = 0
|
40
|
+
Scanner(string: hexSanitized).scanHexInt64(&rgb)
|
41
|
+
|
42
|
+
let length = hexSanitized.count
|
43
|
+
let r, g, b: CGFloat
|
44
|
+
|
45
|
+
if length == 6 {
|
46
|
+
r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0
|
47
|
+
g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0
|
48
|
+
b = CGFloat(rgb & 0x0000FF) / 255.0
|
49
|
+
} else {
|
50
|
+
r = 0; g = 0; b = 0
|
51
|
+
}
|
52
|
+
|
53
|
+
self.init(red: r, green: g, blue: b, alpha: alpha)
|
54
|
+
}
|
55
|
+
}
|
@@ -0,0 +1,175 @@
|
|
1
|
+
//
|
2
|
+
// LocalizationService.swift
|
3
|
+
// Pods
|
4
|
+
//
|
5
|
+
// Created by Basharat Mehdi on 02/10/25.
|
6
|
+
//
|
7
|
+
|
8
|
+
import Foundation
|
9
|
+
|
10
|
+
final class LocalizationService {
|
11
|
+
|
12
|
+
static let shared = LocalizationService()
|
13
|
+
private init() {}
|
14
|
+
|
15
|
+
private var translations: [String: Any] = [:]
|
16
|
+
private var supportedLocales: [String] = []
|
17
|
+
|
18
|
+
private let userDefaults = UserDefaults.standard
|
19
|
+
private let fallbackLocale = "en"
|
20
|
+
|
21
|
+
// MARK: - Public Interface
|
22
|
+
func initialize(locale: String) async {
|
23
|
+
await loadTranslations(locale: locale)
|
24
|
+
}
|
25
|
+
|
26
|
+
func changeLanguage(locale: String, force: Bool = false) async {
|
27
|
+
await loadTranslations(locale: locale, force: force)
|
28
|
+
}
|
29
|
+
|
30
|
+
func translate(_ key: String, args: [String: String]? = nil) -> String {
|
31
|
+
let keys = key.split(separator: ".").map { String($0) }
|
32
|
+
var current: Any? = translations
|
33
|
+
|
34
|
+
for k in keys {
|
35
|
+
if let dict = current as? [String: Any], let next = dict[k] {
|
36
|
+
current = next
|
37
|
+
} else {
|
38
|
+
return key
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
guard var result = current as? String else {
|
43
|
+
return key
|
44
|
+
}
|
45
|
+
|
46
|
+
if let args = args {
|
47
|
+
for (argKey, argValue) in args {
|
48
|
+
let regex = try? NSRegularExpression(pattern: "\\{\(argKey)\\}", options: .caseInsensitive)
|
49
|
+
if let regex = regex {
|
50
|
+
result = regex.stringByReplacingMatches(in: result, range: NSRange(result.startIndex..., in: result), withTemplate: argValue)
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
return result
|
56
|
+
}
|
57
|
+
|
58
|
+
// MARK: - Private Loading Methods
|
59
|
+
private func loadTranslations(locale: String, force: Bool = false) async {
|
60
|
+
let hasUpdate = await checkForUpdate(locale: locale, force: force)
|
61
|
+
|
62
|
+
if !hasUpdate {
|
63
|
+
if let cachedData = userDefaults.data(forKey: "translations_\(locale)"),
|
64
|
+
let json = try? JSONSerialization.jsonObject(with: cachedData, options: []) as? [String: Any] {
|
65
|
+
translations = json
|
66
|
+
return
|
67
|
+
}
|
68
|
+
}
|
69
|
+
|
70
|
+
await fetchTranslations(locale: locale, force: force)
|
71
|
+
}
|
72
|
+
|
73
|
+
private func fetchTranslations(locale: String, force: Bool) async {
|
74
|
+
var currentLocale = locale
|
75
|
+
if !supportedLocales.contains(currentLocale) {
|
76
|
+
currentLocale = fallbackLocale
|
77
|
+
}
|
78
|
+
|
79
|
+
|
80
|
+
let urlString = "\(AcmoConfig.BASE_URL)translations/\(currentLocale)?force=\(force)&format=nested"
|
81
|
+
guard let url = URL(string: urlString) else {
|
82
|
+
print("LocalizationService: Invalid URL for translations")
|
83
|
+
return
|
84
|
+
}
|
85
|
+
|
86
|
+
var request = URLRequest(url: url)
|
87
|
+
request.httpMethod = "GET"
|
88
|
+
request.setTyradsHeaders(with: Tyrads.instance)
|
89
|
+
|
90
|
+
do {
|
91
|
+
let (data, response) = try await URLSession.shared.data(for: request)
|
92
|
+
|
93
|
+
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
|
94
|
+
print("LocalizationService: Failed to load translations: \((response as? HTTPURLResponse)?.statusCode ?? -1)")
|
95
|
+
return
|
96
|
+
}
|
97
|
+
|
98
|
+
if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
99
|
+
translations = jsonResponse
|
100
|
+
|
101
|
+
userDefaults.set(data, forKey: "translations_\(currentLocale)")
|
102
|
+
} else {
|
103
|
+
print("LocalizationService: Failed to parse JSON response.")
|
104
|
+
}
|
105
|
+
} catch {
|
106
|
+
print("LocalizationService: Network error fetching translations: \(error)")
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
private func checkForUpdate(locale: String, force: Bool) async -> Bool {
|
111
|
+
let urlString = "\(AcmoConfig.BASE_URL)translations/version?force=\(force)"
|
112
|
+
guard let url = URL(string: urlString) else {
|
113
|
+
print("LocalizationService: Invalid URL for version check")
|
114
|
+
return false
|
115
|
+
}
|
116
|
+
var request = URLRequest(url: url)
|
117
|
+
request.httpMethod = "GET"
|
118
|
+
request.setTyradsHeaders(with: Tyrads.instance)
|
119
|
+
|
120
|
+
do {
|
121
|
+
let (data, response) = try await URLSession.shared.data(for: request)
|
122
|
+
|
123
|
+
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
|
124
|
+
print("LocalizationService: Failed to get version check response.")
|
125
|
+
return false
|
126
|
+
}
|
127
|
+
|
128
|
+
struct VersionData: Decodable {
|
129
|
+
let code: String
|
130
|
+
let sha256: String
|
131
|
+
}
|
132
|
+
struct VersionResponse: Decodable {
|
133
|
+
let data: [VersionData]
|
134
|
+
}
|
135
|
+
|
136
|
+
let jsonResponse = try JSONDecoder().decode(VersionResponse.self, from: data)
|
137
|
+
let versionData = jsonResponse.data
|
138
|
+
|
139
|
+
supportedLocales = versionData.map { $0.code }
|
140
|
+
|
141
|
+
guard supportedLocales.contains(locale) else {
|
142
|
+
return false
|
143
|
+
}
|
144
|
+
|
145
|
+
guard let currentLocaleVersion = versionData.first(where: { $0.code == locale }) else {
|
146
|
+
return false
|
147
|
+
}
|
148
|
+
|
149
|
+
let currentSha256 = currentLocaleVersion.sha256
|
150
|
+
let cachedVersion = userDefaults.string(forKey: "cached_version_\(locale)")
|
151
|
+
|
152
|
+
if currentSha256 != cachedVersion {
|
153
|
+
userDefaults.set(currentSha256, forKey: "cached_version_\(locale)")
|
154
|
+
return true
|
155
|
+
}
|
156
|
+
|
157
|
+
} catch {
|
158
|
+
print("LocalizationService: Error checking for update: \(error)")
|
159
|
+
}
|
160
|
+
|
161
|
+
return false
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
extension URLRequest {
|
166
|
+
mutating func setTyradsHeaders(with sdk: Tyrads) {
|
167
|
+
self.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
168
|
+
|
169
|
+
self.setValue(sdk.apiKey, forHTTPHeaderField: "X-API-Key")
|
170
|
+
self.setValue(sdk.apiSecret, forHTTPHeaderField: "X-API-Secret")
|
171
|
+
|
172
|
+
self.setValue(AcmoConfig.SDK_PLATFORM, forHTTPHeaderField: "X-SDK-Platform")
|
173
|
+
self.setValue(AcmoConfig.SDK_VERSION, forHTTPHeaderField: "X-SDK-Version")
|
174
|
+
}
|
175
|
+
}
|