@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.
Files changed (141) 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/custom_scroller.js +1 -2
  29. package/lib/commonjs/acmo/modules/dashboard/components/custom_scroller.js.map +1 -1
  30. package/lib/commonjs/acmo/modules/dashboard/components/custom_shimmer.js +1 -2
  31. package/lib/commonjs/acmo/modules/dashboard/components/custom_shimmer.js.map +1 -1
  32. package/lib/commonjs/acmo/modules/dashboard/components/offer_card.js +4 -6
  33. package/lib/commonjs/acmo/modules/dashboard/components/offer_card.js.map +1 -1
  34. package/lib/commonjs/acmo/modules/dashboard/components/offer_list_item.js +14 -9
  35. package/lib/commonjs/acmo/modules/dashboard/components/offer_list_item.js.map +1 -1
  36. package/lib/commonjs/acmo/modules/dashboard/components/premium_empty_widget.js +6 -2
  37. package/lib/commonjs/acmo/modules/dashboard/components/premium_empty_widget.js.map +1 -1
  38. package/lib/commonjs/acmo/modules/dashboard/components/premium_header.js +4 -4
  39. package/lib/commonjs/acmo/modules/dashboard/components/premium_header.js.map +1 -1
  40. package/lib/commonjs/acmo/modules/dashboard/components/premium_loading.js +4 -11
  41. package/lib/commonjs/acmo/modules/dashboard/components/premium_loading.js.map +1 -1
  42. package/lib/commonjs/acmo/modules/dashboard/repository.js +1 -1
  43. package/lib/commonjs/acmo/modules/dashboard/top_offers.js +16 -2
  44. package/lib/commonjs/acmo/modules/dashboard/top_offers.js.map +1 -1
  45. package/lib/commonjs/acmo/modules/localization/localization_context.js +55 -0
  46. package/lib/commonjs/acmo/modules/localization/localization_context.js.map +1 -0
  47. package/lib/commonjs/index.js +38 -5
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/module/acmo/core/helpers/native_methods.js +33 -0
  50. package/lib/module/acmo/core/helpers/native_methods.js.map +1 -0
  51. package/lib/module/acmo/core/helpers/numeral.js +14 -0
  52. package/lib/module/acmo/core/helpers/numeral.js.map +1 -0
  53. package/lib/module/acmo/core/services/localization_service.js +159 -0
  54. package/lib/module/acmo/core/services/localization_service.js.map +1 -0
  55. package/lib/module/acmo/core/storage/storage.js +13 -0
  56. package/lib/module/acmo/core/storage/storage.js.map +1 -1
  57. package/lib/module/acmo/modules/dashboard/components/active_offers_button.js +5 -2
  58. package/lib/module/acmo/modules/dashboard/components/active_offers_button.js.map +1 -1
  59. package/lib/module/acmo/modules/dashboard/components/offer_card.js +3 -3
  60. package/lib/module/acmo/modules/dashboard/components/offer_card.js.map +1 -1
  61. package/lib/module/acmo/modules/dashboard/components/offer_list_item.js +14 -9
  62. package/lib/module/acmo/modules/dashboard/components/offer_list_item.js.map +1 -1
  63. package/lib/module/acmo/modules/dashboard/components/premium_empty_widget.js +6 -2
  64. package/lib/module/acmo/modules/dashboard/components/premium_empty_widget.js.map +1 -1
  65. package/lib/module/acmo/modules/dashboard/components/premium_header.js +4 -4
  66. package/lib/module/acmo/modules/dashboard/components/premium_header.js.map +1 -1
  67. package/lib/module/acmo/modules/dashboard/components/premium_loading.js +5 -12
  68. package/lib/module/acmo/modules/dashboard/components/premium_loading.js.map +1 -1
  69. package/lib/module/acmo/modules/dashboard/repository.js +1 -1
  70. package/lib/module/acmo/modules/dashboard/top_offers.js +15 -0
  71. package/lib/module/acmo/modules/dashboard/top_offers.js.map +1 -1
  72. package/lib/module/acmo/modules/localization/localization_context.js +45 -0
  73. package/lib/module/acmo/modules/localization/localization_context.js.map +1 -0
  74. package/lib/module/index.js +38 -6
  75. package/lib/module/index.js.map +1 -1
  76. package/lib/typescript/commonjs/src/acmo/core/helpers/native_methods.d.ts +6 -0
  77. package/lib/typescript/commonjs/src/acmo/core/helpers/native_methods.d.ts.map +1 -0
  78. package/lib/typescript/commonjs/src/acmo/core/helpers/numeral.d.ts +2 -0
  79. package/lib/typescript/commonjs/src/acmo/core/helpers/numeral.d.ts.map +1 -0
  80. package/lib/typescript/commonjs/src/acmo/core/services/localization_service.d.ts +18 -0
  81. package/lib/typescript/commonjs/src/acmo/core/services/localization_service.d.ts.map +1 -0
  82. package/lib/typescript/commonjs/src/acmo/core/storage/storage.d.ts +1 -0
  83. package/lib/typescript/commonjs/src/acmo/core/storage/storage.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/active_offers_button.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/offer_card.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/offer_list_item.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_empty_widget.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_header.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_loading.d.ts +0 -1
  90. package/lib/typescript/commonjs/src/acmo/modules/dashboard/components/premium_loading.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/src/acmo/modules/dashboard/repository.d.ts.map +1 -1
  92. package/lib/typescript/commonjs/src/acmo/modules/dashboard/top_offers.d.ts.map +1 -1
  93. package/lib/typescript/commonjs/src/acmo/modules/localization/localization_context.d.ts +14 -0
  94. package/lib/typescript/commonjs/src/acmo/modules/localization/localization_context.d.ts.map +1 -0
  95. package/lib/typescript/commonjs/src/index.d.ts +4 -0
  96. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  97. package/lib/typescript/module/src/acmo/core/helpers/native_methods.d.ts +6 -0
  98. package/lib/typescript/module/src/acmo/core/helpers/native_methods.d.ts.map +1 -0
  99. package/lib/typescript/module/src/acmo/core/helpers/numeral.d.ts +2 -0
  100. package/lib/typescript/module/src/acmo/core/helpers/numeral.d.ts.map +1 -0
  101. package/lib/typescript/module/src/acmo/core/services/localization_service.d.ts +18 -0
  102. package/lib/typescript/module/src/acmo/core/services/localization_service.d.ts.map +1 -0
  103. package/lib/typescript/module/src/acmo/core/storage/storage.d.ts +1 -0
  104. package/lib/typescript/module/src/acmo/core/storage/storage.d.ts.map +1 -1
  105. package/lib/typescript/module/src/acmo/modules/dashboard/components/active_offers_button.d.ts.map +1 -1
  106. package/lib/typescript/module/src/acmo/modules/dashboard/components/offer_card.d.ts.map +1 -1
  107. package/lib/typescript/module/src/acmo/modules/dashboard/components/offer_list_item.d.ts.map +1 -1
  108. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_empty_widget.d.ts.map +1 -1
  109. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_header.d.ts.map +1 -1
  110. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_loading.d.ts +0 -1
  111. package/lib/typescript/module/src/acmo/modules/dashboard/components/premium_loading.d.ts.map +1 -1
  112. package/lib/typescript/module/src/acmo/modules/dashboard/repository.d.ts.map +1 -1
  113. package/lib/typescript/module/src/acmo/modules/dashboard/top_offers.d.ts.map +1 -1
  114. package/lib/typescript/module/src/acmo/modules/localization/localization_context.d.ts +14 -0
  115. package/lib/typescript/module/src/acmo/modules/localization/localization_context.d.ts.map +1 -0
  116. package/lib/typescript/module/src/index.d.ts +4 -0
  117. package/lib/typescript/module/src/index.d.ts.map +1 -1
  118. package/package.json +7 -10
  119. package/src/acmo/core/helpers/native_methods.ts +43 -0
  120. package/src/acmo/core/helpers/numeral.ts +14 -0
  121. package/src/acmo/core/services/localization_service.ts +200 -0
  122. package/src/acmo/core/storage/storage.ts +14 -0
  123. package/src/acmo/modules/dashboard/components/active_offers_button.tsx +3 -2
  124. package/src/acmo/modules/dashboard/components/offer_card.tsx +3 -3
  125. package/src/acmo/modules/dashboard/components/offer_list_item.tsx +9 -7
  126. package/src/acmo/modules/dashboard/components/premium_empty_widget.tsx +5 -2
  127. package/src/acmo/modules/dashboard/components/premium_header.tsx +6 -5
  128. package/src/acmo/modules/dashboard/components/premium_loading.tsx +2 -8
  129. package/src/acmo/modules/dashboard/repository.ts +1 -1
  130. package/src/acmo/modules/dashboard/top_offers.tsx +18 -3
  131. package/src/acmo/modules/localization/localization_context.tsx +52 -0
  132. package/src/index.tsx +63 -18
  133. package/lib/commonjs/i18n.js +0 -112
  134. package/lib/commonjs/i18n.js.map +0 -1
  135. package/lib/module/i18n.js +0 -107
  136. package/lib/module/i18n.js.map +0 -1
  137. package/lib/typescript/commonjs/src/i18n.d.ts +0 -3
  138. package/lib/typescript/commonjs/src/i18n.d.ts.map +0 -1
  139. package/lib/typescript/module/src/i18n.d.ts +0 -3
  140. package/lib/typescript/module/src/i18n.d.ts.map +0 -1
  141. package/src/i18n.ts +0 -115
