refactor: Migrate subscription products cell to code
This commit is contained in:
+61
-38
@@ -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
|
||||
}
|
||||
|
||||
+40
-13
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user