added getArticles request, intergrated articles in MyRewardsVC
Showing
10 changed files
with
279 additions
and
34 deletions
This diff is collapsed. Click to expand it.
| ... | @@ -2273,6 +2273,91 @@ public final class WarplySDK { | ... | @@ -2273,6 +2273,91 @@ public final class WarplySDK { |
| 2273 | }) | 2273 | }) |
| 2274 | } | 2274 | } |
| 2275 | } | 2275 | } |
| 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 | + } | ||
| 2276 | 2361 | ||
| 2277 | // MARK: - Profile | 2362 | // MARK: - Profile |
| 2278 | 2363 | ... | ... |
| ... | @@ -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 | - cell.configureCell(data: campaign) | 106 | + let item = items[indexPath.row] |
| 107 | + | ||
| 108 | + if let campaign = item as? CampaignItemModel { | ||
| 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 |
| 113 | - delegate?.didSelectBannerOffer(indexPath.row) | 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 { | ||
| 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,33 +122,81 @@ import UIKit | ... | @@ -119,33 +122,81 @@ 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 | - // } | ||
| 130 | - | ||
| 131 | - // Create banner section with real campaign data | ||
| 132 | - if !self.bannerCampaigns.isEmpty { | ||
| 133 | - let bannerSection = SectionModel( | ||
| 134 | - sectionType: .myRewardsBannerOffers, | ||
| 135 | - title: "Διαγωνισμός", | ||
| 136 | - items: self.bannerCampaigns, | ||
| 137 | - itemType: .campaigns | ||
| 138 | - ) | ||
| 139 | - self.sections.append(bannerSection) | ||
| 140 | } | 136 | } |
| 141 | 137 | ||
| 142 | - // Reload table view with new sections | 138 | + // Load articles after campaigns are loaded |
| 143 | - DispatchQueue.main.async { | 139 | + self.loadArticles() |
| 144 | - self.tableView.reloadData() | 140 | + |
| 145 | - } | ||
| 146 | } failureCallback: { [weak self] errorCode in | 141 | } failureCallback: { [weak self] errorCode in |
| 147 | print("Failed to load campaigns: \(errorCode)") | 142 | print("Failed to load campaigns: \(errorCode)") |
| 148 | - // No sections added on failure - table will be empty | 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) | ||
| 180 | + | ||
| 181 | + // Add articles after campaigns | ||
| 182 | + bannerItems.append(contentsOf: self.articles) | ||
| 183 | + | ||
| 184 | + // Create banner section if we have any items | ||
| 185 | + if !bannerItems.isEmpty { | ||
| 186 | + let bannerSection = SectionModel( | ||
| 187 | + sectionType: .myRewardsBannerOffers, | ||
| 188 | + title: "Διαγωνισμός", | ||
| 189 | + items: bannerItems, | ||
| 190 | + itemType: .mixed | ||
| 191 | + ) | ||
| 192 | + self.sections.append(bannerSection) | ||
| 193 | + | ||
| 194 | + print("✅ [MyRewardsViewController] Created banner section with \(self.bannerCampaigns.count) campaigns and \(self.articles.count) articles") | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + // Reload table view with new sections | ||
| 198 | + DispatchQueue.main.async { | ||
| 199 | + self.tableView.reloadData() | ||
| 149 | } | 200 | } |
| 150 | } | 201 | } |
| 151 | 202 | ||
| ... | @@ -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)") | ||
| 452 | return | 508 | return |
| 453 | } | 509 | } |
| 454 | 510 | ||
| 455 | - let campaign = bannerCampaigns[index] | 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") | ||
| 516 | + return | ||
| 517 | + } | ||
| 518 | + | ||
| 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