@@ -1,58 +1,160 @@
1
+ //
2
+ // device_details.swift
3
+ // Pods
4
+ //
5
+ // Created by Basharat Mehdi on 01/08/25.
6
+ //
1
7
  import UIKit
8
+ import Foundation
2
9
 
3
10
  func getDeviceDetails() -> [String: Any] {
4
- var fd = [String: Any]()
5
-
6
- let device = UIDevice.current
7
- let bundle = Bundle.main
8
- let locale = Locale.current
9
-
10
- // Device basic info
11
- fd["deviceId"] = device.identifierForVendor?.uuidString ?? "Unknown"
12
- fd["device"] = device.model.lowercased().contains("ipad") ? "iPad" : "iPhone"
13
- fd["deviceName"] = device.name
14
- fd["brand"] = "Apple"
15
- fd["model"] = device.model
16
- fd["baseOs"] = device.systemName
17
- fd["releaseVersion"] = device.systemVersion
18
-
19
- // App info
20
- fd["version"] = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
21
- fd["build"] = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
22
- fd["package"] = bundle.bundleIdentifier ?? "Unknown"
23
- fd["installerStore"] = "App Store"
24
-
25
- fd["osLang"] = locale.languageCode ?? "en"
26
-
27
- // security and virtual check
28
- fd["rooted"] = isJailBroken()
29
- fd["virtual"] = isRunningOnSimulator()
30
-
31
- return fd
11
+ var fd = [String: Any]()
12
+
13
+ let device = UIDevice.current
14
+ let bundle = Bundle.main
15
+ let locale = Locale.current
16
+
17
+ // Device basic info
18
+ fd["deviceId"] = device.identifierForVendor?.uuidString ?? "Unknown"
19
+ fd["device"] = device.model.lowercased().contains("ipad") ? "iPad" : "iPhone"
20
+ fd["deviceName"] = device.name
21
+ fd["brand"] = "Apple"
22
+ fd["model"] = device.model
23
+ fd["baseOs"] = device.systemName
24
+ fd["releaseVersion"] = device.systemVersion
25
+ fd["modelName"] = getModelName()
26
+
27
+ fd["product"] = "Darwin"
28
+ fd["platform"] = "iOS"
29
+ fd["apiVersion"] = AcmoConfig.API_VERSION
30
+ fd["sdkVersion"] = AcmoConfig.SDK_VERSION
31
+ fd["sdkPlatform"] = AcmoConfig.SDK_PLATFORM
32
+
33
+ // App info
34
+ fd["version"] = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
35
+ fd["build"] = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
36
+ fd["package"] = bundle.bundleIdentifier ?? "Unknown"
37
+ fd["installerStore"] = "App Store"
38
+
39
+ fd["osLang"] = locale.languageCode ?? "en"
40
+
41
+ // security and virtual check
42
+ fd["rooted"] = isJailBroken()
43
+ fd["virtual"] = isRunningOnSimulator()
44
+
45
+ return fd
32
46
  }
