Manos Chorianopoulos

dynamic campaigns at MyRewardsVC

This diff is collapsed. Click to expand it.
...@@ -16,7 +16,14 @@ public class MyRewardsBannerOfferCollectionViewCell: UICollectionViewCell { ...@@ -16,7 +16,14 @@ public class MyRewardsBannerOfferCollectionViewCell: UICollectionViewCell {
16 // Initialization code 16 // Initialization code
17 } 17 }
18 18
19 - func configureCell(data: OfferModel) { 19 + func configureCell(data: CampaignItemModel) {
20 - backgroundImage.image = UIImage(named: data.bannerImage, in: Bundle.frameworkResourceBundle, compatibleWith: nil) 20 + // Use campaign's banner image - no hardcoded defaults
21 + let imageName = data._banner_img_mobile ?? ""
22 +
23 + if !imageName.isEmpty {
24 + backgroundImage.image = UIImage(named: imageName, in: Bundle.frameworkResourceBundle, compatibleWith: nil)
25 + } else {
26 + backgroundImage.image = nil
27 + }
21 } 28 }
22 } 29 }
......
...@@ -93,8 +93,8 @@ public class MyRewardsBannerOffersScrollTableViewCell: UITableViewCell { ...@@ -93,8 +93,8 @@ public class MyRewardsBannerOffersScrollTableViewCell: UITableViewCell {
93 func configureCell(data: SectionModel?) { 93 func configureCell(data: SectionModel?) {
94 self.data = data 94 self.data = data
95 95
96 - // Configure page control 96 + // Configure page control based on new SectionModel structure
97 - let numberOfPages = self.data?.offers.count ?? 0 97 + let numberOfPages = self.data?.itemCount ?? 0
98 pageControl.numberOfPages = numberOfPages 98 pageControl.numberOfPages = numberOfPages
99 pageControl.currentPage = 0 99 pageControl.currentPage = 0
100 100
...@@ -115,24 +115,36 @@ extension MyRewardsBannerOffersScrollTableViewCell: UICollectionViewDataSource, ...@@ -115,24 +115,36 @@ extension MyRewardsBannerOffersScrollTableViewCell: UICollectionViewDataSource,
115 } 115 }
116 116
117 public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 117 public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
118 - return self.data?.offers.count ?? 0 118 + return self.data?.itemCount ?? 0
119 } 119 }
120 120
121 public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 121 public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
122 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyRewardsBannerOfferCollectionViewCell", for: indexPath) as! MyRewardsBannerOfferCollectionViewCell 122 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyRewardsBannerOfferCollectionViewCell", for: indexPath) as! MyRewardsBannerOfferCollectionViewCell
123 -// cell.configureCell(offer: self.data?.offers[indexPath.row]) 123 +
124 - if let offer = self.data?.offers[indexPath.row] { 124 + // Handle different item types
125 - cell.configureCell(data: offer) 125 + guard let data = self.data,
126 + let itemType = data.itemType,
127 + let items = data.items,
128 + indexPath.row < items.count else {
129 + return cell
130 + }
131 +
132 + switch itemType {
133 + case .campaigns:
134 + if let campaign = items[indexPath.row] as? CampaignItemModel {
135 + cell.configureCell(data: campaign)
136 + }
137 + default:
138 + // Handle other types if needed in the future
139 + break
126 } 140 }
127 - return cell; 141 +
142 + return cell
128 } 143 }
129 144
130 public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 145 public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
131 - // Get the selected offer 146 + // Call the delegate method to notify the parent
132 - if let offer = self.data?.offers[indexPath.row] { 147 + delegate?.didSelectBannerOffer(indexPath.row)
133 - // Call the delegate method to notify the parent
134 - delegate?.didSelectBannerOffer(indexPath.row)
135 - }
136 } 148 }
137 149
138 // MARK: - UICollectionViewDelegateFlowLayout 150 // MARK: - UICollectionViewDelegateFlowLayout
......
...@@ -42,6 +42,17 @@ public class CampaignItemModel: Codable { ...@@ -42,6 +42,17 @@ public class CampaignItemModel: Codable {
42 private var show_expiration: String? 42 private var show_expiration: String?
43 private var coupon_img: String? 43 private var coupon_img: String?
44 44
45 + // New fields from getCampaigns response
46 + private var communication_name: String?
47 + private var category: String?
48 + private var delivery_method: String?
49 + private var display_type: String?
50 + private var audience: String?
51 + private var description: String?
52 + private var workflow_settings: [String: Any]?
53 + private var campaign_url: String? // from extra_fields
54 + private var banner_img_mobile: String? // from extra_fields
55 +
45 public init() { 56 public init() {
46 self.index_url = "" 57 self.index_url = ""
47 self.logo_url = "" 58 self.logo_url = ""
...@@ -73,6 +84,17 @@ public class CampaignItemModel: Codable { ...@@ -73,6 +84,17 @@ public class CampaignItemModel: Codable {
73 self.filter = "" 84 self.filter = ""
74 self.show_expiration = "false" 85 self.show_expiration = "false"
75 self.coupon_img = "" 86 self.coupon_img = ""
87 +
88 + // Initialize new fields
89 + self.communication_name = ""
90 + self.category = ""
91 + self.delivery_method = ""
92 + self.display_type = ""
93 + self.audience = ""
94 + self.description = ""
95 + self.workflow_settings = nil
96 + self.campaign_url = ""
97 + self.banner_img_mobile = ""
76 } 98 }
77 99
78 public init(dictionary: [String: Any]) { 100 public init(dictionary: [String: Any]) {
...@@ -90,6 +112,15 @@ public class CampaignItemModel: Codable { ...@@ -90,6 +112,15 @@ public class CampaignItemModel: Codable {
90 self.ccms = nil 112 self.ccms = nil
91 self.coupon_availability = nil 113 self.coupon_availability = nil
92 114
115 + // Parse new fields from getCampaigns response
116 + self.communication_name = dictionary["communication_name"] as? String? ?? ""
117 + self.category = dictionary["category"] as? String? ?? ""
118 + self.delivery_method = dictionary["delivery_method"] as? String? ?? ""
119 + self.display_type = dictionary["display_type"] as? String? ?? ""
120 + self.audience = dictionary["audience"] as? String? ?? ""
121 + self.description = dictionary["description"] as? String? ?? ""
122 + self.workflow_settings = dictionary["workflow_settings"] as? [String: Any]
123 +
93 let startDateString = dictionary["start_date"] as? String? ?? "" 124 let startDateString = dictionary["start_date"] as? String? ?? ""
94 let dateFormatter = DateFormatter() 125 let dateFormatter = DateFormatter()
95 dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 126 dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
...@@ -127,6 +158,10 @@ public class CampaignItemModel: Codable { ...@@ -127,6 +158,10 @@ public class CampaignItemModel: Codable {
127 self.filter = extra_fields["filter"] as? String ?? "" 158 self.filter = extra_fields["filter"] as? String ?? ""
128 self.show_expiration = extra_fields["show_expiration"] as? String? ?? "false" 159 self.show_expiration = extra_fields["show_expiration"] as? String? ?? "false"
129 self.coupon_img = extra_fields["coupon_img"] as? String? ?? "" 160 self.coupon_img = extra_fields["coupon_img"] as? String? ?? ""
161 +
162 + // Parse new extra_fields
163 + self.campaign_url = extra_fields["campaign_url"] as? String? ?? ""
164 + self.banner_img_mobile = extra_fields["banner_img_mobile"] as? String? ?? ""
130 } else { 165 } else {
131 self.subcategory = "" 166 self.subcategory = ""
132 self.loyaltyCampaignId = "" 167 self.loyaltyCampaignId = ""
...@@ -141,6 +176,10 @@ public class CampaignItemModel: Codable { ...@@ -141,6 +176,10 @@ public class CampaignItemModel: Codable {
141 self.filter = "" 176 self.filter = ""
142 self.show_expiration = "false" 177 self.show_expiration = "false"
143 self.coupon_img = "" 178 self.coupon_img = ""
179 +
180 + // Initialize new extra_fields when no extra_fields exist
181 + self.campaign_url = ""
182 + self.banner_img_mobile = ""
144 } 183 }
145 184
146 // campaign_type_settings 185 // campaign_type_settings
...@@ -260,6 +299,53 @@ public class CampaignItemModel: Codable { ...@@ -260,6 +299,53 @@ public class CampaignItemModel: Codable {
260 get { return self.coupon_img } 299 get { return self.coupon_img }
261 set(newValue) { self.coupon_img = newValue } 300 set(newValue) { self.coupon_img = newValue }
262 } 301 }
302 +
303 + // MARK: - New field accessors
304 +
305 + public var _communication_name: String? {
306 + get { return self.communication_name }
307 + set(newValue) { self.communication_name = newValue }
308 + }
309 +
310 + public var _category: String? {
311 + get { return self.category }
312 + set(newValue) { self.category = newValue }
313 + }
314 +
315 + public var _delivery_method: String? {
316 + get { return self.delivery_method }
317 + set(newValue) { self.delivery_method = newValue }
318 + }
319 +
320 + public var _display_type: String? {
321 + get { return self.display_type }
322 + set(newValue) { self.display_type = newValue }
323 + }
324 +
325 + public var _audience: String? {
326 + get { return self.audience }
327 + set(newValue) { self.audience = newValue }
328 + }
329 +
330 + public var _description: String? {
331 + get { return self.description }
332 + set(newValue) { self.description = newValue }
333 + }
334 +
335 + public var _workflow_settings: [String: Any]? {
336 + get { return self.workflow_settings }
337 + set(newValue) { self.workflow_settings = newValue }
338 + }
339 +
340 + public var _campaign_url: String? {
341 + get { return self.campaign_url }
342 + set(newValue) { self.campaign_url = newValue }
343 + }
344 +
345 + public var _banner_img_mobile: String? {
346 + get { return self.banner_img_mobile }
347 + set(newValue) { self.banner_img_mobile = newValue }
348 + }
263 } 349 }
264 350
265 // MARK: - Loyalty Contextual Offer Model 351 // MARK: - Loyalty Contextual Offer Model
......
...@@ -8,8 +8,61 @@ ...@@ -8,8 +8,61 @@
8 8
9 import Foundation 9 import Foundation
10 10
11 +// MARK: - Section Types
12 +
13 +enum SectionType {
14 + case myRewardsBannerOffers // MyRewardsBannerOffersScrollTableViewCell
15 + case myRewardsHorizontalCouponsets // MyRewardsOffersScrollTableViewCell
16 + case profileHeader // ProfileHeaderTableViewCell (no items)
17 + case profileQuestionnaire // ProfileQuestionnaireTableViewCell (no items)
18 + case profileCouponFilters // ProfileCouponFiltersTableViewCell (no items)
19 + case staticContent // Any cell that displays static content
20 +}
21 +
22 +enum ItemType {
23 + case campaigns // [CampaignItemModel]
24 + case couponSets // [CouponSetItemModel]
25 + case coupons // [CouponItemModel]
26 + case filters // [CouponFilterModel]
27 + case none // For sections with no items
28 +}
29 +
30 +// MARK: - SectionModel
31 +
11 struct SectionModel { 32 struct SectionModel {
12 - let title: String 33 + let sectionType: SectionType // MANDATORY - defines which cell to use
13 - let count: Int 34 + let title: String? // OPTIONAL - section title
14 - let offers: [OfferModel] 35 + let items: [Any]? // OPTIONAL - array of items (nil for sections with no items)
36 + let itemType: ItemType? // OPTIONAL - type of items in the array
37 + let count: Int? // OPTIONAL - explicit count (computed from items.count if nil)
38 + let metadata: [String: Any]? // OPTIONAL - additional section-specific data
39 +}
40 +
41 +// MARK: - SectionModel Extensions
42 +
43 +extension SectionModel {
44 + // Convenience initializer for sections with items
45 + init(sectionType: SectionType, title: String? = nil, items: [Any], itemType: ItemType, metadata: [String: Any]? = nil) {
46 + self.sectionType = sectionType
47 + self.title = title
48 + self.items = items
49 + self.itemType = itemType
50 + self.count = items.count
51 + self.metadata = metadata
52 + }
53 +
54 + // Convenience initializer for sections without items
55 + init(sectionType: SectionType, title: String? = nil, count: Int = 1, metadata: [String: Any]? = nil) {
56 + self.sectionType = sectionType
57 + self.title = title
58 + self.items = nil
59 + self.itemType = .none
60 + self.count = count
61 + self.metadata = metadata
62 + }
63 +
64 + // Computed property for safe count access
65 + var itemCount: Int {
66 + return count ?? items?.count ?? 0
67 + }
15 } 68 }
......
...@@ -293,15 +293,11 @@ import UIKit ...@@ -293,15 +293,11 @@ import UIKit
293 "https://warply.s3.amazonaws.com/dei/campaigns/EnergySaverContest_dev/index.html" 293 "https://warply.s3.amazonaws.com/dei/campaigns/EnergySaverContest_dev/index.html"
294 ] 294 ]
295 295
296 - var bannerOffersSection: SectionModel? 296 + // Dynamic sections array - populated by API calls
297 - var topOffersSection: SectionModel? 297 + var sections: [SectionModel] = []
298 - var favoriteOffersSection: SectionModel? 298 +
299 - var sustainableOffersSection: SectionModel? 299 + // Campaign data for banners
300 - var familyOffersSection: SectionModel? 300 + var bannerCampaigns: [CampaignItemModel] = []
301 - var foodOffersSection: SectionModel?
302 - var escapeOffersSection: SectionModel?
303 - var childOffersSection: SectionModel?
304 - var shoppingOffersSection: SectionModel?
305 301
306 public override func viewDidLoad() { 302 public override func viewDidLoad() {
307 super.viewDidLoad() 303 super.viewDidLoad()
...@@ -319,7 +315,8 @@ import UIKit ...@@ -319,7 +315,8 @@ import UIKit
319 tableView.estimatedRowHeight = 200 315 tableView.estimatedRowHeight = 200
320 tableView.rowHeight = UITableView.automaticDimension 316 tableView.rowHeight = UITableView.automaticDimension
321 317
322 - initializeSections() 318 + // Start with empty sections - will be populated dynamically by API calls
319 + loadCampaigns()
323 } 320 }
324 321
325 // NEW: Safe XIB registration method 322 // NEW: Safe XIB registration method
...@@ -432,22 +429,53 @@ import UIKit ...@@ -432,22 +429,53 @@ import UIKit
432 self.tableView.reloadData() 429 self.tableView.reloadData()
433 } 430 }
434 431
432 + // MARK: - Campaign Loading
433 + private func loadCampaigns() {
434 + // Load campaigns from WarplySDK
435 + WarplySDK.shared.getCampaigns { [weak self] campaigns in
436 + guard let self = self, let campaigns = campaigns else { return }
437 +
438 + // Filter campaigns for banner display (contest campaigns)
439 + self.bannerCampaigns = campaigns.filter { campaign in
440 + // Filter by category "contest" or campaign_type "contest"
441 + return campaign._category == "contest" || campaign._campaign_type == "contest"
442 + }
443 +
444 + // Create banner section with real campaign data
445 + if !self.bannerCampaigns.isEmpty {
446 + let bannerSection = SectionModel(
447 + sectionType: .myRewardsBannerOffers,
448 + title: "Διαγωνισμός",
449 + items: self.bannerCampaigns,
450 + itemType: .campaigns
451 + )
452 + self.sections.append(bannerSection)
453 + }
454 +
455 + // Reload table view with new sections
456 + DispatchQueue.main.async {
457 + self.tableView.reloadData()
458 + }
459 + } failureCallback: { [weak self] errorCode in
460 + print("Failed to load campaigns: \(errorCode)")
461 + // No sections added on failure - table will be empty
462 + }
463 + }
464 +
435 private func openCampaignViewController(with index: Int) { 465 private func openCampaignViewController(with index: Int) {
436 -
437 -// let storyboard = UIStoryboard(name: "Main", bundle: Bundle.frameworkBundle)
438 -// if let vc = storyboard.instantiateViewController(withIdentifier: "CampaignViewController") as? SwiftWarplyFramework.CampaignViewController {
439 -// // vc.campaignUrl = "https://warply.s3.amazonaws.com/dei/campaigns/DehEasterContest_stage/index.html"
440 -// vc.campaignUrl = contestUrls[index]
441 -// vc.showHeader = false
442 -// self.navigationController?.pushViewController(vc, animated: true)
443 -// // self.present(vc, animated: true)
444 -// }
445 -
446 let vc = SwiftWarplyFramework.CampaignViewController(nibName: "CampaignViewController", bundle: Bundle.frameworkBundle) 466 let vc = SwiftWarplyFramework.CampaignViewController(nibName: "CampaignViewController", bundle: Bundle.frameworkBundle)
447 - // vc.campaignUrl = "https://warply.s3.amazonaws.com/dei/campaigns/DehEasterContest_stage/index.html"
448 - vc.campaignUrl = contestUrls[index]
449 - vc.showHeader = false
450 467
468 + // Use real campaign URL if available, otherwise fallback to static URLs
469 + if index < bannerCampaigns.count {
470 + // Use campaign URL from real campaign data
471 + let campaign = bannerCampaigns[index]
472 + vc.campaignUrl = campaign._campaign_url ?? campaign.index_url ?? contestUrls[min(index, contestUrls.count - 1)]
473 + } else {
474 + // Fallback to static contest URLs
475 + vc.campaignUrl = contestUrls[min(index, contestUrls.count - 1)]
476 + }
477 +
478 + vc.showHeader = false
451 self.navigationController?.pushViewController(vc, animated: true) 479 self.navigationController?.pushViewController(vc, animated: true)
452 } 480 }
453 481
...@@ -469,7 +497,7 @@ import UIKit ...@@ -469,7 +497,7 @@ import UIKit
469 extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{ 497 extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{
470 498
471 public func numberOfSections(in tableView: UITableView) -> Int { 499 public func numberOfSections(in tableView: UITableView) -> Int {
472 - return 9 500 + return sections.count
473 } 501 }
474 502
475 public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 503 public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
...@@ -489,7 +517,6 @@ extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{ ...@@ -489,7 +517,6 @@ extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{
489 } 517 }
490 518
491 public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { 519 public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
492 -// return CGFloat.leastNormalMagnitude
493 return 0.0 520 return 0.0
494 } 521 }
495 522
...@@ -498,62 +525,36 @@ extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{ ...@@ -498,62 +525,36 @@ extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{
498 } 525 }
499 526
500 public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 527 public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
501 - if (indexPath.section == 0) { 528 + guard indexPath.section < sections.count else {
529 + return UITableViewCell() // Return empty cell if section doesn't exist
530 + }
531 +
532 + let sectionModel = sections[indexPath.section]
533 +
534 + switch sectionModel.sectionType {
535 + case .myRewardsBannerOffers:
502 let cell = tableView.dequeueReusableCell(withIdentifier: "MyRewardsBannerOffersScrollTableViewCell", for: indexPath) as! MyRewardsBannerOffersScrollTableViewCell 536 let cell = tableView.dequeueReusableCell(withIdentifier: "MyRewardsBannerOffersScrollTableViewCell", for: indexPath) as! MyRewardsBannerOffersScrollTableViewCell
503 - cell.delegate = self // Set the banner offers delegate 537 + cell.delegate = self
504 - cell.configureCell(data: self.bannerOffersSection) 538 + cell.configureCell(data: sectionModel)
505 -// cell.parent = self
506 return cell 539 return cell
507 540
508 - } else { 541 + case .myRewardsHorizontalCouponsets:
509 let cell = tableView.dequeueReusableCell(withIdentifier: "MyRewardsOffersScrollTableViewCell", for: indexPath) as! MyRewardsOffersScrollTableViewCell 542 let cell = tableView.dequeueReusableCell(withIdentifier: "MyRewardsOffersScrollTableViewCell", for: indexPath) as! MyRewardsOffersScrollTableViewCell
543 + cell.delegate = self
544 + cell.configureCell(data: sectionModel)
545 + return cell
510 546
511 - cell.delegate = self // Set the offers delegate 547 + case .profileHeader, .profileQuestionnaire, .profileCouponFilters, .staticContent:
512 - 548 + // These will be implemented later when needed
513 - if (indexPath.section == 1) { 549 + let cell = UITableViewCell()
514 - cell.configureCell(data: self.topOffersSection) 550 + cell.textLabel?.text = sectionModel.title ?? "Section"
515 - } else if (indexPath.section == 2) {
516 - cell.configureCell(data: self.favoriteOffersSection)
517 - } else if (indexPath.section == 3) {
518 - cell.configureCell(data: self.sustainableOffersSection)
519 - } else if (indexPath.section == 4) {
520 - cell.configureCell(data: self.familyOffersSection)
521 - } else if (indexPath.section == 5) {
522 - cell.configureCell(data: self.foodOffersSection)
523 - } else if (indexPath.section == 6) {
524 - cell.configureCell(data: self.escapeOffersSection)
525 - } else if (indexPath.section == 7) {
526 - cell.configureCell(data: self.childOffersSection)
527 - } else {
528 - cell.configureCell(data: self.shoppingOffersSection)
529 - }
530 -
531 return cell 551 return cell
532 } 552 }
533 -
534 } 553 }
535 554
536 public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 555 public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
537 - if (indexPath.section == 0) { 556 + // Handle selection if needed - currently no action required
538 - // Do nothing - Each button is handled differently 557 + tableView.deselectRow(at: indexPath, animated: true)
539 -
540 - } else if (indexPath.section == 1) {
541 - // Do nothing
542 - } else if (indexPath.section == 2) {
543 - // Do nothing
544 - } else if (indexPath.section == 3) {
545 - // Do nothing
546 - } else if (indexPath.section == 4) {
547 - // Do nothing
548 - } else if (indexPath.section == 5) {
549 - // Do nothing
550 - } else if (indexPath.section == 6) {
551 - // Do nothing
552 - } else if (indexPath.section == 7) {
553 - // Do nothing
554 - } else {
555 - // Do nothing
556 - }
557 } 558 }
558 } 559 }
559 560
......