added getArticles request, intergrated articles in MyRewardsVC
Showing
10 changed files
with
270 additions
and
25 deletions
This diff is collapsed. Click to expand it.
... | @@ -2274,6 +2274,91 @@ public final class WarplySDK { | ... | @@ -2274,6 +2274,91 @@ public final class WarplySDK { |
2274 | } | 2274 | } |
2275 | } | 2275 | } |
2276 | 2276 | ||
2277 | + // MARK: - Articles | ||
2278 | + | ||
2279 | + /// Get articles (carousel content) | ||
2280 | + /// - Parameters: | ||
2281 | + /// - language: Language for the articles (optional, defaults to applicationLocale) | ||
2282 | + /// - categories: Categories to retrieve (optional, defaults to nil for all categories) | ||
2283 | + /// - completion: Completion handler with articles array | ||
2284 | + /// - failureCallback: Failure callback with error code | ||
2285 | + public func getArticles( | ||
2286 | + language: String? = nil, | ||
2287 | + categories: [String]? = nil, | ||
2288 | + completion: @escaping ([ArticleModel]?) -> Void, | ||
2289 | + failureCallback: @escaping (Int) -> Void | ||
2290 | + ) { | ||
2291 | + let finalLanguage = language ?? self.applicationLocale | ||
2292 | + | ||
2293 | + Task { | ||
2294 | + do { | ||
2295 | + let response = try await networkService.getArticles(language: finalLanguage, categories: categories) | ||
2296 | + | ||
2297 | + await MainActor.run { | ||
2298 | + if response["status"] as? Int == 1 { | ||
2299 | + // Success analytics | ||
2300 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
2301 | + dynatraceEvent._eventName = "custom_success_get_articles_loyalty" | ||
2302 | + dynatraceEvent._parameters = nil | ||
2303 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
2304 | + | ||
2305 | + var articles: [ArticleModel] = [] | ||
2306 | + | ||
2307 | + // Parse from context.CONTENT structure | ||
2308 | + if let content = response["CONTENT"] as? [[String: Any]] { | ||
2309 | + for articleDict in content { | ||
2310 | + let article = ArticleModel(dictionary: articleDict) | ||
2311 | + articles.append(article) | ||
2312 | + } | ||
2313 | + | ||
2314 | + let categoriesDesc = categories?.joined(separator: ", ") ?? "all categories" | ||
2315 | + print("✅ [WarplySDK] Retrieved \(articles.count) articles for \(categoriesDesc)") | ||
2316 | + completion(articles) | ||
2317 | + } else { | ||
2318 | + print("⚠️ [WarplySDK] No articles found in response") | ||
2319 | + completion([]) | ||
2320 | + } | ||
2321 | + } else { | ||
2322 | + // Error analytics | ||
2323 | + let dynatraceEvent = LoyaltySDKDynatraceEventModel() | ||
2324 | + dynatraceEvent._eventName = "custom_error_get_articles_loyalty" | ||
2325 | + dynatraceEvent._parameters = nil | ||
2326 | + self.postFrameworkEvent("dynatrace", sender: dynatraceEvent) | ||
2327 | + | ||
2328 | + failureCallback(-1) | ||
2329 | + } | ||
2330 | + } | ||
2331 | + } catch { | ||
2332 | + await MainActor.run { | ||
2333 | + self.handleError(error, context: "getArticles", endpoint: "getArticles", failureCallback: failureCallback) | ||
2334 | + } | ||
2335 | + } | ||
2336 | + } | ||
2337 | + } | ||
2338 | + | ||
2339 | + /// Get articles (async/await variant) | ||
2340 | + /// - Parameters: | ||
2341 | + /// - language: Language for the articles (optional, defaults to applicationLocale) | ||
2342 | + /// - categories: Categories to retrieve (optional, defaults to nil for all categories) | ||
2343 | + /// - Returns: Array of articles | ||
2344 | + /// - Throws: WarplyError if the request fails | ||
2345 | + public func getArticles( | ||
2346 | + language: String? = nil, | ||
2347 | + categories: [String]? = nil | ||
2348 | + ) async throws -> [ArticleModel] { | ||
2349 | + return try await withCheckedThrowingContinuation { continuation in | ||
2350 | + getArticles(language: language, categories: categories, completion: { articles in | ||
2351 | + if let articles = articles { | ||
2352 | + continuation.resume(returning: articles) | ||
2353 | + } else { | ||
2354 | + continuation.resume(throwing: WarplyError.networkError) | ||
2355 | + } | ||
2356 | + }, failureCallback: { errorCode in | ||
2357 | + continuation.resume(throwing: WarplyError.unknownError(errorCode)) | ||
2358 | + }) | ||
2359 | + } | ||
2360 | + } | ||
2361 | + | ||
2277 | // MARK: - Profile | 2362 | // MARK: - Profile |
2278 | 2363 | ||
2279 | /// Get user profile details | 2364 | /// Get user profile details | ... | ... |
... | @@ -73,6 +73,9 @@ public enum Endpoint { | ... | @@ -73,6 +73,9 @@ public enum Endpoint { |
73 | case getMerchants(language: String, categories: [String], defaultShown: Bool, center: Double, tags: [String], uuid: String, distance: Int, parentUuids: [String]) | 73 | case getMerchants(language: String, categories: [String], defaultShown: Bool, center: Double, tags: [String], uuid: String, distance: Int, parentUuids: [String]) |
74 | case getMerchantCategories(language: String) | 74 | case getMerchantCategories(language: String) |
75 | 75 | ||
76 | + // Articles | ||
77 | + case getArticles(language: String, categories: [String]?) | ||
78 | + | ||
76 | // Card Management | 79 | // Card Management |
77 | case addCard(cardNumber: String, cardIssuer: String, cardHolder: String, expirationMonth: String, expirationYear: String) | 80 | case addCard(cardNumber: String, cardIssuer: String, cardHolder: String, expirationMonth: String, expirationYear: String) |
78 | case getCards | 81 | case getCards |
... | @@ -127,7 +130,7 @@ public enum Endpoint { | ... | @@ -127,7 +130,7 @@ public enum Endpoint { |
127 | return "/user/v5/{appUUID}/logout" | 130 | return "/user/v5/{appUUID}/logout" |
128 | 131 | ||
129 | // Standard Context endpoints - /api/mobile/v2/{appUUID}/context/ | 132 | // Standard Context endpoints - /api/mobile/v2/{appUUID}/context/ |
130 | - case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories: | 133 | + case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories, .getArticles: |
131 | return "/api/mobile/v2/{appUUID}/context/" | 134 | return "/api/mobile/v2/{appUUID}/context/" |
132 | 135 | ||
133 | // Authenticated Context endpoints - /oauth/{appUUID}/context | 136 | // Authenticated Context endpoints - /oauth/{appUUID}/context |
... | @@ -160,7 +163,7 @@ public enum Endpoint { | ... | @@ -160,7 +163,7 @@ public enum Endpoint { |
160 | switch self { | 163 | switch self { |
161 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, | 164 | case .register, .changePassword, .resetPassword, .requestOtp, .verifyTicket, .refreshToken, .logout, .getCampaigns, .getCampaignsPersonalized, |
162 | .getCoupons, .getCouponSets, .getAvailableCoupons, | 165 | .getCoupons, .getCouponSets, .getAvailableCoupons, |
163 | - .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .sendEvent, .sendDeviceInfo, .getCosmoteUser: | 166 | + .getMarketPassDetails, .getProfile, .addCard, .getCards, .deleteCard, .getTransactionHistory, .getPointsHistory, .validateCoupon, .redeemCoupon, .getMerchants, .getMerchantCategories, .getArticles, .sendEvent, .sendDeviceInfo, .getCosmoteUser: |
164 | return .POST | 167 | return .POST |
165 | case .getSingleCampaign, .getNetworkStatus: | 168 | case .getSingleCampaign, .getNetworkStatus: |
166 | return .GET | 169 | return .GET |
... | @@ -389,6 +392,22 @@ public enum Endpoint { | ... | @@ -389,6 +392,22 @@ public enum Endpoint { |
389 | ] | 392 | ] |
390 | ] | 393 | ] |
391 | 394 | ||
395 | + // Articles - using content structure for DEI API | ||
396 | + case .getArticles(let language, let categories): | ||
397 | + var contentParams: [String: Any] = [ | ||
398 | + "language": language, | ||
399 | + "action": "retrieve_multilingual" | ||
400 | + ] | ||
401 | + | ||
402 | + // Only add category field if categories are provided | ||
403 | + if let categories = categories, !categories.isEmpty { | ||
404 | + contentParams["category"] = categories | ||
405 | + } | ||
406 | + | ||
407 | + return [ | ||
408 | + "content": contentParams | ||
409 | + ] | ||
410 | + | ||
392 | // Analytics endpoints - events structure | 411 | // Analytics endpoints - events structure |
393 | case .sendEvent(let eventName, let priority): | 412 | case .sendEvent(let eventName, let priority): |
394 | return [ | 413 | return [ |
... | @@ -444,7 +463,7 @@ public enum Endpoint { | ... | @@ -444,7 +463,7 @@ public enum Endpoint { |
444 | return .userManagement | 463 | return .userManagement |
445 | 464 | ||
446 | // Standard Context - /api/mobile/v2/{appUUID}/context/ | 465 | // Standard Context - /api/mobile/v2/{appUUID}/context/ |
447 | - case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories: | 466 | + case .getCampaigns, .getAvailableCoupons, .getCouponSets, .getMerchantCategories, .getArticles: |
448 | return .standardContext | 467 | return .standardContext |
449 | 468 | ||
450 | // Authenticated Context - /oauth/{appUUID}/context | 469 | // Authenticated Context - /oauth/{appUUID}/context |
... | @@ -486,7 +505,7 @@ public enum Endpoint { | ... | @@ -486,7 +505,7 @@ public enum Endpoint { |
486 | // Standard Authentication (loyalty headers only) | 505 | // Standard Authentication (loyalty headers only) |
487 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, | 506 | case .register, .resetPassword, .requestOtp, .getCampaigns, .getAvailableCoupons, .getCouponSets, .refreshToken, .logout, |
488 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, | 507 | .verifyTicket, .getSingleCampaign, .sendEvent, .sendDeviceInfo, |
489 | - .getMerchants, .getMerchantCategories, .getNetworkStatus: | 508 | + .getMerchants, .getMerchantCategories, .getArticles, .getNetworkStatus: |
490 | return .standard | 509 | return .standard |
491 | 510 | ||
492 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | 511 | // Bearer Token Authentication (loyalty headers + Authorization: Bearer) | ... | ... |
... | @@ -978,6 +978,26 @@ extension NetworkService { | ... | @@ -978,6 +978,26 @@ extension NetworkService { |
978 | return response | 978 | return response |
979 | } | 979 | } |
980 | 980 | ||
981 | + // MARK: - Articles Methods | ||
982 | + | ||
983 | + /// Get articles (carousel content) | ||
984 | + /// - Parameters: | ||
985 | + /// - language: Language for the articles | ||
986 | + /// - categories: Categories to retrieve (optional) | ||
987 | + /// - Returns: Response dictionary containing articles | ||
988 | + /// - Throws: NetworkError if request fails | ||
989 | + public func getArticles(language: String, categories: [String]?) async throws -> [String: Any] { | ||
990 | + let categoriesDesc = categories?.joined(separator: ", ") ?? "all categories" | ||
991 | + print("🔄 [NetworkService] Getting articles for language: \(language), categories: \(categoriesDesc)") | ||
992 | + | ||
993 | + let endpoint = Endpoint.getArticles(language: language, categories: categories) | ||
994 | + let response = try await requestRaw(endpoint) | ||
995 | + | ||
996 | + print("✅ [NetworkService] Get articles request completed") | ||
997 | + | ||
998 | + return response | ||
999 | + } | ||
1000 | + | ||
981 | // MARK: - Coupon Operations Methods | 1001 | // MARK: - Coupon Operations Methods |
982 | 1002 | ||
983 | /// Validate a coupon for the user | 1003 | /// Validate a coupon for the user | ... | ... |
... | @@ -40,4 +40,9 @@ public class MyRewardsBannerOfferCollectionViewCell: UICollectionViewCell { | ... | @@ -40,4 +40,9 @@ public class MyRewardsBannerOfferCollectionViewCell: UICollectionViewCell { |
40 | // Use campaign's banner image - no hardcoded defaults | 40 | // Use campaign's banner image - no hardcoded defaults |
41 | self.postImageURL = data._banner_img_mobile ?? "" | 41 | self.postImageURL = data._banner_img_mobile ?? "" |
42 | } | 42 | } |
43 | + | ||
44 | + func configureCell(data: ArticleModel) { | ||
45 | + // Use article's preview image - same visual treatment as campaigns | ||
46 | + self.postImageURL = data.img_preview ?? "" | ||
47 | + } | ||
43 | } | 48 | } | ... | ... |
... | @@ -9,6 +9,7 @@ import UIKit | ... | @@ -9,6 +9,7 @@ import UIKit |
9 | 9 | ||
10 | protocol MyRewardsBannerOffersScrollTableViewCellDelegate: AnyObject { | 10 | protocol MyRewardsBannerOffersScrollTableViewCellDelegate: AnyObject { |
11 | func didSelectBannerOffer(_ index: Int) | 11 | func didSelectBannerOffer(_ index: Int) |
12 | + func didSelectBannerArticle(_ index: Int) | ||
12 | } | 13 | } |
13 | 14 | ||
14 | @objc(MyRewardsBannerOffersScrollTableViewCell) | 15 | @objc(MyRewardsBannerOffersScrollTableViewCell) |
... | @@ -95,22 +96,39 @@ extension MyRewardsBannerOffersScrollTableViewCell: UICollectionViewDataSource, | ... | @@ -95,22 +96,39 @@ extension MyRewardsBannerOffersScrollTableViewCell: UICollectionViewDataSource, |
95 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { | 96 | public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { |
96 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyRewardsBannerOfferCollectionViewCell", for: indexPath) as! MyRewardsBannerOfferCollectionViewCell | 97 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyRewardsBannerOfferCollectionViewCell", for: indexPath) as! MyRewardsBannerOfferCollectionViewCell |
97 | 98 | ||
98 | - // Handle only CampaignItemModel - banner cells are campaign-specific | 99 | + // Handle both CampaignItemModel and ArticleModel |
99 | guard let data = self.data, | 100 | guard let data = self.data, |
100 | let items = data.items, | 101 | let items = data.items, |
101 | - indexPath.row < items.count, | 102 | + indexPath.row < items.count else { |
102 | - let campaign = items[indexPath.row] as? CampaignItemModel else { | ||
103 | return cell | 103 | return cell |
104 | } | 104 | } |
105 | 105 | ||
106 | + let item = items[indexPath.row] | ||
107 | + | ||
108 | + if let campaign = item as? CampaignItemModel { | ||
106 | cell.configureCell(data: campaign) | 109 | cell.configureCell(data: campaign) |
110 | + } else if let article = item as? ArticleModel { | ||
111 | + cell.configureCell(data: article) | ||
112 | + } | ||
107 | 113 | ||
108 | return cell | 114 | return cell |
109 | } | 115 | } |
110 | 116 | ||
111 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { | 117 | public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { |
112 | - // Call the delegate method to notify the parent | 118 | + // Determine item type and call appropriate delegate method |
119 | + guard let data = self.data, | ||
120 | + let items = data.items, | ||
121 | + indexPath.row < items.count else { | ||
122 | + return | ||
123 | + } | ||
124 | + | ||
125 | + let item = items[indexPath.row] | ||
126 | + | ||
127 | + if item is CampaignItemModel { | ||
113 | delegate?.didSelectBannerOffer(indexPath.row) | 128 | delegate?.didSelectBannerOffer(indexPath.row) |
129 | + } else if item is ArticleModel { | ||
130 | + delegate?.didSelectBannerArticle(indexPath.row) | ||
131 | + } | ||
114 | } | 132 | } |
115 | 133 | ||
116 | // MARK: - UICollectionViewDelegateFlowLayout | 134 | // MARK: - UICollectionViewDelegateFlowLayout | ... | ... |
... | @@ -113,5 +113,7 @@ public class MyRewardsProfileInfoTableViewCell: UITableViewCell { | ... | @@ -113,5 +113,7 @@ public class MyRewardsProfileInfoTableViewCell: UITableViewCell { |
113 | private func loadProfileImage(from urlString: String) { | 113 | private func loadProfileImage(from urlString: String) { |
114 | // For now, use default image - can be enhanced later with URL loading | 114 | // For now, use default image - can be enhanced later with URL loading |
115 | profileImage.image = UIImage(named: "profile_pic_default", in: Bundle.frameworkResourceBundle, compatibleWith: nil) | 115 | profileImage.image = UIImage(named: "profile_pic_default", in: Bundle.frameworkResourceBundle, compatibleWith: nil) |
116 | + | ||
117 | + // TODO: Add dynamic profile picture and tags | ||
116 | } | 118 | } |
117 | } | 119 | } | ... | ... |
This diff is collapsed. Click to expand it.
... | @@ -24,6 +24,8 @@ public enum SectionType { | ... | @@ -24,6 +24,8 @@ public enum SectionType { |
24 | public enum ItemType { | 24 | public enum ItemType { |
25 | case profile // ProfileModel | 25 | case profile // ProfileModel |
26 | case campaigns // [CampaignItemModel] | 26 | case campaigns // [CampaignItemModel] |
27 | + case articles // [ArticleModel] | ||
28 | + case mixed // Mixed content (campaigns + articles) | ||
27 | case couponSets // [CouponSetItemModel] | 29 | case couponSets // [CouponSetItemModel] |
28 | case coupons // [CouponItemModel] | 30 | case coupons // [CouponItemModel] |
29 | case offers // [OfferModel] - temporary, will migrate to dynamic coupons later | 31 | case offers // [OfferModel] - temporary, will migrate to dynamic coupons later | ... | ... |
... | @@ -40,6 +40,9 @@ import UIKit | ... | @@ -40,6 +40,9 @@ import UIKit |
40 | // Campaign data for banners | 40 | // Campaign data for banners |
41 | var bannerCampaigns: [CampaignItemModel] = [] | 41 | var bannerCampaigns: [CampaignItemModel] = [] |
42 | 42 | ||
43 | + // Articles data for banners | ||
44 | + var articles: [ArticleModel] = [] | ||
45 | + | ||
43 | // Coupon sets data | 46 | // Coupon sets data |
44 | var couponSets: [CouponSetItemModel] = [] | 47 | var couponSets: [CouponSetItemModel] = [] |
45 | 48 | ||
... | @@ -119,34 +122,82 @@ import UIKit | ... | @@ -119,34 +122,82 @@ import UIKit |
119 | private func loadCampaigns() { | 122 | private func loadCampaigns() { |
120 | // Load campaigns from WarplySDK | 123 | // Load campaigns from WarplySDK |
121 | WarplySDK.shared.getCampaigns { [weak self] campaigns in | 124 | WarplySDK.shared.getCampaigns { [weak self] campaigns in |
122 | - guard let self = self, let campaigns = campaigns else { return } | 125 | + guard let self = self, let campaigns = campaigns else { |
126 | + // Even if campaigns fail, try to load articles | ||
127 | + self?.loadArticles() | ||
128 | + return | ||
129 | + } | ||
123 | 130 | ||
124 | // Filter campaigns for banner display (contest campaigns) if needed | 131 | // Filter campaigns for banner display (contest campaigns) if needed |
125 | self.bannerCampaigns = campaigns | 132 | self.bannerCampaigns = campaigns |
126 | - // .filter { campaign in | 133 | + .filter { campaign in |
127 | - // // Filter by category "contest" or campaign_type "contest" | 134 | + // Filter by category "contest" or campaign_type "contest" |
128 | - // return campaign._category == "contest" || campaign._campaign_type == "contest" | 135 | + return campaign._category == "contest" || campaign._campaign_type == "contest" |
129 | - // } | 136 | + } |
137 | + | ||
138 | + // Load articles after campaigns are loaded | ||
139 | + self.loadArticles() | ||
140 | + | ||
141 | + } failureCallback: { [weak self] errorCode in | ||
142 | + print("Failed to load campaigns: \(errorCode)") | ||
143 | + // Even if campaigns fail, try to load articles | ||
144 | + self?.loadArticles() | ||
145 | + } | ||
146 | + } | ||
147 | + | ||
148 | + // MARK: - Articles Loading | ||
149 | + private func loadArticles() { | ||
150 | + // Load articles from WarplySDK with "Carousel" category filter | ||
151 | + WarplySDK.shared.getArticles(categories: ["Carousel"]) { [weak self] articles in | ||
152 | + guard let self = self, let articles = articles else { | ||
153 | + // Create banner section with only campaigns if articles fail | ||
154 | + self?.createBannerSection() | ||
155 | + return | ||
156 | + } | ||
157 | + | ||
158 | + self.articles = articles | ||
159 | + print("✅ [MyRewardsViewController] Loaded \(articles.count) carousel articles") | ||
160 | + | ||
161 | + // Create banner section with both campaigns and articles | ||
162 | + self.createBannerSection() | ||
163 | + | ||
164 | + // TODO: Add Couponsets here | ||
165 | + | ||
166 | + } failureCallback: { [weak self] errorCode in | ||
167 | + print("Failed to load carousel articles: \(errorCode)") | ||
168 | + // Create banner section with only campaigns if articles fail | ||
169 | + self?.createBannerSection() | ||
170 | + } | ||
171 | + } | ||
172 | + | ||
173 | + // MARK: - Banner Section Creation | ||
174 | + private func createBannerSection() { | ||
175 | + // Combine campaigns and articles for banner section | ||
176 | + var bannerItems: [Any] = [] | ||
177 | + | ||
178 | + // Add campaigns first | ||
179 | + bannerItems.append(contentsOf: self.bannerCampaigns) | ||
130 | 180 | ||
131 | - // Create banner section with real campaign data | 181 | + // Add articles after campaigns |
132 | - if !self.bannerCampaigns.isEmpty { | 182 | + bannerItems.append(contentsOf: self.articles) |
183 | + | ||
184 | + // Create banner section if we have any items | ||
185 | + if !bannerItems.isEmpty { | ||
133 | let bannerSection = SectionModel( | 186 | let bannerSection = SectionModel( |
134 | sectionType: .myRewardsBannerOffers, | 187 | sectionType: .myRewardsBannerOffers, |
135 | title: "Διαγωνισμός", | 188 | title: "Διαγωνισμός", |
136 | - items: self.bannerCampaigns, | 189 | + items: bannerItems, |
137 | - itemType: .campaigns | 190 | + itemType: .mixed |
138 | ) | 191 | ) |
139 | self.sections.append(bannerSection) | 192 | self.sections.append(bannerSection) |
193 | + | ||
194 | + print("✅ [MyRewardsViewController] Created banner section with \(self.bannerCampaigns.count) campaigns and \(self.articles.count) articles") | ||
140 | } | 195 | } |
141 | 196 | ||
142 | // Reload table view with new sections | 197 | // Reload table view with new sections |
143 | DispatchQueue.main.async { | 198 | DispatchQueue.main.async { |
144 | self.tableView.reloadData() | 199 | self.tableView.reloadData() |
145 | } | 200 | } |
146 | - } failureCallback: { [weak self] errorCode in | ||
147 | - print("Failed to load campaigns: \(errorCode)") | ||
148 | - // No sections added on failure - table will be empty | ||
149 | - } | ||
150 | } | 201 | } |
151 | 202 | ||
152 | // MARK: - Coupon Sets Loading | 203 | // MARK: - Coupon Sets Loading |
... | @@ -446,13 +497,25 @@ import UIKit | ... | @@ -446,13 +497,25 @@ import UIKit |
446 | } | 497 | } |
447 | 498 | ||
448 | private func openCampaignViewController(with index: Int) { | 499 | private func openCampaignViewController(with index: Int) { |
449 | - // Validate index bounds | 500 | + // Get the combined banner items (campaigns + articles) |
450 | - guard index < bannerCampaigns.count else { | 501 | + var bannerItems: [Any] = [] |
451 | - print("Invalid campaign index: \(index)") | 502 | + bannerItems.append(contentsOf: self.bannerCampaigns) |
503 | + bannerItems.append(contentsOf: self.articles) | ||
504 | + | ||
505 | + // Validate index bounds for combined items | ||
506 | + guard index < bannerItems.count else { | ||
507 | + print("Invalid banner item index: \(index)") | ||
508 | + return | ||
509 | + } | ||
510 | + | ||
511 | + let item = bannerItems[index] | ||
512 | + | ||
513 | + // Handle only campaigns - articles will be handled by didSelectBannerArticle | ||
514 | + guard let campaign = item as? CampaignItemModel else { | ||
515 | + print("Item at index \(index) is not a campaign") | ||
452 | return | 516 | return |
453 | } | 517 | } |
454 | 518 | ||
455 | - let campaign = bannerCampaigns[index] | ||
456 | let campaignUrl = campaign._campaign_url ?? campaign.index_url | 519 | let campaignUrl = campaign._campaign_url ?? campaign.index_url |
457 | 520 | ||
458 | // Check if URL is not empty before proceeding | 521 | // Check if URL is not empty before proceeding |
... | @@ -468,6 +531,32 @@ import UIKit | ... | @@ -468,6 +531,32 @@ import UIKit |
468 | self.navigationController?.pushViewController(vc, animated: true) | 531 | self.navigationController?.pushViewController(vc, animated: true) |
469 | } | 532 | } |
470 | 533 | ||
534 | + private func openArticleViewController(with index: Int) { | ||
535 | + // Get the combined banner items (campaigns + articles) | ||
536 | + var bannerItems: [Any] = [] | ||
537 | + bannerItems.append(contentsOf: self.bannerCampaigns) | ||
538 | + bannerItems.append(contentsOf: self.articles) | ||
539 | + | ||
540 | + // Validate index bounds for combined items | ||
541 | + guard index < bannerItems.count else { | ||
542 | + print("Invalid banner item index: \(index)") | ||
543 | + return | ||
544 | + } | ||
545 | + | ||
546 | + let item = bannerItems[index] | ||
547 | + | ||
548 | + // Handle only articles | ||
549 | + guard let article = item as? ArticleModel else { | ||
550 | + print("Item at index \(index) is not an article") | ||
551 | + return | ||
552 | + } | ||
553 | + | ||
554 | + // TODO: Implement article navigation | ||
555 | + // This could navigate to a web view with article content, | ||
556 | + // or a dedicated article detail screen | ||
557 | + print("TODO: Navigate to article: \(article.title ?? "Unknown") - \(article.uuid ?? "No UUID")") | ||
558 | + } | ||
559 | + | ||
471 | private func openCouponViewController(with offer: OfferModel) { | 560 | private func openCouponViewController(with offer: OfferModel) { |
472 | // let vc = SwiftWarplyFramework.CouponViewController(nibName: "CouponViewController", bundle: Bundle.frameworkBundle) | 561 | // let vc = SwiftWarplyFramework.CouponViewController(nibName: "CouponViewController", bundle: Bundle.frameworkBundle) |
473 | // vc.coupon = offer | 562 | // vc.coupon = offer |
... | @@ -570,6 +659,11 @@ extension MyRewardsViewController: MyRewardsBannerOffersScrollTableViewCellDeleg | ... | @@ -570,6 +659,11 @@ extension MyRewardsViewController: MyRewardsBannerOffersScrollTableViewCellDeleg |
570 | openCampaignViewController(with: index) | 659 | openCampaignViewController(with: index) |
571 | } | 660 | } |
572 | 661 | ||
662 | + func didSelectBannerArticle(_ index: Int) { | ||
663 | + // Navigate to Article detail (TODO implementation) | ||
664 | + openArticleViewController(with: index) | ||
665 | + } | ||
666 | + | ||
573 | // func didTapProfileButton() { | 667 | // func didTapProfileButton() { |
574 | // // Navigate to ProfileViewController | 668 | // // Navigate to ProfileViewController |
575 | // openProfileViewController() | 669 | // openProfileViewController() | ... | ... |
-
Please register or login to post a comment