33
47
 
34
48
  func isRunningOnSimulator() -> Bool {
35
- return ProcessInfo().environment["SIMULATOR_DEVICE_NAME"] != nil
49
+ return ProcessInfo().environment["SIMULATOR_DEVICE_NAME"] != nil
36
50
  }
37
51
 
38
52
  func isJailBroken() -> Bool {
39
- #if targetEnvironment(simulator)
40
- return false
41
- #else
42
- let paths = [
43
- "/Applications/Cydia.app",
44
- "/Library/MobileSubstrate/MobileSubstrate.dylib",
45
- "/bin/bash",
46
- "/usr/sbin/sshd",
47
- "/etc/apt",
48
- "/private/var/lib/apt/"
49
- ]
50
- for path in paths {
51
- if FileManager.default.fileExists(atPath: path) {
52
- return true
53
- }
53
+ #if targetEnvironment(simulator)
54
+ return false
55
+ #else
56
+ let paths = [
57
+ "/Applications/Cydia.app",
58
+ "/Library/MobileSubstrate/MobileSubstrate.dylib",
59
+ "/bin/bash",
60
+ "/usr/sbin/sshd",
61
+ "/etc/apt",
62
+ "/private/var/lib/apt/"
63
+ ]
64
+ for path in paths {
65
+ if FileManager.default.fileExists(atPath: path) {
66
+ return true
54
67
  }
55
- return false
56
- #endif
68
+ }
69
+ return false
70
+ #endif
71
+ }
72
+
73
+ func deviceIdentifier() -> String {
74
+ #if targetEnvironment(simulator)
75
+ if let simIdentifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"] {
76
+ return simIdentifier
77
+ }
78
+ return "Simulator"
79
+ #else
80
+ var systemInfo = utsname()
81
+ uname(&systemInfo)
82
+ let machineMirror = Mirror(reflecting: systemInfo.machine)
83
+ let identifier = machineMirror.children.reduce("") { identifier, element in
84
+ guard let value = element.value as? Int8, value != 0 else { return identifier }
85
+ return identifier + String(UnicodeScalar(UInt8(value)))
86
+ }
87
+ return identifier
88
+ #endif
57
89
  }
