refactor: Migrate subscription products cell to code

This commit is contained in:
Andrea Busi
2024-06-14 16:15:26 +02:00
parent d46a2e1559
commit 382dcfa794
4 changed files with 110 additions and 174 deletions
@@ -9,56 +9,79 @@
import UIKit
import StoreKit
class SubscriptionProductTableViewCell: UITableViewCell {
class SubscriptionProductTableViewCell: EQNBaseContainerTableViewCell {
override var isHeaderVisible: Bool { false }
// MARK: - UI
private lazy var productImageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
var product: SKProduct? {
didSet {
updateUI()
}
}
var availability: EQNPurchaseAvailability? {
didSet {
updateUI()
}
}
private lazy var productTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.shared.cardTextColor
label.font = .preferredFont(forTextStyle: .headline)
label.numberOfLines = 0
return label
}()
@IBOutlet private weak var productImageView: UIImageView!
@IBOutlet private weak var productTitleLabel: UILabel!
@IBOutlet private weak var productDescriptionLabel: UILabel?
@IBOutlet private weak var productInfoLabel: UILabel!
private lazy var productInfoLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = AppTheme.Colors.red
label.font = .preferredFont(forTextStyle: .body)
label.numberOfLines = 0
return label
}()
// MARK: - Internal
// MARK: - View Lifecycle
// force an inset to have the same style of EQNBaseTableViewCell
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
let inset: CGFloat = 8
var frame = newFrame
frame.origin.x += inset
frame.size.width -= 2 * inset
super.frame = frame
}
}
// MARK: - Private
private func updateUI() {
guard let product = product else { return }
override func setupUI() {
super.setupUI()
containerView.addSubview(productImageView)
containerView.addSubview(productTitleLabel)
containerView.addSubview(productInfoLabel)
productImageView.widthAnchor.constraint(equalToConstant: 80.0).isActive = true
productImageView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
productTitleLabel.topAnchor.constraint(equalTo: topView.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
productTitleLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: .cardPadding.negative).isActive = true
productImageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: .cardPadding).isActive = true
productImageView.trailingAnchor.constraint(equalTo: productTitleLabel.leadingAnchor, constant: .cardPadding.negative).isActive = true
productImageView.centerYAnchor.constraint(equalTo: productTitleLabel.centerYAnchor).isActive = true
productInfoLabel.topAnchor.constraint(equalTo: productTitleLabel.bottomAnchor, constant: .cardVerticalSpacing).isActive = true
productInfoLabel.leadingAnchor.constraint(equalTo: productImageView.leadingAnchor).isActive = true
productInfoLabel.trailingAnchor.constraint(equalTo: productTitleLabel.trailingAnchor).isActive = true
productInfoLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: .cardVerticalSpacing.negative).isActive = true
}
// MARK: - Public
func update(
product: SKProduct,
availability: EQNPurchaseAvailability?
) {
productImageView.image = VersioneProProducts.image(for: product.productIdentifier)
productTitleLabel.text = product.localizedTitle
productDescriptionLabel?.text = product.localizedDescription
let infoKey = VersioneProProducts.is100kSubscription(for: product.productIdentifier) ? "inapp_available_100k" : "inapp_available_10k"
let counter = availability(for: product.productIdentifier)
let counter = availabilityCounter(for: product.productIdentifier, availability: availability)
productInfoLabel.text = String(format: NSLocalizedString(infoKey, comment: ""), counter)
}
private func availability(for productIdentifier: String) -> Int {
private func availabilityCounter(
for productIdentifier: String,
availability: EQNPurchaseAvailability?
) -> Int {
if VersioneProProducts.is100kSubscription(for: productIdentifier) {
return availability?.top100kAvailable ?? 0
}
@@ -8,26 +8,53 @@
import UIKit
class SubscriptionsHeaderTableViewCell: UITableViewCell {
var isLoading = false {
didSet {
updateUI()
}
class SubscriptionsHeaderTableViewCell: UITableViewHeaderFooterView {
// MARK: - UI
private lazy var headerTitleLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.preferredFont(forTextStyle: .title2)
label.textColor = AppTheme.Colors.darkGray
return label
}()
private lazy var loadingActivityIndicator: UIActivityIndicatorView = {
let spinner = UIActivityIndicatorView(style: .medium)
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.hidesWhenStopped = true
return spinner
}()
// MARK: - Init
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
setupUI()
}
var title: String? = nil {
didSet {
updateUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
@IBOutlet private weak var headerTitleLabel: UILabel!
@IBOutlet private weak var loadingActivityIndicator: UIActivityIndicatorView!
// MARK: - Private
private func updateUI() {
private func setupUI() {
contentView.addSubview(headerTitleLabel)
contentView.addSubview(loadingActivityIndicator)
headerTitleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
headerTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: .cardPadding).isActive = true
loadingActivityIndicator.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: .cardPadding.negative).isActive = true
loadingActivityIndicator.centerYAnchor.constraint(equalTo: headerTitleLabel.centerYAnchor).isActive = true
}
// MARK: - Public
func update(isLoading: Bool, title: String?) {
headerTitleLabel.text = title
if isLoading && title != nil {
@@ -105,6 +105,8 @@ class SubscriptionsViewController: UITableViewController {
tableView.estimatedRowHeight = 600.0
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
tableView.registerHeaderFooterView(for: SubscriptionsHeaderTableViewCell.self)
}
private func updateUI() {
@@ -229,12 +231,9 @@ class SubscriptionsViewController: UITableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let tableSection = sections[section]
if let cell = tableView.dequeueReusableCell(withIdentifier: "SectionHeaderCell") as? SubscriptionsHeaderTableViewCell {
cell.title = tableSection.sectionTitle
cell.isLoading = isLoading
return cell
}
return nil
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
view.update(isLoading: isLoading, title: tableSection.sectionTitle)
return view
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
@@ -257,33 +256,6 @@ class SubscriptionsViewController: UITableViewController {
}
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let tableSection = sections[indexPath.section]
if tableSection == .active || tableSection == .description {
return
}
// add round borders to first and last row in products cells
let cornerRadius = AppTheme.shared.cardCornerRadius
var corners: UIRectCorner = []
if indexPath.row == 0 {
corners.update(with: .topLeft)
corners.update(with: .topRight)
}
if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
corners.update(with: .bottomLeft)
corners.update(with: .bottomRight)
}
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: cell.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
cell.layer.mask = maskLayer
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableSection = sections[indexPath.section]
if tableSection == .active {
@@ -299,9 +271,8 @@ class SubscriptionsViewController: UITableViewController {
}
let products = availableProducts(for: tableSection)
let cell = tableView.dequeueReusableCell(withIdentifier: "SubscriptionCell", for: indexPath) as! SubscriptionProductTableViewCell
cell.product = products[indexPath.row]
cell.availability = availability
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionProductTableViewCell.self, for: indexPath)
cell.update(product: products[indexPath.row], availability: availability)
return cell
}
@@ -65,6 +65,7 @@
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZqJ-8B-jWz">
<rect key="frame" x="0.0" y="0.0" width="377.5" height="30"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<state key="normal" title="Privacy disclaimer"/>
<connections>
<action selector="openExternalLinkTapped:" destination="tdo-m9-oeL" eventType="touchUpInside" id="tAD-Yt-kTw"/>
@@ -72,6 +73,7 @@
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lur-Tu-wib">
<rect key="frame" x="0.0" y="30" width="377.5" height="30"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<state key="normal" title="Terms and conditions"/>
<connections>
<action selector="openExternalLinkTapped:" destination="tdo-m9-oeL" eventType="touchUpInside" id="p4g-jL-xPV"/>
@@ -922,92 +924,6 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="SectionHeaderCell" id="urG-ON-XcB" customClass="SubscriptionsHeaderTableViewCell" customModule="Earthquake_Network" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="urG-ON-XcB" id="SrE-iI-Nig">
<rect key="frame" x="0.0" y="0.0" width="414" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillProportionally" translatesAutoresizingMaskIntoConstraints="NO" id="e1H-JK-cpd">
<rect key="frame" x="8" y="12" width="398" height="20"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" tag="1" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="251" horizontalCompressionResistancePriority="749" text="Monthly subscriptions" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0xS-ly-xZg">
<rect key="frame" x="0.0" y="0.0" width="370" height="20"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="20"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="751" verticalHuggingPriority="750" horizontalCompressionResistancePriority="751" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="4kc-T4-2fq">
<rect key="frame" x="370" y="0.0" width="28" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="28" id="3si-0h-9NB"/>
<constraint firstAttribute="height" constant="20" id="vXr-mZ-d4H"/>
</constraints>
</activityIndicatorView>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstItem="e1H-JK-cpd" firstAttribute="leading" secondItem="SrE-iI-Nig" secondAttribute="leading" constant="8" id="59O-Jd-Fai"/>
<constraint firstAttribute="trailing" secondItem="e1H-JK-cpd" secondAttribute="trailing" constant="8" id="HUb-bF-7ch"/>
<constraint firstItem="e1H-JK-cpd" firstAttribute="centerY" secondItem="SrE-iI-Nig" secondAttribute="centerY" id="XIm-jA-fkD"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" systemColor="systemGroupedBackgroundColor"/>
<connections>
<outlet property="headerTitleLabel" destination="0xS-ly-xZg" id="9HL-cx-MaB"/>
<outlet property="loadingActivityIndicator" destination="4kc-T4-2fq" id="VqP-5w-IuD"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="SubscriptionCell" rowHeight="140" id="ltf-er-wHX" customClass="SubscriptionProductTableViewCell" customModule="Earthquake_Network" customModuleProvider="target">
<rect key="frame" x="0.0" y="94" width="414" height="140"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ltf-er-wHX" id="ohD-ot-UaH">
<rect key="frame" x="0.0" y="0.0" width="383.5" height="140"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="top_10k" translatesAutoresizingMaskIntoConstraints="NO" id="pBU-VB-dzq">
<rect key="frame" x="20" y="11" width="80" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="80" id="dL4-xc-IzO"/>
<constraint firstAttribute="height" constant="40" id="qTD-Bc-irq"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="10" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1 month priority 100k" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WFT-d3-gHf">
<rect key="frame" x="108" y="21" width="259.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Top 100K: %lu subscriptions still available to be alerted in less than 1 second since the detection of the quake" textAlignment="justified" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eWa-1i-s0D">
<rect key="frame" x="20" y="49.5" width="355.5" height="79.5"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" name="Red"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="eWa-1i-s0D" firstAttribute="leading" secondItem="ohD-ot-UaH" secondAttribute="leadingMargin" id="0Kx-ZY-SnM"/>
<constraint firstItem="eWa-1i-s0D" firstAttribute="top" secondItem="WFT-d3-gHf" secondAttribute="bottom" constant="8" id="D9U-7z-aMR"/>
<constraint firstAttribute="bottomMargin" secondItem="eWa-1i-s0D" secondAttribute="bottom" id="Fxo-Yu-DMD"/>
<constraint firstAttribute="trailingMargin" secondItem="eWa-1i-s0D" secondAttribute="trailing" id="Zas-EU-Cg0"/>
<constraint firstItem="WFT-d3-gHf" firstAttribute="leading" secondItem="pBU-VB-dzq" secondAttribute="trailing" constant="8" id="hCa-CY-m2c"/>
<constraint firstItem="pBU-VB-dzq" firstAttribute="top" secondItem="ohD-ot-UaH" secondAttribute="topMargin" id="l2X-YS-hwt"/>
<constraint firstItem="pBU-VB-dzq" firstAttribute="leading" secondItem="ohD-ot-UaH" secondAttribute="leadingMargin" id="pZM-jm-byI"/>
<constraint firstAttribute="trailingMargin" secondItem="WFT-d3-gHf" secondAttribute="trailing" constant="8" id="sKw-na-Hh7"/>
<constraint firstItem="WFT-d3-gHf" firstAttribute="centerY" secondItem="pBU-VB-dzq" secondAttribute="centerY" id="zBg-o2-QT9"/>
</constraints>
</tableViewCellContentView>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<connections>
<outlet property="productImageView" destination="pBU-VB-dzq" id="WQJ-hJ-KLJ"/>
<outlet property="productInfoLabel" destination="eWa-1i-s0D" id="rOW-FQ-3H6"/>
<outlet property="productTitleLabel" destination="WFT-d3-gHf" id="YCx-fv-bJD"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="cXN-cY-DjM" id="bo0-tj-smp"/>
<outlet property="delegate" destination="cXN-cY-DjM" id="y7A-F9-Alv"/>
@@ -1551,7 +1467,6 @@ Sisma rilevato da 10 smartphone</string>
<image name="tabbar-icon-settings" width="25" height="25"/>
<image name="telegram_icon" width="100" height="100"/>
<image name="top_100k" width="97.5" height="30"/>
<image name="top_10k" width="86" height="26.5"/>
<image name="twitter_icon" width="100" height="100"/>
<namedColor name="Gray (dark)">
<color red="0.10999999940395355" green="0.13300000131130219" blue="0.14900000393390656" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>