@tyrads.com/tyrads-sdk 3.1.0-beta.0 → 3.2.0-beta.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.
Files changed (135) hide show
  1. package/android/build.gradle +1 -1
  2. package/android/src/main/java/com/tyradssdk/TyradsSdkModule.kt +130 -46
  3. package/ios/Tyrads/AcmoAssets.swift +14 -0
  4. package/ios/Tyrads/ApiHeaders.swift +1 -0
  5. package/ios/Tyrads/Tyrads.swift +184 -57
  6. package/ios/Tyrads/WebViewController.swift +27 -3
  7. package/ios/Tyrads/core/utils/AcmoKeyNames.swift +29 -0
  8. package/ios/Tyrads/core/utils/ColorExtension.swift +55 -0
  9. package/ios/Tyrads/core/utils/Services/LocalizationService.swift +175 -0
  10. package/ios/Tyrads/helpers/device_details.swift +148 -46
  11. package/ios/Tyrads/legal/AcmoPrivacyPage.swift +353 -0
  12. package/ios/Tyrads/legal/PrivacyPageController.swift +31 -0
  13. package/ios/Tyrads/user/AcmoUserUpdatePage.swift +302 -0
  14. package/ios/Tyrads/user/AcmoUsersUpdateController.swift +26 -0
  15. package/ios/Tyrads/user/Repository.swift +89 -0
  16. package/ios/TyradsSdk.mm +15 -3
  17. package/ios/TyradsSdk.swift +101 -46
  18. package/lib/commonjs/acmo/core/helpers/native_methods.js +37 -0
  19. package/lib/commonjs/acmo/core/helpers/native_methods.js.map +1 -0
  20. package/lib/commonjs/acmo/core/helpers/numeral.js +19 -0
  21. package/lib/commonjs/acmo/core/helpers/numeral.js.map +1 -0
  22. package/lib/commonjs/acmo/core/services/localization_service.js +164 -0
  23. package/lib/commonjs/acmo/core/services/localization_service.js.map +1 -0
  24. package/lib/commonjs/acmo/core/storage/storage.js +15 -1
  25. package/lib/commonjs/acmo/core/storage/storage.js.map +1 -1
  26. package/lib/commonjs/acmo/modules/dashboard/components/active_offers_button.js +5 -2
  27. package/lib/commonjs/acmo/modules/dashboard/components/active_offers_button.js.map +1 -1
  28. package/lib/commonjs/acmo/modules/dashboard/components/offer_card.js +3 -4
  29. package/lib/commonjs/acmo/modules/dashboard/components/offer_card.js.map +1 -1
  30. package/lib/commonjs/acmo/modules/dashboard/components/offer_list_item.js +12 -6
  31. package/lib/commonjs/acmo/modules/dashboard/components/offer_list_item.js.map +1 -1
  32. package/lib/commonjs/acmo/modules/dashboard/components/premium_empty_widget.js +6 -2
  33. package/lib/commonjs/acmo/modules/dashboard/components/premium_empty_widget.js.map +1 -1
  34. package/lib/commonjs/acmo/modules/dashboard/components/premium_header.js +4 -4
  35. package/lib/commonjs/acmo/modules/dashboard/components/premium_header.js.map +1 -1
  36. package/lib/commonjs/acmo/modules/dashboard/components/premium_loading.js +4 -11
  37. package/lib/commonjs/acmo/modules/dashboard/components/premium_loading.js.map +1 -1
  38. package/lib/commonjs/acmo/modules/dashboard/repository.js +1 -1
  39. package/lib/commonjs/acmo/modules/dashboard/top_offers.js +15 -0
  40. package/lib/commonjs/acmo/modules/dashboard/top_offers.js.map +1 -1
  41. package/lib/commonjs/acmo/modules/localization/localization_context.js +56 -0
  42. package/lib/commonjs/acmo/modules/localization/localization_context.js.map +1 -0
  43. package/lib/commonjs/index.js +39 -5
  44. package/lib/commonjs/index.js.map +1 -1
  45. package/lib/module/acmo/core/helpers/native_methods.js +33 -0
  46. package/lib/module/acmo/core/helpers/native_methods.js.map +1 -0
  47. package/lib/module/acmo/core/helpers/numeral.js +14 -0
  48. package/lib/module/acmo/core/helpers/numeral.js.map +1 -0
  49. package/lib/module/acmo/core/services/localization_service.js +159 -0
  50. package/lib/module/acmo/core/services/localization_service.js.map +1 -0
  51. package/lib/module/acmo/core/storage/storage.js +13 -0
  52. package/lib/module/acmo/core/storage/storage.js.map +1 -1
  53. package/lib/module/acmo/modules/dashboard/components/active_offers_button.js +5 -2
  54. package/lib/module/acmo/modules/dashboard/components/active_offers_button.js.map +1 -1
  55. package/lib/module/acmo/modules/dashboard/components/offer_card.js +3 -3
  56. package/lib/module/acmo/modules/dashboard/components/offer_card.js.map +1 -1
  57. package/lib/module/acmo/modules/dashboard/components/offer_list_item.js +12 -6
  58. package/lib/module/acmo/modules/dashboard/components/offer_list_item.js.map +1 -1
  59. package/lib/module/acmo/modules/dashboard/components/premium_empty_widget.js +6 -2
  60. package/lib/module/acmo/modules/dashboard/components/premium_empty_widget.js.map +1 -1
  61. package/lib/module/acmo/modules/dashboard/components/premium_header.js +4 -4
  62. package/lib/module/acmo/modules/dashboard/components/premium_header.js.map +1 -1
  63. package/lib/module/acmo/modules/dashboard/components/premium_loading.js +5 -12
  64. package/lib/module/acmo/modules/dashboard/components/premium_loading.js.map +1 -1
  65. package/lib/module/acmo/modules/dashboard/repository.js +1 -1
  66. package/lib/module/acmo/modules/dashboard/top_offers.js +15 -0
  67. package/lib/module/acmo/modules/dashboard/top_offers.js.map +1 -1
  68. package/lib/module/acmo/modules/localization/localization_context.js +45 -0
  69. package/lib/module/acmo/modules/localization/localization_context.js.map +1 -0
  70. package/lib/module/index.js +38 -6
  71. package/lib/module/index.js.map +1 -1
  72. package/lib/typescript/commonjs/src/acmo/core/helpers/native_methods.d.ts +6 -0
  73. package/lib/typescript/commonjs/src/acmo/core/helpers/native_methods.d.ts.map +1 -0
  74. package/lib/typescript/commonjs/src/acmo/core/helpers/numeral.d.ts +2 -0
  75. package/lib/typescript/commonjs/src/acmo/core/helpers/numeral.d.ts.map +1 -0
  76. package/lib/typescript/commonjs/src/acmo/core/services/localization_service.d.ts +18 -0
  77. package/lib/typescript/commonjs/src/acmo/core/services/localization_service.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/src/acmo/core/storage/storage.d.ts +1 -0
  79. package/lib/typescript/commonjs/src/acmo/core/storage/storage.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/active_offers_button.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/offer_card.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/offer_list_item.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_empty_widget.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_header.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_loading.d.ts +0 -1
  86. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_loading.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/src/acmo/modules/dashboard/top_offers.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/src/acmo/modules/localization/localization_context.d.ts +14 -0
  89. package/lib/typescript/commonjs/src/acmo/modules/localization/localization_context.d.ts.map +1 -0
  90. package/lib/typescript/commonjs/src/index.d.ts +4 -0
  91. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  92. package/lib/typescript/module/src/acmo/core/helpers/native_methods.d.ts +6 -0
  93. package/lib/typescript/module/src/acmo/core/helpers/native_methods.d.ts.map +1 -0
  94. package/lib/typescript/module/src/acmo/core/helpers/numeral.d.ts +2 -0
  95. package/lib/typescript/module/src/acmo/core/helpers/numeral.d.ts.map +1 -0
  96. package/lib/typescript/module/src/acmo/core/services/localization_service.d.ts +18 -0
  97. package/lib/typescript/module/src/acmo/core/services/localization_service.d.ts.map +1 -0
  98. package/lib/typescript/module/src/acmo/core/storage/storage.d.ts +1 -0
  99. package/lib/typescript/module/src/acmo/core/storage/storage.d.ts.map +1 -1
  100. package/lib/typescript/module/src/acmo/modules/dashboard/components/active_offers_button.d.ts.map +1 -1
  101. package/lib/typescript/module/src/acmo/modules/dashboard/components/offer_card.d.ts.map +1 -1
  102. package/lib/typescript/module/src/acmo/modules/dashboard/components/offer_list_item.d.ts.map +1 -1
  103. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_empty_widget.d.ts.map +1 -1
  104. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_header.d.ts.map +1 -1
  105. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_loading.d.ts +0 -1
  106. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_loading.d.ts.map +1 -1
  107. package/lib/typescript/module/src/acmo/modules/dashboard/top_offers.d.ts.map +1 -1
  108. package/lib/typescript/module/src/acmo/modules/localization/localization_context.d.ts +14 -0
  109. package/lib/typescript/module/src/acmo/modules/localization/localization_context.d.ts.map +1 -0
  110. package/lib/typescript/module/src/index.d.ts +4 -0
  111. package/lib/typescript/module/src/index.d.ts.map +1 -1
  112. package/package.json +2 -6
  113. package/src/acmo/core/helpers/native_methods.ts +43 -0
  114. package/src/acmo/core/helpers/numeral.ts +14 -0
  115. package/src/acmo/core/services/localization_service.ts +200 -0
  116. package/src/acmo/core/storage/storage.ts +14 -0
  117. package/src/acmo/modules/dashboard/components/active_offers_button.tsx +3 -2
  118. package/src/acmo/modules/dashboard/components/offer_card.tsx +3 -3
  119. package/src/acmo/modules/dashboard/components/offer_list_item.tsx +8 -5
  120. package/src/acmo/modules/dashboard/components/premium_empty_widget.tsx +5 -2
  121. package/src/acmo/modules/dashboard/components/premium_header.tsx +6 -5
  122. package/src/acmo/modules/dashboard/components/premium_loading.tsx +2 -8
  123. package/src/acmo/modules/dashboard/repository.ts +1 -1
  124. package/src/acmo/modules/dashboard/top_offers.tsx +18 -3
  125. package/src/acmo/modules/localization/localization_context.tsx +52 -0
  126. package/src/index.tsx +63 -18
  127. package/lib/commonjs/i18n.js +0 -112
  128. package/lib/commonjs/i18n.js.map +0 -1
  129. package/lib/module/i18n.js +0 -107
  130. package/lib/module/i18n.js.map +0 -1
  131. package/lib/typescript/commonjs/src/i18n.d.ts +0 -3
  132. package/lib/typescript/commonjs/src/i18n.d.ts.map +0 -1
  133. package/lib/typescript/module/src/i18n.d.ts +0 -3
  134. package/lib/typescript/module/src/i18n.d.ts.map +0 -1
  135. package/src/i18n.ts +0 -115
