Manos Chorianopoulos

MyRewards dynamic profile info

......@@ -7,7 +7,7 @@
<key>Pods-SwiftWarplyFramework.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
</dict>
</dict>
......
......@@ -40,6 +40,8 @@
1E4C4CFB2DE6014500279AAD /* CopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4C4CFA2DE6014500279AAD /* CopyableLabel.swift */; };
1E64E1832DE48E0600543217 /* MyRewardsOfferCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E64E1822DE48E0600543217 /* MyRewardsOfferCollectionViewCell.xib */; };
1E64E1842DE48E0600543217 /* MyRewardsOfferCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E64E1812DE48E0600543217 /* MyRewardsOfferCollectionViewCell.swift */; };
1E66E4972E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E66E4962E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.xib */; };
1E66E4982E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E66E4952E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.swift */; };
1E917CD62DDF64B2002221D8 /* MyRewardsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E917CD52DDF64B2002221D8 /* MyRewardsViewController.xib */; };
1E917CD72DDF64B2002221D8 /* MyRewardsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E917CD42DDF64B2002221D8 /* MyRewardsViewController.swift */; };
1E917CDB2DDF68C7002221D8 /* CouponViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1E917CDA2DDF68C7002221D8 /* CouponViewController.xib */; };
......@@ -116,6 +118,8 @@
1E4C4CFA2DE6014500279AAD /* CopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLabel.swift; sourceTree = "<group>"; };
1E64E1812DE48E0600543217 /* MyRewardsOfferCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyRewardsOfferCollectionViewCell.swift; sourceTree = "<group>"; };
1E64E1822DE48E0600543217 /* MyRewardsOfferCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyRewardsOfferCollectionViewCell.xib; sourceTree = "<group>"; };
1E66E4952E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyRewardsProfileInfoTableViewCell.swift; sourceTree = "<group>"; };
1E66E4962E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyRewardsProfileInfoTableViewCell.xib; sourceTree = "<group>"; };
1E917CD42DDF64B2002221D8 /* MyRewardsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyRewardsViewController.swift; sourceTree = "<group>"; };
1E917CD52DDF64B2002221D8 /* MyRewardsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MyRewardsViewController.xib; sourceTree = "<group>"; };
1E917CD92DDF68C7002221D8 /* CouponViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponViewController.swift; sourceTree = "<group>"; };
......@@ -276,6 +280,15 @@
path = MyRewardsOfferCollectionViewCell;
sourceTree = "<group>";
};
1E66E4942E30F8C600BCEF9D /* MyRewardsProfileInfoTableViewCell */ = {
isa = PBXGroup;
children = (
1E66E4952E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.swift */,
1E66E4962E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.xib */,
);
path = MyRewardsProfileInfoTableViewCell;
sourceTree = "<group>";
};
1E917CD32DDF6472002221D8 /* MyRewardsViewController */ = {
isa = PBXGroup;
children = (
......@@ -326,6 +339,7 @@
1ED41E492DE0C21800836ABA /* MyRewardsBannerOfferCollectionViewCell */,
1EB4F4282DE0A09500D934C0 /* MyRewardsOffersScrollTableViewCell */,
1EB4F4222DE09A4300D934C0 /* MyRewardsBannerOffersScrollTableViewCell */,
1E66E4942E30F8C600BCEF9D /* MyRewardsProfileInfoTableViewCell */,
);
path = cells;
sourceTree = "<group>";
......@@ -588,6 +602,7 @@
1EA8E5C12DDF427A00CD3418 /* PingLCG-Light.otf in Resources */,
1EA8E5C22DDF427A00CD3418 /* PingLCG-Regular.otf in Resources */,
1EB4F42B2DE0A0AF00D934C0 /* MyRewardsOffersScrollTableViewCell.xib in Resources */,
1E66E4972E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.xib in Resources */,
1EDBAF102DE8443B00911E79 /* ProfileHeaderTableViewCell.xib in Resources */,
1E917CDB2DDF68C7002221D8 /* CouponViewController.xib in Resources */,
1E917CE02DDF6909002221D8 /* ProfileViewController.xib in Resources */,
......@@ -668,6 +683,7 @@
E6A77853282933340045BBA8 /* SwiftWarplyFramework.docc in Sources */,
1EDBAF092DE843FB00911E79 /* ProfileCouponFiltersTableViewCell.swift in Sources */,
E6A778DF282933E60045BBA8 /* WarplyReactMethods.m in Sources */,
1E66E4982E30F9F200BCEF9D /* MyRewardsProfileInfoTableViewCell.swift in Sources */,
1E917CD72DDF64B2002221D8 /* MyRewardsViewController.swift in Sources */,
1E917CDC2DDF68C7002221D8 /* CouponViewController.swift in Sources */,
1E4C4CFB2DE6014500279AAD /* CopyableLabel.swift in Sources */,
......
......@@ -7,7 +7,7 @@
<key>SwiftWarplyFramework.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>0</integer>
</dict>
</dict>
</dict>
......
......@@ -9,7 +9,7 @@ import UIKit
protocol MyRewardsBannerOffersScrollTableViewCellDelegate: AnyObject {
func didSelectBannerOffer(_ index: Int)
func didTapProfileButton()
// func didTapProfileButton()
}
@objc(MyRewardsBannerOffersScrollTableViewCell)
......@@ -103,7 +103,7 @@ public class MyRewardsBannerOffersScrollTableViewCell: UITableViewCell {
@objc private func profileButtonTapped() {
// TODO: UNCOMMENT
delegate?.didTapProfileButton()
// delegate?.didTapProfileButton()
}
}
......
......@@ -17,7 +17,7 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="VzF-dQ-3Wa" userLabel="Parent View">
<rect key="frame" x="0.0" y="0.0" width="413" height="465"/>
<rect key="frame" x="0.0" y="0.0" width="413" height="349"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JTs-J5-DO2" userLabel="Top View">
<rect key="frame" x="24" y="36" width="365" height="35"/>
......@@ -106,7 +106,7 @@
</constraints>
</view>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="I7z-Fz-SNP">
<rect key="frame" x="0.0" y="91" width="413" height="348"/>
<rect key="frame" x="0.0" y="-25" width="413" height="348"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="348" id="aEf-Xl-A2C"/>
......@@ -124,7 +124,7 @@
</connections>
</collectionView>
<pageControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" numberOfPages="3" translatesAutoresizingMaskIntoConstraints="NO" id="lOm-CZ-dVG">
<rect key="frame" x="170" y="439" width="73" height="26"/>
<rect key="frame" x="170" y="323" width="73" height="26"/>
</pageControl>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
......@@ -136,9 +136,14 @@
<constraint firstAttribute="trailing" secondItem="JTs-J5-DO2" secondAttribute="trailing" constant="24" id="Rdi-FK-2Ft"/>
<constraint firstAttribute="trailing" secondItem="I7z-Fz-SNP" secondAttribute="trailing" id="avB-Gc-ttI"/>
<constraint firstItem="I7z-Fz-SNP" firstAttribute="leading" secondItem="VzF-dQ-3Wa" secondAttribute="leading" id="pTY-7d-s1S"/>
<constraint firstItem="I7z-Fz-SNP" firstAttribute="top" secondItem="JTs-J5-DO2" secondAttribute="bottom" constant="20" id="vJF-jy-p83"/>
<constraint firstItem="I7z-Fz-SNP" firstAttribute="top" secondItem="VzF-dQ-3Wa" secondAttribute="top" constant="20" id="vJF-jy-p83"/>
<constraint firstItem="lOm-CZ-dVG" firstAttribute="centerX" secondItem="VzF-dQ-3Wa" secondAttribute="centerX" id="xpw-tV-NcW"/>
</constraints>
<variation key="default">
<mask key="subviews">
<exclude reference="JTs-J5-DO2"/>
</mask>
</variation>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
......
//
// MyRewardsProfileInfoTableViewCell.swift
// SwiftWarplyFramework
//
// Created by Manos Chorianopoulos on 23/7/25.
//
import UIKit
protocol MyRewardsProfileInfoTableViewCellDelegate: AnyObject {
func didTapProfileButton()
}
@objc(MyRewardsProfileInfoTableViewCell)
public class MyRewardsProfileInfoTableViewCell: UITableViewCell {
@IBOutlet weak var tagView1: UIView!
@IBOutlet weak var tagLabel1: UILabel!
@IBOutlet weak var tagView2: UIView!
@IBOutlet weak var tagLabel2: UILabel!
@IBOutlet weak var profileImage: UIImageView!
@IBOutlet weak var profileButton: UIButton!
weak var delegate: MyRewardsProfileInfoTableViewCellDelegate?
// MARK: - Properties
private var profileModel: ProfileModel?
public override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
profileImage.image = UIImage(named: "profile_pic_default", in: Bundle.frameworkResourceBundle, compatibleWith: nil)
profileButton.addTarget(self, action: #selector(profileButtonTapped), for: .touchUpInside)
tagView1.backgroundColor = UIColor(rgb: 0x09914E)
tagView1.layer.cornerRadius = 4.0
tagLabel1.font = UIFont(name: "PingLCG-Regular", size: 17)
tagLabel1.textColor = UIColor(rgb: 0xFFFFFF)
tagView2.backgroundColor = UIColor(rgb: 0xFC9F25)
tagView2.layer.cornerRadius = 4.0
tagLabel2.font = UIFont(name: "PingLCG-Regular", size: 17)
tagLabel2.textColor = UIColor(rgb: 0xFFFFFF)
}
public override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
@objc private func profileButtonTapped() {
delegate?.didTapProfileButton()
}
// MARK: - Configuration Methods
public func configureCell(data: SectionModel?) {
guard let data = data,
let itemType = data.itemType,
itemType == .profile else {
configureDefaultState()
return
}
// Check if we have real profile data
if let metadata = data.metadata,
let profile = metadata["profile"] as? ProfileModel {
// Configure with real profile data
configureCell(profile: profile)
} else {
// Configure with default state (no profile data)
configureDefaultState()
}
}
public func configureCell(profile: ProfileModel) {
self.profileModel = profile
// TODO: Configure tag labels with profile data
// We need to determine what data should populate tagView1 and tagView2
// Possible options from ProfileModel:
// - _user_points (loyalty points)
// - _badge (user badge/tier)
// - _verified (verification status)
// - Custom business logic based on profile data
// For now, keep tags visible with current static styling
tagView1.isHidden = false
tagView2.isHidden = false
// Load profile image if available
if !profile._image_url.isEmpty {
loadProfileImage(from: profile._image_url)
} else {
profileImage.image = UIImage(named: "profile_pic_default", in: Bundle.frameworkResourceBundle, compatibleWith: nil)
}
}
private func configureDefaultState() {
// Default state: no profile data available
self.profileModel = nil
// Keep tags visible even in default state
tagView1.isHidden = false
tagView2.isHidden = false
profileImage.image = UIImage(named: "profile_pic_default", in: Bundle.frameworkResourceBundle, compatibleWith: nil)
}
private func loadProfileImage(from urlString: String) {
// For now, use default image - can be enhanced later with URL loading
profileImage.image = UIImage(named: "profile_pic_default", in: Bundle.frameworkResourceBundle, compatibleWith: nil)
}
}
......@@ -11,6 +11,7 @@ import Foundation
// MARK: - Section Types
enum SectionType {
case myRewardsProfileInfo // MyRewardsProfileInfoTableViewCell
case myRewardsBannerOffers // MyRewardsBannerOffersScrollTableViewCell
case myRewardsHorizontalCouponsets // MyRewardsOffersScrollTableViewCell
case profileHeader // ProfileHeaderTableViewCell (no items)
......@@ -21,6 +22,7 @@ enum SectionType {
}
enum ItemType {
case profile // ProfileModel
case campaigns // [CampaignItemModel]
case couponSets // [CouponSetItemModel]
case coupons // [CouponItemModel]
......
......@@ -40,6 +40,10 @@ import UIKit
// Campaign data for banners
var bannerCampaigns: [CampaignItemModel] = []
// Profile data
var profileModel: ProfileModel?
var profileSection: SectionModel?
public override func viewDidLoad() {
super.viewDidLoad()
......@@ -56,13 +60,18 @@ import UIKit
tableView.estimatedRowHeight = 200
tableView.rowHeight = UITableView.automaticDimension
// Start with empty sections - will be populated dynamically by API calls
loadCampaigns()
// Always create profile section first (with default state)
createDefaultProfileSection()
// Load data
loadProfile() // Try to populate profile with real data
loadCampaigns() // Load campaigns
}
// NEW: Safe XIB registration method
private func registerTableViewCells() {
let cellConfigs = [
("MyRewardsProfileInfoTableViewCell", "MyRewardsProfileInfoTableViewCell"),
("MyRewardsBannerOffersScrollTableViewCell", "MyRewardsBannerOffersScrollTableViewCell"),
("MyRewardsOffersScrollTableViewCell", "MyRewardsOffersScrollTableViewCell")
]
......@@ -127,6 +136,66 @@ import UIKit
}
}
// MARK: - Profile Loading
public func loadProfile() {
// Always attempt to load profile, regardless of authentication status
// If not authenticated, the API call will fail gracefully and we keep the default state
WarplySDK.shared.getProfile { [weak self] profile in
guard let self = self else { return }
if let profile = profile {
// Success: Update with real profile data
self.profileModel = profile
self.updateProfileSectionWithData(profile)
print("✅ [MyRewardsViewController] Profile loaded successfully")
} else {
// No profile data: Keep default state
print("ℹ️ [MyRewardsViewController] No profile data received - keeping default state")
}
} failureCallback: { [weak self] errorCode in
print("⚠️ [MyRewardsViewController] Profile loading failed with error: \(errorCode) - keeping default state")
// Don't remove section - just keep the default state
// The profile section remains visible with default profile pic
}
}
private func createDefaultProfileSection() {
// Create profile section with default/empty state
let defaultProfileSection = SectionModel(
sectionType: .myRewardsProfileInfo,
title: nil,
count: 1,
metadata: ["profile": nil] // nil profile = default state
)
// Always insert at index 0 (top of the list)
sections.insert(defaultProfileSection, at: 0)
profileSection = defaultProfileSection
}
private func updateProfileSectionWithData(_ profile: ProfileModel) {
// Create updated profile section with real data
let updatedProfileSection = SectionModel(
sectionType: .myRewardsProfileInfo,
title: nil,
count: 1,
metadata: ["profile": profile]
)
// Find and update the profile section
if let profileIndex = sections.firstIndex(where: { $0.sectionType == .myRewardsProfileInfo }) {
sections[profileIndex] = updatedProfileSection
profileSection = updatedProfileSection
// Reload only the profile section
DispatchQueue.main.async {
self.tableView.reloadSections(IndexSet(integer: profileIndex), with: .none)
}
}
}
private func openCampaignViewController(with index: Int) {
// Validate index bounds
guard index < bannerCampaigns.count else {
......@@ -203,6 +272,12 @@ extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{
let sectionModel = sections[indexPath.section]
switch sectionModel.sectionType {
case .myRewardsProfileInfo:
let cell = tableView.dequeueReusableCell(withIdentifier: "MyRewardsProfileInfoTableViewCell", for: indexPath) as! MyRewardsProfileInfoTableViewCell
cell.delegate = self
cell.configureCell(data: sectionModel)
return cell
case .myRewardsBannerOffers:
let cell = tableView.dequeueReusableCell(withIdentifier: "MyRewardsBannerOffersScrollTableViewCell", for: indexPath) as! MyRewardsBannerOffersScrollTableViewCell
cell.delegate = self
......@@ -232,16 +307,24 @@ extension MyRewardsViewController: UITableViewDelegate, UITableViewDataSource{
}
// Add delegate conformance
extension MyRewardsViewController: MyRewardsProfileInfoTableViewCellDelegate {
func didTapProfileButton() {
// Navigate to ProfileViewController
openProfileViewController()
}
}
// Add delegate conformance
extension MyRewardsViewController: MyRewardsBannerOffersScrollTableViewCellDelegate {
func didSelectBannerOffer(_ index: Int) {
// Navigate to CampaignViewController
openCampaignViewController(with: index)
}
func didTapProfileButton() {
// Navigate to ProfileViewController
openProfileViewController()
}
// func didTapProfileButton() {
// // Navigate to ProfileViewController
// openProfileViewController()
// }
}
// Add delegate conformance
......