Manos Chorianopoulos

update SwiftWarplyFramework Client Documentation

......@@ -292,8 +292,11 @@ The SwiftWarplyFramework provides ready-to-use view controllers that you can int
| View Controller | Purpose | Description |
|----------------|---------|-------------|
| `MyRewardsViewController` | Main rewards screen | Displays campaigns, offers, and banners |
| `MyRewardsViewController` | Main rewards screen | Displays carousel banners, filter bar, and horizontally-scrollable category offer rows |
| `ProfileViewController` | User profile & coupons | Shows user profile and coupon management |
| `MyCouponsViewController` | User's coupon list | Full-screen list of the user's active, redeemed, and expired coupons with filter tabs |
| `MapViewController` | Merchant store map | Interactive MKMapView showing merchant store locations; accepts an optional `CouponSetItemModel` coupon and `isMarket` flag |
| `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` |
| `CouponViewController` | Individual coupon details | Displays detailed coupon information |
| `CouponsetViewController` | Coupon set details | Displays detailed coupon set information with expandable description, terms, store locator, and website link |
......@@ -328,6 +331,28 @@ class MainViewController: UIViewController {
// Push onto navigation stack
navigationController?.pushViewController(couponsetVC, animated: true)
}
func showMyCoupons() {
// Create the my coupons view controller
let myCouponsVC = MyCouponsViewController()
navigationController?.pushViewController(myCouponsVC, animated: true)
}
func showMap(for couponSet: CouponSetItemModel? = nil) {
// Create the map view controller
let mapVC = MapViewController()
// Optionally pass a coupon to pre-filter the map to that merchant's stores
// mapVC.coupon = couponItem
// mapVC.isMarket = false
navigationController?.pushViewController(mapVC, animated: true)
}
func showCategoryOffers(for section: SectionModel) {
// Open the full-grid view for a specific category
let categoryVC = CategoryOffersViewController()
categoryVC.sectionData = section
navigationController?.pushViewController(categoryVC, animated: true)
}
}
```
......@@ -1070,6 +1095,90 @@ Task {
}
```
### Get Coupon Filters
Returns the available filter categories for coupon browsing (e.g. "Active", "Redeemed", "Expired").
```swift
// Completion handler
WarplySDK.shared.getCouponFilters(language: "el") { filtersModel in
guard let filtersModel = filtersModel else { return }
print("Filters loaded: \(filtersModel)")
} failureCallback: { errorCode in
print("Failed to get coupon filters: \(errorCode)")
}
// Async/await
Task {
do {
let filtersModel = try await WarplySDK.shared.getCouponFilters(language: "el")
// Use filtersModel to populate filter UI
} catch {
print("Error getting coupon filters: \(error)")
}
}
```
### Get Stores (for a Merchant)
Returns the physical store locations for a specific merchant, used to populate `MapViewController`.
```swift
// Completion handler
WarplySDK.shared.getStores(language: "el", merchantUuid: "merchant-uuid-here") { stores in
guard let stores = stores else { return }
for store in stores {
print("Store: \(store._name), lat: \(store._lat), lon: \(store._lon)")
}
} failureCallback: { errorCode in
print("Failed to get stores: \(errorCode)")
}
// Async/await
Task {
do {
let stores = try await WarplySDK.shared.getStores(language: "el", merchantUuid: "merchant-uuid-here")
print("Loaded \(stores.count) stores")
} catch {
print("Error getting stores: \(error)")
}
}
```
### Get Carousel Content
Returns the carousel banner items displayed at the top of `MyRewardsViewController`.
```swift
// Completion handler
WarplySDK.shared.getCarouselContent { carouselItems in
guard let items = carouselItems else { return }
for item in items {
print("Carousel item: \(item._name), url: \(item._url ?? "none")")
}
} failureCallback: { errorCode in
print("Failed to get carousel content: \(errorCode)")
}
// Async/await
Task {
do {
let items = try await WarplySDK.shared.getCarouselContent()
print("Loaded \(items.count) carousel items")
} catch {
print("Error getting carousel content: \(error)")
}
}
```
**Carousel item URL routing** — when a user taps a carousel banner, the `_url` field determines the navigation:
| URL contains | Action |
|---|---|
| `"singleOffer"` | Find the `CouponSetItemModel` matching `_uuid` and open `CouponsetViewController` |
| `"index.html"` | Open the URL in `CampaignViewController` (WebView) |
| `"Offers?"` | (Future) Navigate to a specific category (uuid TBD) |
### Check Coupon Availability
```swift
......@@ -1754,7 +1863,7 @@ Task {
When reporting issues, please include:
1. **Framework Version**: `2.1.0`
1. **Framework Version**: `2.5.1`
2. **iOS Version**: Minimum 17.0
3. **Error Details**: Full error messages and stack traces
4. **Code Sample**: Minimal reproducible example
......@@ -1765,7 +1874,7 @@ When reporting issues, please include:
```swift
// Get framework version and status
print("Framework Version: 2.1.0")
print("Framework Version: 2.5.1")
print("SDK Initialized: \(WarplySDK.shared != nil)")
print("Network Status: \(WarplySDK.shared.getNetworkStatus())")
print("Current Language: \(WarplySDK.shared.applicationLocale)")
......@@ -1790,6 +1899,31 @@ Start with the Quick Start guide and gradually adopt the advanced features as ne
## 📋 **Changelog**
### **Version 2.5.1** - *March 20, 2026*
#### **🆕 New Features**
- **`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.
- **`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.
- **`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.
- **`MyRewardsViewController` Updated**:
- Banner section now uses `CarouselItemModel` (from `getCarouselContent()`) instead of legacy `CampaignItemModel`.
- Added `MyRewardsFiltersTableViewCell` row beneath the banner section on every screen load, showing a search/filter button and a map button (actions are stubbed — `TODO`).
- Tapping the "All" button on any category row now pushes `CategoryOffersViewController` with the full `SectionModel` for that category.
- Carousel banner tap routing implemented: `singleOffer``CouponsetViewController`; `index.html``CampaignViewController`; `Offers?` → TODO.
- **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.
- **New API: `getCouponFilters()`** — Fetches the available coupon filter categories (`CouponFiltersDataModel`) used by both `MyCouponsViewController` and `ProfileViewController`.
- **New API: `getStores()`** — Fetches the physical store locations (`[MerchantModel]`) for a given merchant UUID, used to populate `MapViewController` pin annotations.
- **New Model: `CarouselItemModel`** — Represents a banner carousel item with fields: `_uuid`, `_name`, `_entity`, `_app_img`, `_app_url`, `_url`, `_web_img`, `_web_img_responsive`.
#### **🔧 Improvements**
- `MyRewardsOfferCollectionViewCell`: `discountLabel` now auto-scales its font down to 50% (`minimumScaleFactor = 0.5`) instead of truncating when the discount text is long.
- `MyRewardsFiltersTableViewCell`: Map button now correctly covers the full 44×44 tap area of `mapView`.
#### **🚨 Breaking Changes**
- **None**: Full backward compatibility maintained
---
### **Version 2.5.0** - *March 13, 2026*
#### **🆕 New Features**
......
......@@ -112,15 +112,23 @@ SwiftWarplyFramework/
│ │ ├── screens/ # Pre-built view controllers
│ │ │ ├── MyRewardsViewController/ # Main rewards screen
│ │ │ ├── ProfileViewController/ # User profile screen
│ │ │ ├── MyCouponsViewController/ # User coupon list screen (NEW 2.5.1)
│ │ │ ├── MapViewController/ # Merchant store map screen (NEW 2.5.1)
│ │ │ ├── CategoryOffersViewController/ # Category offers 2-column grid (NEW 2.5.1)
│ │ │ ├── CouponViewController/ # Single coupon detail
│ │ │ ├── CouponsetViewController/ # Coupon set detail
│ │ │ └── CampaignViewController/ # WebView for campaign URLs
│ │ ├── cells/ # Reusable table/collection view cells
│ │ │ ├── MyRewardsBannerOfferCollectionViewCell/
│ │ │ ├── MyRewardsBannerOffersScrollTableViewCell/
│ │ │ ├── MyRewardsFiltersTableViewCell/ # Filter bar cell (NEW 2.5.1)
│ │ │ ├── MyRewardsOfferCollectionViewCell/
│ │ │ ├── MyRewardsOffersScrollTableViewCell/
│ │ │ ├── MyRewardsProfileInfoTableViewCell/
│ │ │ ├── MyCouponsHeaderTableViewCell/ # Header for MyCouponsViewController (NEW 2.5.1)
│ │ │ ├── CategoryOfferCollectionViewCell/ # Grid card cell (NEW 2.5.1)
│ │ │ ├── CategoryOffersGridTableViewCell/ # Table cell housing 2-col grid (NEW 2.5.1)
│ │ │ ├── CategoryOffersHeaderTableViewCell/ # Header for CategoryOffersViewController (NEW 2.5.1)
│ │ │ ├── ProfileCouponFiltersTableViewCell/
│ │ │ ├── ProfileCouponTableViewCell/
│ │ │ ├── ProfileFilterCollectionViewCell/
......@@ -626,8 +634,12 @@ All models use **dictionary-based initialization** (`init(dictionary: [String: A
| `TransactionModel` | TransactionModel.swift | `transactionDate`, transaction details | Sorted by date descending |
| `PointsHistoryModel` | PointsHistoryModel.swift | `entryDate`, points entries | Sorted by date descending |
| `MarketPassDetailsModel` | Market.swift | Supermarket pass details | Market/supermarket feature |
| `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` |
| `RedeemedSMHistoryModel` | Coupon.swift | `_totalRedeemedValue`, `_redeemedCouponList` | Supermarket coupon redemption history |
| `ArticleModel` | ArticleModel.swift | Content/article fields | Carousel content |
| `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) |
| `CouponFiltersDataModel` | CouponFilterModel.swift | Filter category data | Top-level model returned by `getCouponFilters()`; contains filter lists (NEW 2.5.1) |
| `CouponFilterModel` | CouponFilterModel.swift | `id`, `title`, `count` | Individual filter chip (e.g. "Active", "Redeemed"); used by `MyCouponsViewController` and `ProfileViewController` (NEW 2.5.1) |
| `MerchantCategoryModel` | MerchantCategoryModel.swift | Category fields | Merchant categorization |
| `VerifyTicketResponseModel` | Response.swift | `getStatus` (1=success) | Generic API response wrapper |
| `GenericResponseModel` | Response.swift | Generic response fields | Flexible response wrapper |
......@@ -676,8 +688,11 @@ All screens use **XIB-based** layouts (not SwiftUI) with `Bundle.frameworkBundle
| Screen | Storyboard ID | Purpose |
|--------|--------------|---------|
| `MyRewardsViewController` | — (XIB) | Main rewards dashboard with campaigns, offers, banners |
| `MyRewardsViewController` | — (XIB) | Main rewards dashboard: carousel banners (`CarouselItemModel`), filter bar row, horizontal category offer rows. Fetches `getCarouselContent()`, `getCouponSetsNew()`, `getCouponFilters()` on load. |
| `ProfileViewController` | — (XIB) | User profile with coupons, filters, questionnaires |
| `MyCouponsViewController` | — (XIB) | Full coupon list for the current user with filter tabs. Fetches coupons on load. Public init via `MyCouponsViewController()`. (NEW 2.5.1) |
| `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) |
| `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) |
| `CouponViewController` | — (XIB) | Single coupon detail view |
| `CouponsetViewController` | — (XIB) | Coupon set detail view |
| `CampaignViewController` | `"CampaignViewController"` (Storyboard) | WebView for campaign URLs |
......@@ -707,11 +722,16 @@ if let navigationController = controller.navigationController {
### Cell Architecture
Table/Collection view cells are XIB-based, each in its own directory:
- `MyRewardsBannerOfferCollectionViewCell` — Banner offer in collection view
- `MyRewardsOfferCollectionViewCell` — Standard offer in collection view
- `MyRewardsBannerOffersScrollTableViewCell` — Horizontal scrolling banner offers
- `MyRewardsOffersScrollTableViewCell` — Horizontal scrolling offers
- `MyRewardsBannerOfferCollectionViewCell` — Banner carousel item in collection view (configures from `CarouselItemModel`)
- `MyRewardsOfferCollectionViewCell` — Standard offer card in horizontal-scroll collection view (configures from `CouponSetItemModel`); `discountLabel` auto-scales font to `minimumScaleFactor = 0.5`
- `MyRewardsBannerOffersScrollTableViewCell` — Horizontal scrolling banner carousel with page control; delegate: `MyRewardsBannerOffersScrollTableViewCellDelegate` (`didSelectBannerCarouselItem`)
- `MyRewardsOffersScrollTableViewCell` — Horizontal scrolling offer cards per category; delegate: `MyRewardsOffersScrollTableViewCellDelegate` (`didSelectCouponSet`, `didSelectAllOffers`). `allButton: UIButton` triggers `didSelectAllOffers` via `addTarget`
- `MyRewardsProfileInfoTableViewCell` — Profile info header
- `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)
- `MyCouponsHeaderTableViewCell` — Section header cell for `MyCouponsViewController` (NEW 2.5.1)
- `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)
- `CategoryOffersGridTableViewCell``UITableViewCell` containing a non-scrolling `UICollectionView` (2-column grid); dynamically computes its own height via `collectionViewHeightConstraint`; `didSelectItemAt` pushes `CouponsetViewController` (NEW 2.5.1)
- `CategoryOffersHeaderTableViewCell` — Header cell for `CategoryOffersViewController` showing category title, search/filter button (`filtersView`/`filtersButton`), and map button (`mapView`/`mapButton`) (NEW 2.5.1)
- `ProfileCouponTableViewCell` — Coupon row in profile
- `ProfileCouponFiltersTableViewCell` — Filter chips for coupons
- `ProfileFilterCollectionViewCell` — Individual filter chip
......@@ -804,6 +824,8 @@ public func getCoupons(language: String? = nil, ...) {
- `getCoupons(language:completion:failureCallback:)` / async variant
- `getCouponsUniversal(language:completion:failureCallback:)`
- `getCouponSets(language:completion:failureCallback:)` / async variant
- `getCouponSetsNew(language:completion:failureCallback:)` / async variant — variant used by `MyRewardsViewController`
- `getCouponFilters(language:completion:failureCallback:)` / async variant — returns `CouponFiltersDataModel` (NEW 2.5.1)
- `getAvailableCoupons(completion:)` / async variant
- `validateCoupon(_:completion:)` / async variant
- `redeemCoupon(productId:productUuid:merchantId:completion:)` / async variant
......@@ -826,9 +848,11 @@ public func getCoupons(language: String? = nil, ...) {
**Merchants:**
- `getMerchants(language:categories:defaultShown:center:tags:uuid:distance:parentUuids:completion:failureCallback:)` / async variant
- `getMerchantCategories(language:completion:failureCallback:)` / async variant
- `getStores(language:merchantUuid:completion:failureCallback:)` / async variant — returns `[MerchantModel]` for a specific merchant UUID; used by `MapViewController` (NEW 2.5.1)
**Content:**
- `getArticles(language:categories:completion:failureCallback:)` / async variant
- `getCarouselContent(completion:failureCallback:)` / async variant — returns `[CarouselItemModel]` for the banner carousel in `MyRewardsViewController` (NEW 2.5.1)
**Market/Supermarket:**
- `getMarketPassDetails(completion:failureCallback:)` / async variant
......@@ -1213,6 +1237,13 @@ let keychainDiag = await KeychainManager.shared.getDiagnosticInfo()
| Config | `Configuration/WarplyConfiguration.swift` | `WarplyConfiguration` struct |
| Campaigns | `models/Campaign.swift` | `CampaignItemModel` class |
| Coupons | `models/Coupon.swift` | `CouponItemModel` class |
| Carousel items | `models/CarouselItemModel.swift` | `CarouselItemModel` class (NEW 2.5.1) |
| Coupon filters | `models/CouponFilterModel.swift` | `CouponFiltersDataModel` / `CouponFilterModel` (NEW 2.5.1) |
| Main rewards screen | `screens/MyRewardsViewController/` | Push via `MyRewardsViewController()` |
| Coupon list screen | `screens/MyCouponsViewController/` | Push via `MyCouponsViewController()` (NEW 2.5.1) |
| Map screen | `screens/MapViewController/` | Push via `MapViewController()`; set `coupon` + `isMarket` (NEW 2.5.1) |
| Category grid screen | `screens/CategoryOffersViewController/` | Push via `CategoryOffersViewController()`; set `sectionData` (NEW 2.5.1) |
| UI section types | `models/SectionModel.swift` | `SectionType` enum |
**Critical Rules for AI Agents:**
1. **Always maintain dual event posting** (SwiftEventBus + EventDispatcher)
......