@@ -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
- private var apiKey: String = ""
15
- private var apiSecret: String = ""
16
- private var encKey: String?
17
- private var publisherUserID: String = ""
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
- private var currentLanguage: String = "en"
20
- private var newUser: Bool = false
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
- private func log(_ message: String) {
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
- self.currentLanguage = Locale.current.languageCode ?? ""
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
- let identifierType = "IDFA"
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
- advertisingId = ""
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: Locale.current.languageCode ?? "en",
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
- public func showOffers(_ launchMode: Int = 3, route: String? = nil, campaignID: Int? = nil) async {
155
- // self.initializationWait.wait()
156
- await ensureInitialized()
157
- var components = URLComponents()
158
- components.scheme = "https"
159
- components.host = "sdk.tyrads.com"
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
- components.queryItems = [
162
- URLQueryItem(name: "token", value: self.token),
163
- URLQueryItem(name: "to", value: campaignID != nil ? "\(route ?? "")/\(campaignID!)" : route)
164
- ]
165
- var urlString: String = ""
166
- if let url = components.url {
167
- urlString = url.absoluteString
168
- print(urlString)
169
- } else {
170
- print("Failed to create URL with components: \(components)")
171
- }
172
-
173
- do {
174
- guard let url = URL(string: urlString) else {
175
- throw NSError(domain: "TyradsSdk", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
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
- switch launchMode {
179
- case 1, 2:
180
- DispatchQueue.main.async {
181
- let acmoVC = AcmoWebViewController(url: url)
182
- acmoVC.modalPresentationStyle = .fullScreen
183
-
184
- if let rootViewController = UIApplication.shared.windows.first?.rootViewController {
185
- rootViewController.present(acmoVC, animated: true, completion: nil)
186
- }
187
- }
188
- case 3:
189
- DispatchQueue.main.async {
190
- UIApplication.shared.open(url, options: [:], completionHandler: nil)
191
- }
192
- default:
193
- DispatchQueue.main.async {
194
- UIApplication.shared.open(url, options: [:], completionHandler: nil)
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
- } catch {
198
- print("An error occurred: \(error)")
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
- public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
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["languageCode"] as? String {
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
+ }