Manos Chorianopoulos

update SwiftWarplyFramework Client Documentation

...@@ -292,8 +292,11 @@ The SwiftWarplyFramework provides ready-to-use view controllers that you can int ...@@ -292,8 +292,11 @@ The SwiftWarplyFramework provides ready-to-use view controllers that you can int
292 292
293 | View Controller | Purpose | Description | 293 | View Controller | Purpose | Description |
294 |----------------|---------|-------------| 294 |----------------|---------|-------------|
295 -| `MyRewardsViewController` | Main rewards screen | Displays campaigns, offers, and banners | 295 +| `MyRewardsViewController` | Main rewards screen | Displays carousel banners, filter bar, and horizontally-scrollable category offer rows |
296 | `ProfileViewController` | User profile & coupons | Shows user profile and coupon management | 296 | `ProfileViewController` | User profile & coupons | Shows user profile and coupon management |
297 +| `MyCouponsViewController` | User's coupon list | Full-screen list of the user's active, redeemed, and expired coupons with filter tabs |
298 +| `MapViewController` | Merchant store map | Interactive MKMapView showing merchant store locations; accepts an optional `CouponSetItemModel` coupon and `isMarket` flag |
299 +| `CategoryOffersViewController` | Category offers grid | 2-column grid of all offers in a specific category; opened from the "All" button on a category row; accepts a `SectionModel` |
297 | `CouponViewController` | Individual coupon details | Displays detailed coupon information | 300 | `CouponViewController` | Individual coupon details | Displays detailed coupon information |
298 | `CouponsetViewController` | Coupon set details | Displays detailed coupon set information with expandable description, terms, store locator, and website link | 301 | `CouponsetViewController` | Coupon set details | Displays detailed coupon set information with expandable description, terms, store locator, and website link |
299 302
...@@ -328,6 +331,28 @@ class MainViewController: UIViewController { ...@@ -328,6 +331,28 @@ class MainViewController: UIViewController {
328 // Push onto navigation stack 331 // Push onto navigation stack
329 navigationController?.pushViewController(couponsetVC, animated: true) 332 navigationController?.pushViewController(couponsetVC, animated: true)
330 } 333 }
334 +
335 + func showMyCoupons() {
336 + // Create the my coupons view controller
337 + let myCouponsVC = MyCouponsViewController()
338 + navigationController?.pushViewController(myCouponsVC, animated: true)
339 + }
340 +
341 + func showMap(for couponSet: CouponSetItemModel? = nil) {
342 + // Create the map view controller
343 + let mapVC = MapViewController()
344 + // Optionally pass a coupon to pre-filter the map to that merchant's stores
345 + // mapVC.coupon = couponItem
346 + // mapVC.isMarket = false
347 + navigationController?.pushViewController(mapVC, animated: true)
348 + }
349 +
350 + func showCategoryOffers(for section: SectionModel) {
351 + // Open the full-grid view for a specific category
352 + let categoryVC = CategoryOffersViewController()
353 + categoryVC.sectionData = section
354 + navigationController?.pushViewController(categoryVC, animated: true)
355 + }
331 } 356 }
332 ``` 357 ```
333 358
...@@ -1070,6 +1095,90 @@ Task { ...@@ -1070,6 +1095,90 @@ Task {
1070 } 1095 }
1071 ``` 1096 ```
1072 1097
1098 +### Get Coupon Filters
1099 +
1100 +Returns the available filter categories for coupon browsing (e.g. "Active", "Redeemed", "Expired").
1101 +
1102 +```swift
1103 +// Completion handler
1104 +WarplySDK.shared.getCouponFilters(language: "el") { filtersModel in
1105 + guard let filtersModel = filtersModel else { return }
1106 + print("Filters loaded: \(filtersModel)")
1107 +} failureCallback: { errorCode in
1108 + print("Failed to get coupon filters: \(errorCode)")
1109 +}
1110 +
1111 +// Async/await
1112 +Task {
1113 + do {
1114 + let filtersModel = try await WarplySDK.shared.getCouponFilters(language: "el")
1115 + // Use filtersModel to populate filter UI
1116 + } catch {
1117 + print("Error getting coupon filters: \(error)")
1118 + }
1119 +}
1120 +```
1121 +
1122 +### Get Stores (for a Merchant)
1123 +
1124 +Returns the physical store locations for a specific merchant, used to populate `MapViewController`.
1125 +
1126 +```swift
1127 +// Completion handler
1128 +WarplySDK.shared.getStores(language: "el", merchantUuid: "merchant-uuid-here") { stores in
1129 + guard let stores = stores else { return }
1130 + for store in stores {
1131 + print("Store: \(store._name), lat: \(store._lat), lon: \(store._lon)")
1132 + }
1133 +} failureCallback: { errorCode in
1134 + print("Failed to get stores: \(errorCode)")
1135 +}
1136 +
1137 +// Async/await
1138 +Task {
1139 + do {
1140 + let stores = try await WarplySDK.shared.getStores(language: "el", merchantUuid: "merchant-uuid-here")
1141 + print("Loaded \(stores.count) stores")
1142 + } catch {
1143 + print("Error getting stores: \(error)")
1144 + }
1145 +}
1146 +```
1147 +
1148 +### Get Carousel Content
1149 +
1150 +Returns the carousel banner items displayed at the top of `MyRewardsViewController`.
1151 +
1152 +```swift
1153 +// Completion handler
1154 +WarplySDK.shared.getCarouselContent { carouselItems in
1155 + guard let items = carouselItems else { return }
1156 + for item in items {
1157 + print("Carousel item: \(item._name), url: \(item._url ?? "none")")
1158 + }
1159 +} failureCallback: { errorCode in
1160 + print("Failed to get carousel content: \(errorCode)")
1161 +}
1162 +
1163 +// Async/await
1164 +Task {
1165 + do {
1166 + let items = try await WarplySDK.shared.getCarouselContent()
1167 + print("Loaded \(items.count) carousel items")
1168 + } catch {
1169 + print("Error getting carousel content: \(error)")
1170 + }
1171 +}
1172 +```
1173 +
1174 +**Carousel item URL routing** — when a user taps a carousel banner, the `_url` field determines the navigation:
1175 +
1176 +| URL contains | Action |
1177 +|---|---|
1178 +| `"singleOffer"` | Find the `CouponSetItemModel` matching `_uuid` and open `CouponsetViewController` |
1179 +| `"index.html"` | Open the URL in `CampaignViewController` (WebView) |
1180 +| `"Offers?"` | (Future) Navigate to a specific category (uuid TBD) |
1181 +
1073 ### Check Coupon Availability 1182 ### Check Coupon Availability
1074 1183
1075 ```swift 1184 ```swift
...@@ -1754,7 +1863,7 @@ Task { ...@@ -1754,7 +1863,7 @@ Task {
1754 1863
1755 When reporting issues, please include: 1864 When reporting issues, please include:
1756 1865
1757 -1. **Framework Version**: `2.1.0` 1866 +1. **Framework Version**: `2.5.1`
1758 2. **iOS Version**: Minimum 17.0 1867 2. **iOS Version**: Minimum 17.0
1759 3. **Error Details**: Full error messages and stack traces 1868 3. **Error Details**: Full error messages and stack traces
1760 4. **Code Sample**: Minimal reproducible example 1869 4. **Code Sample**: Minimal reproducible example
...@@ -1765,7 +1874,7 @@ When reporting issues, please include: ...@@ -1765,7 +1874,7 @@ When reporting issues, please include:
1765 1874
1766 ```swift 1875 ```swift
1767 // Get framework version and status 1876 // Get framework version and status
1768 -print("Framework Version: 2.1.0") 1877 +print("Framework Version: 2.5.1")
1769 print("SDK Initialized: \(WarplySDK.shared != nil)") 1878 print("SDK Initialized: \(WarplySDK.shared != nil)")
1770 print("Network Status: \(WarplySDK.shared.getNetworkStatus())") 1879 print("Network Status: \(WarplySDK.shared.getNetworkStatus())")
1771 print("Current Language: \(WarplySDK.shared.applicationLocale)") 1880 print("Current Language: \(WarplySDK.shared.applicationLocale)")
...@@ -1790,6 +1899,31 @@ Start with the Quick Start guide and gradually adopt the advanced features as ne ...@@ -1790,6 +1899,31 @@ Start with the Quick Start guide and gradually adopt the advanced features as ne
1790 1899
1791 ## 📋 **Changelog** 1900 ## 📋 **Changelog**
1792 1901
1902 +### **Version 2.5.1** - *March 20, 2026*
1903 +
1904 +#### **🆕 New Features**
1905 +- **`MyCouponsViewController` Added**: New full-screen view controller displaying the user's complete coupon list with filter tabs (Active, Redeemed, Expired). Loads coupon data from the API on launch and supports dynamic filter selection.
1906 +- **`MapViewController` Added**: New map view controller using `MKMapView` to display merchant store locations. Accepts an optional `coupon: CouponItemModel?` to pre-filter stores for a specific merchant, and an `isMarket: Bool?` flag to differentiate supermarket vs. regular merchant context.
1907 +- **`CategoryOffersViewController` Added**: New view controller displaying a 2-column grid of all offers within a specific category. Opened automatically from the "All" button on any category row in `MyRewardsViewController`. Accepts a `sectionData: SectionModel` property containing the full offer list.
1908 +- **`MyRewardsViewController` Updated**:
1909 + - Banner section now uses `CarouselItemModel` (from `getCarouselContent()`) instead of legacy `CampaignItemModel`.
1910 + - Added `MyRewardsFiltersTableViewCell` row beneath the banner section on every screen load, showing a search/filter button and a map button (actions are stubbed — `TODO`).
1911 + - Tapping the "All" button on any category row now pushes `CategoryOffersViewController` with the full `SectionModel` for that category.
1912 + - Carousel banner tap routing implemented: `singleOffer``CouponsetViewController`; `index.html``CampaignViewController`; `Offers?` → TODO.
1913 +- **New API: `getCarouselContent()`** — Fetches the ordered list of carousel banner items (`[CarouselItemModel]`) for the banner section. Each item carries a `_url` field that drives tap-navigation routing.
1914 +- **New API: `getCouponFilters()`** — Fetches the available coupon filter categories (`CouponFiltersDataModel`) used by both `MyCouponsViewController` and `ProfileViewController`.
1915 +- **New API: `getStores()`** — Fetches the physical store locations (`[MerchantModel]`) for a given merchant UUID, used to populate `MapViewController` pin annotations.
1916 +- **New Model: `CarouselItemModel`** — Represents a banner carousel item with fields: `_uuid`, `_name`, `_entity`, `_app_img`, `_app_url`, `_url`, `_web_img`, `_web_img_responsive`.
1917 +
1918 +#### **🔧 Improvements**
1919 +- `MyRewardsOfferCollectionViewCell`: `discountLabel` now auto-scales its font down to 50% (`minimumScaleFactor = 0.5`) instead of truncating when the discount text is long.
1920 +- `MyRewardsFiltersTableViewCell`: Map button now correctly covers the full 44×44 tap area of `mapView`.
1921 +
1922 +#### **🚨 Breaking Changes**
1923 +- **None**: Full backward compatibility maintained
1924 +
1925 +---
1926 +
1793 ### **Version 2.5.0** - *March 13, 2026* 1927 ### **Version 2.5.0** - *March 13, 2026*
1794 1928
1795 #### **🆕 New Features** 1929 #### **🆕 New Features**
......
...@@ -112,15 +112,23 @@ SwiftWarplyFramework/ ...@@ -112,15 +112,23 @@ SwiftWarplyFramework/
112 │ │ ├── screens/ # Pre-built view controllers 112 │ │ ├── screens/ # Pre-built view controllers
113 │ │ │ ├── MyRewardsViewController/ # Main rewards screen 113 │ │ │ ├── MyRewardsViewController/ # Main rewards screen
114 │ │ │ ├── ProfileViewController/ # User profile screen 114 │ │ │ ├── ProfileViewController/ # User profile screen
115 +│ │ │ ├── MyCouponsViewController/ # User coupon list screen (NEW 2.5.1)
116 +│ │ │ ├── MapViewController/ # Merchant store map screen (NEW 2.5.1)
117 +│ │ │ ├── CategoryOffersViewController/ # Category offers 2-column grid (NEW 2.5.1)
115 │ │ │ ├── CouponViewController/ # Single coupon detail 118 │ │ │ ├── CouponViewController/ # Single coupon detail
116 │ │ │ ├── CouponsetViewController/ # Coupon set detail 119 │ │ │ ├── CouponsetViewController/ # Coupon set detail
117 │ │ │ └── CampaignViewController/ # WebView for campaign URLs 120 │ │ │ └── CampaignViewController/ # WebView for campaign URLs
118 │ │ ├── cells/ # Reusable table/collection view cells 121 │ │ ├── cells/ # Reusable table/collection view cells
119 │ │ │ ├── MyRewardsBannerOfferCollectionViewCell/ 122 │ │ │ ├── MyRewardsBannerOfferCollectionViewCell/
120 │ │ │ ├── MyRewardsBannerOffersScrollTableViewCell/ 123 │ │ │ ├── MyRewardsBannerOffersScrollTableViewCell/
124 +│ │ │ ├── MyRewardsFiltersTableViewCell/ # Filter bar cell (NEW 2.5.1)
121 │ │ │ ├── MyRewardsOfferCollectionViewCell/ 125 │ │ │ ├── MyRewardsOfferCollectionViewCell/
122 │ │ │ ├── MyRewardsOffersScrollTableViewCell/ 126 │ │ │ ├── MyRewardsOffersScrollTableViewCell/
123 │ │ │ ├── MyRewardsProfileInfoTableViewCell/ 127 │ │ │ ├── MyRewardsProfileInfoTableViewCell/
128 +│ │ │ ├── MyCouponsHeaderTableViewCell/ # Header for MyCouponsViewController (NEW 2.5.1)
129 +│ │ │ ├── CategoryOfferCollectionViewCell/ # Grid card cell (NEW 2.5.1)
130 +│ │ │ ├── CategoryOffersGridTableViewCell/ # Table cell housing 2-col grid (NEW 2.5.1)
131 +│ │ │ ├── CategoryOffersHeaderTableViewCell/ # Header for CategoryOffersViewController (NEW 2.5.1)
124 │ │ │ ├── ProfileCouponFiltersTableViewCell/ 132 │ │ │ ├── ProfileCouponFiltersTableViewCell/
125 │ │ │ ├── ProfileCouponTableViewCell/ 133 │ │ │ ├── ProfileCouponTableViewCell/
126 │ │ │ ├── ProfileFilterCollectionViewCell/ 134 │ │ │ ├── ProfileFilterCollectionViewCell/
...@@ -626,8 +634,12 @@ All models use **dictionary-based initialization** (`init(dictionary: [String: A ...@@ -626,8 +634,12 @@ All models use **dictionary-based initialization** (`init(dictionary: [String: A
626 | `TransactionModel` | TransactionModel.swift | `transactionDate`, transaction details | Sorted by date descending | 634 | `TransactionModel` | TransactionModel.swift | `transactionDate`, transaction details | Sorted by date descending |
627 | `PointsHistoryModel` | PointsHistoryModel.swift | `entryDate`, points entries | Sorted by date descending | 635 | `PointsHistoryModel` | PointsHistoryModel.swift | `entryDate`, points entries | Sorted by date descending |
628 | `MarketPassDetailsModel` | Market.swift | Supermarket pass details | Market/supermarket feature | 636 | `MarketPassDetailsModel` | Market.swift | Supermarket pass details | Market/supermarket feature |
637 +| `SectionModel` | SectionModel.swift | `sectionType: SectionType`, `title`, `items`, `itemType`, `count`, `metadata` | Drives `UITableView` row rendering. `SectionType` cases: `.myRewardsProfileInfo`, `.myRewardsFilters` (NEW 2.5.1), `.myRewardsBannerOffers`, `.myRewardsHorizontalCouponsets`, `.profileHeader`, `.profileQuestionnaire`, `.profileCouponFilters`, `.profileCoupon`, `.staticContent` |
629 | `RedeemedSMHistoryModel` | Coupon.swift | `_totalRedeemedValue`, `_redeemedCouponList` | Supermarket coupon redemption history | 638 | `RedeemedSMHistoryModel` | Coupon.swift | `_totalRedeemedValue`, `_redeemedCouponList` | Supermarket coupon redemption history |
630 | `ArticleModel` | ArticleModel.swift | Content/article fields | Carousel content | 639 | `ArticleModel` | ArticleModel.swift | Content/article fields | Carousel content |
640 +| `CarouselItemModel` | CarouselItemModel.swift | `_uuid`, `_name`, `_entity`, `_app_img`, `_app_url`, `_url`, `_web_img`, `_web_img_responsive` | Banner carousel items for MyRewardsViewController; `_url` drives tap navigation routing (NEW 2.5.1) |
641 +| `CouponFiltersDataModel` | CouponFilterModel.swift | Filter category data | Top-level model returned by `getCouponFilters()`; contains filter lists (NEW 2.5.1) |
642 +| `CouponFilterModel` | CouponFilterModel.swift | `id`, `title`, `count` | Individual filter chip (e.g. "Active", "Redeemed"); used by `MyCouponsViewController` and `ProfileViewController` (NEW 2.5.1) |
631 | `MerchantCategoryModel` | MerchantCategoryModel.swift | Category fields | Merchant categorization | 643 | `MerchantCategoryModel` | MerchantCategoryModel.swift | Category fields | Merchant categorization |
632 | `VerifyTicketResponseModel` | Response.swift | `getStatus` (1=success) | Generic API response wrapper | 644 | `VerifyTicketResponseModel` | Response.swift | `getStatus` (1=success) | Generic API response wrapper |
633 | `GenericResponseModel` | Response.swift | Generic response fields | Flexible response wrapper | 645 | `GenericResponseModel` | Response.swift | Generic response fields | Flexible response wrapper |
...@@ -676,8 +688,11 @@ All screens use **XIB-based** layouts (not SwiftUI) with `Bundle.frameworkBundle ...@@ -676,8 +688,11 @@ All screens use **XIB-based** layouts (not SwiftUI) with `Bundle.frameworkBundle
676 688
677 | Screen | Storyboard ID | Purpose | 689 | Screen | Storyboard ID | Purpose |
678 |--------|--------------|---------| 690 |--------|--------------|---------|
679 -| `MyRewardsViewController` | — (XIB) | Main rewards dashboard with campaigns, offers, banners | 691 +| `MyRewardsViewController` | — (XIB) | Main rewards dashboard: carousel banners (`CarouselItemModel`), filter bar row, horizontal category offer rows. Fetches `getCarouselContent()`, `getCouponSetsNew()`, `getCouponFilters()` on load. |
680 | `ProfileViewController` | — (XIB) | User profile with coupons, filters, questionnaires | 692 | `ProfileViewController` | — (XIB) | User profile with coupons, filters, questionnaires |
693 +| `MyCouponsViewController` | — (XIB) | Full coupon list for the current user with filter tabs. Fetches coupons on load. Public init via `MyCouponsViewController()`. (NEW 2.5.1) |
694 +| `MapViewController` | — (XIB) | `MKMapView`-based merchant store map. `public var coupon: CouponItemModel?` (optional, filters to merchant's stores). `public var isMarket: Bool?` (supermarket vs regular). Calls `getStores(merchantUuid:)` internally. (NEW 2.5.1) |
695 +| `CategoryOffersViewController` | — (XIB) | 2-column grid of all offers in a category. `public var sectionData: SectionModel?`. Pushed from `MyRewardsOffersScrollTableViewCell` "All" button via `didSelectAllOffers` delegate. Navigation bar shown with back button via `setBackButton()`. (NEW 2.5.1) |
681 | `CouponViewController` | — (XIB) | Single coupon detail view | 696 | `CouponViewController` | — (XIB) | Single coupon detail view |
682 | `CouponsetViewController` | — (XIB) | Coupon set detail view | 697 | `CouponsetViewController` | — (XIB) | Coupon set detail view |
683 | `CampaignViewController` | `"CampaignViewController"` (Storyboard) | WebView for campaign URLs | 698 | `CampaignViewController` | `"CampaignViewController"` (Storyboard) | WebView for campaign URLs |
...@@ -707,11 +722,16 @@ if let navigationController = controller.navigationController { ...@@ -707,11 +722,16 @@ if let navigationController = controller.navigationController {
707 722
708 ### Cell Architecture 723 ### Cell Architecture
709 Table/Collection view cells are XIB-based, each in its own directory: 724 Table/Collection view cells are XIB-based, each in its own directory:
710 -- `MyRewardsBannerOfferCollectionViewCell` — Banner offer in collection view 725 +- `MyRewardsBannerOfferCollectionViewCell` — Banner carousel item in collection view (configures from `CarouselItemModel`)
711 -- `MyRewardsOfferCollectionViewCell` — Standard offer in collection view 726 +- `MyRewardsOfferCollectionViewCell` — Standard offer card in horizontal-scroll collection view (configures from `CouponSetItemModel`); `discountLabel` auto-scales font to `minimumScaleFactor = 0.5`
712 -- `MyRewardsBannerOffersScrollTableViewCell` — Horizontal scrolling banner offers 727 +- `MyRewardsBannerOffersScrollTableViewCell` — Horizontal scrolling banner carousel with page control; delegate: `MyRewardsBannerOffersScrollTableViewCellDelegate` (`didSelectBannerCarouselItem`)
713 -- `MyRewardsOffersScrollTableViewCell` — Horizontal scrolling offers 728 +- `MyRewardsOffersScrollTableViewCell` — Horizontal scrolling offer cards per category; delegate: `MyRewardsOffersScrollTableViewCellDelegate` (`didSelectCouponSet`, `didSelectAllOffers`). `allButton: UIButton` triggers `didSelectAllOffers` via `addTarget`
714 - `MyRewardsProfileInfoTableViewCell` — Profile info header 729 - `MyRewardsProfileInfoTableViewCell` — Profile info header
730 +- `MyRewardsFiltersTableViewCell` — Filter bar row with `filtersButton` (search/filter) and `mapButton` (map); both are transparent overlay buttons covering their parent views; actions are `TODO` stubs (NEW 2.5.1)
731 +- `MyCouponsHeaderTableViewCell` — Section header cell for `MyCouponsViewController` (NEW 2.5.1)
732 +- `CategoryOfferCollectionViewCell` — Card cell for `CategoryOffersViewController` 2-column grid; configures from `CouponSetItemModel` only; top half = gray banner + centered logo + discount circle; bottom half = merchant name + bold title + subtitle + expiry (NEW 2.5.1)
733 +- `CategoryOffersGridTableViewCell``UITableViewCell` containing a non-scrolling `UICollectionView` (2-column grid); dynamically computes its own height via `collectionViewHeightConstraint`; `didSelectItemAt` pushes `CouponsetViewController` (NEW 2.5.1)
734 +- `CategoryOffersHeaderTableViewCell` — Header cell for `CategoryOffersViewController` showing category title, search/filter button (`filtersView`/`filtersButton`), and map button (`mapView`/`mapButton`) (NEW 2.5.1)
715 - `ProfileCouponTableViewCell` — Coupon row in profile 735 - `ProfileCouponTableViewCell` — Coupon row in profile
716 - `ProfileCouponFiltersTableViewCell` — Filter chips for coupons 736 - `ProfileCouponFiltersTableViewCell` — Filter chips for coupons
717 - `ProfileFilterCollectionViewCell` — Individual filter chip 737 - `ProfileFilterCollectionViewCell` — Individual filter chip
...@@ -804,6 +824,8 @@ public func getCoupons(language: String? = nil, ...) { ...@@ -804,6 +824,8 @@ public func getCoupons(language: String? = nil, ...) {
804 - `getCoupons(language:completion:failureCallback:)` / async variant 824 - `getCoupons(language:completion:failureCallback:)` / async variant
805 - `getCouponsUniversal(language:completion:failureCallback:)` 825 - `getCouponsUniversal(language:completion:failureCallback:)`
806 - `getCouponSets(language:completion:failureCallback:)` / async variant 826 - `getCouponSets(language:completion:failureCallback:)` / async variant
827 +- `getCouponSetsNew(language:completion:failureCallback:)` / async variant — variant used by `MyRewardsViewController`
828 +- `getCouponFilters(language:completion:failureCallback:)` / async variant — returns `CouponFiltersDataModel` (NEW 2.5.1)
807 - `getAvailableCoupons(completion:)` / async variant 829 - `getAvailableCoupons(completion:)` / async variant
808 - `validateCoupon(_:completion:)` / async variant 830 - `validateCoupon(_:completion:)` / async variant
809 - `redeemCoupon(productId:productUuid:merchantId:completion:)` / async variant 831 - `redeemCoupon(productId:productUuid:merchantId:completion:)` / async variant
...@@ -826,9 +848,11 @@ public func getCoupons(language: String? = nil, ...) { ...@@ -826,9 +848,11 @@ public func getCoupons(language: String? = nil, ...) {
826 **Merchants:** 848 **Merchants:**
827 - `getMerchants(language:categories:defaultShown:center:tags:uuid:distance:parentUuids:completion:failureCallback:)` / async variant 849 - `getMerchants(language:categories:defaultShown:center:tags:uuid:distance:parentUuids:completion:failureCallback:)` / async variant
828 - `getMerchantCategories(language:completion:failureCallback:)` / async variant 850 - `getMerchantCategories(language:completion:failureCallback:)` / async variant
851 +- `getStores(language:merchantUuid:completion:failureCallback:)` / async variant — returns `[MerchantModel]` for a specific merchant UUID; used by `MapViewController` (NEW 2.5.1)
829 852
830 **Content:** 853 **Content:**
831 - `getArticles(language:categories:completion:failureCallback:)` / async variant 854 - `getArticles(language:categories:completion:failureCallback:)` / async variant
855 +- `getCarouselContent(completion:failureCallback:)` / async variant — returns `[CarouselItemModel]` for the banner carousel in `MyRewardsViewController` (NEW 2.5.1)
832 856
833 **Market/Supermarket:** 857 **Market/Supermarket:**
834 - `getMarketPassDetails(completion:failureCallback:)` / async variant 858 - `getMarketPassDetails(completion:failureCallback:)` / async variant
...@@ -1213,6 +1237,13 @@ let keychainDiag = await KeychainManager.shared.getDiagnosticInfo() ...@@ -1213,6 +1237,13 @@ let keychainDiag = await KeychainManager.shared.getDiagnosticInfo()
1213 | Config | `Configuration/WarplyConfiguration.swift` | `WarplyConfiguration` struct | 1237 | Config | `Configuration/WarplyConfiguration.swift` | `WarplyConfiguration` struct |
1214 | Campaigns | `models/Campaign.swift` | `CampaignItemModel` class | 1238 | Campaigns | `models/Campaign.swift` | `CampaignItemModel` class |
1215 | Coupons | `models/Coupon.swift` | `CouponItemModel` class | 1239 | Coupons | `models/Coupon.swift` | `CouponItemModel` class |
1240 +| Carousel items | `models/CarouselItemModel.swift` | `CarouselItemModel` class (NEW 2.5.1) |
1241 +| Coupon filters | `models/CouponFilterModel.swift` | `CouponFiltersDataModel` / `CouponFilterModel` (NEW 2.5.1) |
1242 +| Main rewards screen | `screens/MyRewardsViewController/` | Push via `MyRewardsViewController()` |
1243 +| Coupon list screen | `screens/MyCouponsViewController/` | Push via `MyCouponsViewController()` (NEW 2.5.1) |
1244 +| Map screen | `screens/MapViewController/` | Push via `MapViewController()`; set `coupon` + `isMarket` (NEW 2.5.1) |
1245 +| Category grid screen | `screens/CategoryOffersViewController/` | Push via `CategoryOffersViewController()`; set `sectionData` (NEW 2.5.1) |
1246 +| UI section types | `models/SectionModel.swift` | `SectionType` enum |
1216 1247
1217 **Critical Rules for AI Agents:** 1248 **Critical Rules for AI Agents:**
1218 1. **Always maintain dual event posting** (SwiftEventBus + EventDispatcher) 1249 1. **Always maintain dual event posting** (SwiftEventBus + EventDispatcher)
......