58
90
 
91
+
92
+ func getModelName() -> String {
93
+ let identifier = deviceIdentifier()
94
+
95
+ let iphoneModelMapping: [String: String] = [
96
+ "iPhone1,1": "iPhone",
97
+ "iPhone1,2": "iPhone 3G",
98
+ "iPhone2,1": "iPhone 3GS",
99
+ "iPhone3,1": "iPhone 4",
100
+ "iPhone3,2": "iPhone 4 GSM Rev A",
101
+ "iPhone3,3": "iPhone 4 CDMA",
102
+ "iPhone4,1": "iPhone 4S",
103
+ "iPhone5,1": "iPhone 5 (GSM)",
104
+ "iPhone5,2": "iPhone 5 (GSM+CDMA)",
105
+ "iPhone5,3": "iPhone 5C (GSM)",
106
+ "iPhone5,4": "iPhone 5C (Global)",
107
+ "iPhone6,1": "iPhone 5S (GSM)",
108
+ "iPhone6,2": "iPhone 5S (Global)",
109
+ "iPhone7,1": "iPhone 6 Plus",
110
+ "iPhone7,2": "iPhone 6",
111
+ "iPhone8,1": "iPhone 6s",
112
+ "iPhone8,2": "iPhone 6s Plus",
113
+ "iPhone8,4": "iPhone SE (1st Gen)",
114
+ "iPhone9,1": "iPhone 7",
115
+ "iPhone9,2": "iPhone 7 Plus",
116
+ "iPhone9,3": "iPhone 7",
117
+ "iPhone9,4": "iPhone 7 Plus",
118
+ "iPhone10,1": "iPhone 8",
119
+ "iPhone10,2": "iPhone 8 Plus",
120
+ "iPhone10,3": "iPhone X Global",
121
+ "iPhone10,4": "iPhone 8",
122
+ "iPhone10,5": "iPhone 8 Plus",
123
+ "iPhone10,6": "iPhone X GSM",
124
+ "iPhone11,2": "iPhone XS",
125
+ "iPhone11,4": "iPhone XS Max",
126
+ "iPhone11,6": "iPhone XS Max Global",
127
+ "iPhone11,8": "iPhone XR",
128
+ "iPhone12,1": "iPhone 11",
129
+ "iPhone12,3": "iPhone 11 Pro",
130
+ "iPhone12,5": "iPhone 11 Pro Max",
131
+ "iPhone12,8": "iPhone SE (2nd Gen)",
132
+ "iPhone13,1": "iPhone 12 Mini",
133
+ "iPhone13,2": "iPhone 12",
134
+ "iPhone13,3": "iPhone 12 Pro",
135
+ "iPhone13,4": "iPhone 12 Pro Max",
136
+ "iPhone14,2": "iPhone 13 Pro",
137
+ "iPhone14,3": "iPhone 13 Pro Max",
138
+ "iPhone14,4": "iPhone 13 Mini",
139
+ "iPhone14,5": "iPhone 13",
140
+ "iPhone14,6": "iPhone SE (3rd Gen)",
141
+ "iPhone14,7": "iPhone 14",
142
+ "iPhone14,8": "iPhone 14 Plus",
143
+ "iPhone15,2": "iPhone 14 Pro",
144
+ "iPhone15,3": "iPhone 14 Pro Max",
145
+ "iPhone15,4": "iPhone 15",
146
+ "iPhone15,5": "iPhone 15 Plus",
147
+ "iPhone16,1": "iPhone 15 Pro",
148
+ "iPhone16,2": "iPhone 15 Pro Max",
149
+ "iPhone17,1": "iPhone 16 Pro",
150
+ "iPhone17,2": "iPhone 16 Pro Max",
151
+ "iPhone17,3": "iPhone 16",
152
+ "iPhone17,4": "iPhone 16 Plus",
153
+ "iPhone17,5": "iPhone 16e",
154
+ "iPhone18,1": "iPhone 17 Pro",
155
+ "iPhone18,2": "iPhone 17 Pro Max",
156
+ "iPhone18,3": "iPhone 17",
157
+ "iPhone18,4": "iPhone Air"
158
+ ]
159
+ return iphoneModelMapping[identifier] ?? identifier
160
+ }
@@ -0,0 +1,353 @@
1
+ //
2
+ // AcmoPrivacyPage.swift
3
+ // Pods
4
+ //
5
+ // Created by Basharat Mehdi on 29/09/25.
6
+ //
7
+
8
+ import SwiftUI
9
+ import UIKit
10
+
11
+ struct AcmoPrivacyPolicyPage: View {
12
+ @Environment(\.presentationMode) var presentationMode
13
+
14
+ var localization: LocalizationService = LocalizationService.shared
15
+
16
+ var onAccept: (() -> Void)?
17
+
18
+ public init(onAccept: (() -> Void)? = nil) {
19
+ self.onAccept = onAccept
20
+ }
21
+
22
+ var body: some View {
23
+ ZStack {
24
+ if #available(iOS 14.0, *) {
25
+ Color.white.ignoresSafeArea()
26
+ } else {
27
+ Color.white
28
+ }
29
+
30
+ VStack {
31
+ HStack {
32
+ Spacer()
33
+ Button(action: {
34
+ presentationMode.wrappedValue.dismiss()
35
+ }) {
36
+ Image(systemName: "xmark")
37
+ .foregroundColor(Color.gray.opacity(0.6))
38
+ .font(.system(size: 20))
39
+ .padding()
40
+ }
41
+ }
42
+
43
+ BodyView(localization: localization)
44
+
45
+ Spacer().frame(height: 24)
46
+
47
+ ScrollView {
48
+ VStack {
49
+ InfoView(localization: localization)
50
+ Spacer().frame(height: 155)
51
+ }
52
+ .frame(maxWidth: .infinity)
53
+ }
54
+ .frame(maxHeight: UIScreen.main.bounds.height / 2)
55
+
56
+ Spacer()
57
+ }
58
+ .padding(.horizontal, 30)
59
+
60
+ VStack {
61
+ Spacer()
62
+ VStack(spacing: 16) {
63
+ Info2View(localization: localization)
64
+ TwoButtons(
65
+ onAccept: {
66
+ UserDefaults.standard.set(true, forKey: "\(AcmoKeyNames.PRIVACY_ACCEPTED_FOR_USER_ID)\(Tyrads.instance.publisherUserID)")
67
+ presentationMode.wrappedValue.dismiss()
68
+ onAccept?()
69
+ },
70
+ onReject: {
71
+ presentationMode.wrappedValue.dismiss()
72
+ },
73
+ localization: localization
74
+ )
75
+ }
76
+ .frame(width: UIScreen.main.bounds.width, height: 140)
77
+ .padding(16)
78
+ .background(Color.white)
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ // MARK: - Body Section
85
+ struct BodyView: View {
86
+ let localization: LocalizationService
87
+
88
+ func decodeBase64Image(_ base64: String) -> UIImage? {
89
+ guard let data = Data(base64Encoded: base64) else { return nil }
90
+ return UIImage(data: data)
91
+ }
92
+
93
+ var body: some View {
94
+ VStack(spacing: 12) {
95
+ Text(localization.translate("data.initialization.intro.title"))
96
+ .font(.system(size: 16, weight: .semibold))
97
+ .multilineTextAlignment(.center)
98
+
99
+ if let image = decodeBase64Image(AcmoAssets.privacyBannerBase64) {
100
+ Image(uiImage: image)
101
+ .resizable()
102
+ .scaledToFit()
103
+ .frame(height: 160)
104
+ } else {
105
+ Text("Image not found")
106
+ }
107
+
108
+ Text(localization.translate("data.initialization.intro.subtitle"))
109
+ .font(.system(size: 16, weight: .medium))
110
+ .multilineTextAlignment(.center)
111
+ .lineSpacing(3)
112
+ }
113
+ .frame(maxWidth: UIScreen.main.bounds.width * 0.6)
114
+ }
115
+ }
116
+
117
+ // MARK: - Info Section (scrollable legal text)
118
+ struct InfoView: View {
119
+ let localization: LocalizationService
120
+ var body: some View {
121
+ Text(.init(localization.translate("data.initialization.legal.explanation")))
122
+ .font(.system(size: 14))
123
+ .padding(10)
124
+ .foregroundColor(Color.black.opacity(0.65))
125
+ .multilineTextAlignment(.leading)
126
+ }
127
+ func wrapLinks(text: String) -> String {
128
+ let urlRegexPattern = "(https?:\\/\\/[^\\s]+)"
129
+
130
+ do {
131
+ let regex = try NSRegularExpression(pattern: urlRegexPattern, options: [])
132
+ let range = NSRange(text.startIndex..., in: text)
133
+ let replacedText = regex.stringByReplacingMatches(
134
+ in: text,
135
+ range: range,
136
+ withTemplate: "https://tyrads.com/tyrsdk-privacy-policy"
137
+ )
138
+
139
+ return replacedText
140
+
141
+ } catch {
142
+ print("Error creating regex: \(error.localizedDescription)")
143
+ return text
144
+ }
145
+ }
146
+ }
147
+
148
+ // MARK: - Info2 Section
149
+ struct Info2View: View {
150
+ let localization: LocalizationService
151
+ var body: some View {
152
+ StyledTextView(text: localization.translate("data.initialization.intro.label.iHaveRead"))
153
+ .padding(10)
154
+ .multilineTextAlignment(.leading)
155
+ .frame(width: UIScreen.main.bounds.width * 0.88)
156
+ }
157
+ }
158
+
159
+
160
+ // MARK: - Buttons
161
+ struct TwoButtons: View {
162
+ var onAccept: () -> Void
163
+ var onReject: () -> Void
164
+ let localization: LocalizationService
165
+
166
+ var body: some View {
167
+ VStack(spacing: 8) {
168
+ Button(action: onAccept) {
169
+ Text(localization
170
+ .translate("data.initialization.intro.cta.accept"))
171
+ .font(.system(size: 15, weight: .medium))
172
+ .foregroundColor(.white)
173
+ .frame(width: 160, height: 35)
174
+ .background(Color(hex: Tyrads.instance.mainColor ?? "#000000"))
175
+ .cornerRadius(5)
176
+ }
177
+
178
+ Button(action: onReject) {
179
+ Text(localization
180
+ .translate("data.initialization.intro.cta.reject"))
181
+ .font(.system(size: 15, weight: .medium))
182
+ .foregroundColor(Color(red: 179/255, green: 44/255, blue: 44/255))
183
+ .frame(width: 150, height: 35)
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // MARK: StyledText View
190
+ struct StyledTextView: View {
191
+ let text: String
192
+ let fontSize: CGFloat = 14
193
+ let fontWeight: Font.Weight = .regular
194
+ let textColor: Color = .black
195
+ let linkColor: Color = Color(hex: Tyrads.instance.mainColor ?? "#000000")
196
+
197
+ var body: some View {
198
+ WrappingHStack(components: parseTextComponents(), fontSize: fontSize, fontWeight: fontWeight, textColor: textColor, linkColor: linkColor)
199
+ .font(.system(size: fontSize, weight: fontWeight))
200
+ }
201
+
202
+ private func parseTextComponents() -> [TextComponent] {
203
+ var components: [TextComponent] = []
204
+ let pattern = "<([a-z]+)>(.*?)</\\1>"
205
+
206
+ do {
207
+ let regex = try NSRegularExpression(pattern: pattern, options: [])
208
+ let nsString = text as NSString
209
+ let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: nsString.length))
210
+
211
+ if matches.isEmpty {
212
+ return [TextComponent(text: text, isLink: false)]
213
+ }
214
+
215
+ var lastIndex = 0
216
+
217
+ for match in matches {
218
+ if match.range.location > lastIndex {
219
+ let beforeRange = NSRange(location: lastIndex, length: match.range.location - lastIndex)
220
+ let beforeText = nsString.substring(with: beforeRange)
221
+ components.append(TextComponent(text: beforeText, isLink: false))
222
+ }
223
+
224
+ let tagNameRange = match.range(at: 1)
225
+ let contentRange = match.range(at: 2)
226
+
227
+ let tagName = nsString.substring(with: tagNameRange)
228
+ let content = nsString.substring(with: contentRange)
229
+
230
+ components.append(TextComponent(text: content, isLink: true, tag: tagName))
231
+ lastIndex = match.range.location + match.range.length
232
+ }
233
+
234
+ if lastIndex < nsString.length {
235
+ let remainingText = nsString.substring(from: lastIndex)
236
+ components.append(TextComponent(text: remainingText, isLink: false))
237
+ }
238
+
239
+ } catch {
240
+ components = [TextComponent(text: text, isLink: false)]
241
+ }
242
+
243
+ return components
244
+ }
245
+
246
+ private func handleLinkTap(for tag: String?) {
247
+ guard let tag = tag else { return }
248
+
249
+ switch tag {
250
+ case "tos":
251
+ if let url = URL(string: "https://tyrads.com/tyrsdk-terms-of-service/") {
252
+ UIApplication.shared.open(url)
253
+ }
254
+ case "pp":
255
+ if let url = URL(string: "https://tyrads.com/tyrsdk-privacy-policy/") {
256
+ UIApplication.shared.open(url)
257
+ }
258
+ default:
259
+ break
260
+ }
261
+ }
262
+ }
263
+
264
+ struct WrappingHStack: View {
265
+ let components: [TextComponent]
266
+ let fontSize: CGFloat
267
+ let fontWeight: Font.Weight
268
+ let textColor: Color
269
+ let linkColor: Color
270
+
271
+ var body: some View {
272
+ GeometryReader { geometry in
273
+ self.generateContent(in: geometry)
274
+ }
275
+ }
276
+
277
+ private func generateContent(in geometry: GeometryProxy) -> some View {
278
+ var width = CGFloat.zero
279
+ var height = CGFloat.zero
280
+
281
+ return ZStack(alignment: .topLeading) {
282
+ ForEach(components, id: \.id) { component in
283
+ self.item(for: component)
284
+ .alignmentGuide(.leading) { dimension in
285
+ if abs(width - dimension.width) > geometry.size.width {
286
+ width = 0
287
+ height -= dimension.height
288
+ }
289
+ let result = width
290
+ if component.id == components.last?.id {
291
+ width = 0
292
+ } else {
293
+ width -= dimension.width
294
+ }
295
+ return result
296
+ }
297
+ .alignmentGuide(.top) { dimension in
298
+ let result = height
299
+ if component.id == components.last?.id {
300
+ height = 0
301
+ }
302
+ return result
303
+ }
304
+ }
305
+ }
306
+ }
307
+
308
+ @ViewBuilder
309
+ private func item(for component: TextComponent) -> some View {
310
+ if component.isLink {
311
+ Text(component.text)
312
+ .font(.system(size: fontSize, weight: fontWeight))
313
+ .foregroundColor(linkColor)
314
+ .onTapGesture {
315
+ handleLinkTap(for: component.tag)
316
+ }
317
+ } else {
318
+ Text(component.text)
319
+ .font(.system(size: fontSize, weight: fontWeight))
320
+ .foregroundColor(textColor)
321
+ }
322
+ }
323
+
324
+ private func handleLinkTap(for tag: String?) {
325
+ guard let tag = tag else { return }
326
+
327
+ switch tag {
328
+ case "tos":
329
+ if let url = URL(string: "https://tyrads.com/tyrsdk-terms-of-service/") {
330
+ UIApplication.shared.open(url)
331
+ }
332
+ case "pp":
333
+ if let url = URL(string: "https://tyrads.com/tyrsdk-privacy-policy/") {
334
+ UIApplication.shared.open(url)
335
+ }
336
+ default:
337
+ break
338
+ }
339
+ }
340
+ }
341
+
342
+ struct TextComponent: Identifiable {
343
+ let id = UUID()
344
+ let text: String
345
+ let isLink: Bool
346
+ let tag: String?
347
+
348
+ init(text: String, isLink: Bool, tag: String? = nil) {
349
+ self.text = text
350
+ self.isLink = isLink
351
+ self.tag = tag
352
+ }
353
+ }
@@ -0,0 +1,31 @@
1
+ //
2
+ // AcmoPrivacyPolicyController.swift
3
+ // Pods
4
+ //
5
+ // Created by Basharat Mehdi on 29/09/25.
6
+ //
7
+
8
+ import UIKit
9
+ import SwiftUI
10
+
11
+ public typealias PolicyCompletion = () -> Void
12
+
13
+ class AcmoPrivacyPolicyController: UIHostingController<AcmoPrivacyPolicyPage> {
14
+
15
+ init(onAccept: PolicyCompletion? = nil) {
16
+
17
+ let rootView = AcmoPrivacyPolicyPage(onAccept: onAccept)
18
+
19
+ super.init(rootView: rootView)
20
+
21
+ self.modalPresentationStyle = .fullScreen
22
+ }
23
+
24
+ @MainActor required dynamic init?(coder aDecoder: NSCoder) {
25
+ fatalError("init(coder:) has not been implemented")
26
+ }
27
+
28
+ override func viewWillDisappear(_ animated: Bool) {
29
+ super.viewWillDisappear(animated)
30
+ }
31
+ }