Fableon/Fableon/Object/Libs/Alert/FAPayRetainAlert.swift
2026-01-13 17:39:24 +08:00

505 lines
17 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// FAPayRetainAlert.swift
// Fableon
//
// Created by on 2026/1/13.
//
import UIKit
import SnapKit
import YYText
class FAPayRetainAlert: FABaseAlert {
var model: FAPayDateModel? {
didSet {
self.collectionView.reloadData()
self.titleLabel.text = model?.retrieve_lang?.title
self.countdownView.model = model
bonusTagView.update(text: model?.retrieve_lang?.subtitle ?? "")
}
}
private var selectedIndex = FAPayRetainAlertData.defaultSelectedIndex
private lazy var bgView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: FAPayRetainAlertData.Assets.background))
imageView.contentMode = .scaleToFill
imageView.isUserInteractionEnabled = true
return imageView
}()
private lazy var titleLabel: FALabel = {
let label = FALabel()
label.borderLineWidth = 6
label.borderColor = ._9_ED_0_FF
label.text = FAPayRetainAlertData.titleText
label.font = .font(ofSize: 44, weight: .init(900)).withBoldItalic()
label.textStartPoint = .init(x: 0, y: 0.5)
label.textEndPoint = .init(x: 1, y: 0.5)
label.textColors = [
UIColor.fa_hex(0x0178FF).cgColor,
UIColor.fa_hex(0x0D8AFF).cgColor
]
return label
}()
private lazy var countdownView: PayRetainCountdownView = {
let view = PayRetainCountdownView()
view.update(prefix: FAPayRetainAlertData.countdownPrefix,
countdown: FAPayRetainAlertData.countdown,
suffix: FAPayRetainAlertData.countdownSuffix)
return view
}()
private lazy var bonusTagView: PayRetainBonusTagView = {
let view = PayRetainBonusTagView()
return view
}()
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.minimumLineSpacing = FAPayRetainAlertData.Layout.packItemSpacing
layout.minimumInteritemSpacing = 0
layout.itemSize = FAPayRetainAlertData.Layout.packItemSize
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.backgroundColor = .clear
view.isScrollEnabled = false
view.showsVerticalScrollIndicator = false
view.dataSource = self
view.delegate = self
view.register(FAPayRetainPackCell.self, forCellWithReuseIdentifier: FAPayRetainPackCell.reuseIdentifier)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
fa_setupLayout()
collectionView.reloadData()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension FAPayRetainAlert {
private func fa_setupLayout() {
contentWidth = FAPayRetainAlertData.Layout.contentWidth
contentView.backgroundColor = .clear
contentView.layer.cornerRadius = 0
contentView.layer.masksToBounds = true
contentView.addSubview(bgView)
contentView.addSubview(titleLabel)
contentView.addSubview(countdownView)
contentView.addSubview(bonusTagView)
contentView.addSubview(collectionView)
bgView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(143)
make.centerX.equalToSuperview()
make.right.lessThanOrEqualToSuperview().offset(0)
}
countdownView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(FAPayRetainAlertData.Layout.countdownLeft)
make.top.equalTo(titleLabel.snp.bottom).offset(2)
}
bonusTagView.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(35)
make.centerX.equalToSuperview()
make.right.lessThanOrEqualToSuperview().offset(-10)
make.height.equalTo(FAPayRetainAlertData.Layout.bonusTagHeight)
}
collectionView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.right.equalToSuperview().offset(-10)
make.top.equalTo(bonusTagView.snp.bottom).offset(15)
make.bottom.equalToSuperview().offset(-10)
}
}
}
// MARK: - UICollectionViewDataSource & Delegate
extension FAPayRetainAlert: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.model?.list_coins?.count ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FAPayRetainPackCell.reuseIdentifier, for: indexPath) as! FAPayRetainPackCell
let item = self.model?.list_coins?[indexPath.row]
cell.configure(with: item, isSelected: indexPath.item == selectedIndex)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.item
collectionView.reloadData()
}
}
// MARK: - Subviews
extension FAPayRetainAlert {
final class PayRetainCountdownView: UIView {
var model: FAPayDateModel? {
didSet {
prefixLabel.text = model?.retrieve_lang?.remaining_time
suffixLabel.text = model?.retrieve_lang?.miss_out
}
}
private static let defaultCountdownSeconds = 4 * 60 * 60
private var countdownTimer: Timer?
private var remainingSeconds: Int = 0
private let prefixLabel = UILabel()
private let suffixLabel = UILabel()
private let timeStackView = UIStackView()
private let hourView = TimeBoxView()
private let minuteView = TimeBoxView()
private let secondView = TimeBoxView()
private let colonLabel1 = UILabel()
private let colonLabel2 = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
deinit {
stopCountdown()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//
func update(prefix: String, countdown: FAPayRetainAlertData.Countdown, suffix: String) {
prefixLabel.text = prefix
suffixLabel.text = suffix
hourView.setText(countdown.hours)
minuteView.setText(countdown.minutes)
secondView.setText(countdown.seconds)
}
override func didMoveToWindow() {
super.didMoveToWindow()
if window != nil {
startCountdown()
} else {
stopCountdown()
}
}
private func setupView() {
let mainStack = UIStackView()
mainStack.axis = .horizontal
mainStack.alignment = .center
mainStack.spacing = 5
prefixLabel.font = .font(ofSize: 12, weight: .medium)
prefixLabel.textColor = UIColor.fa_hex(0x0F0F0F)
suffixLabel.font = .font(ofSize: 12, weight: .medium)
suffixLabel.textColor = UIColor.fa_hex(0x0F0F0F)
timeStackView.axis = .horizontal
timeStackView.alignment = .center
timeStackView.spacing = 3
colonLabel1.font = .font(ofSize: 12, weight: .medium)
colonLabel1.textColor = UIColor.fa_hex(0x0F0F0F)
colonLabel1.text = ":"
colonLabel2.font = .font(ofSize: 12, weight: .medium)
colonLabel2.textColor = UIColor.fa_hex(0x0F0F0F)
colonLabel2.text = ":"
timeStackView.addArrangedSubview(hourView)
timeStackView.addArrangedSubview(colonLabel1)
timeStackView.addArrangedSubview(minuteView)
timeStackView.addArrangedSubview(colonLabel2)
timeStackView.addArrangedSubview(secondView)
addSubview(mainStack)
mainStack.addArrangedSubview(prefixLabel)
mainStack.addArrangedSubview(timeStackView)
mainStack.addArrangedSubview(suffixLabel)
mainStack.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
// 4
private func startCountdown() {
stopCountdown()
remainingSeconds = Self.defaultCountdownSeconds
updateTimeLabels()
countdownTimer = Timer.scheduledTimer(timeInterval: 1,
target: YYTextWeakProxy(target: self),
selector: #selector(handleCountdownTimer),
userInfo: nil,
repeats: true)
}
private func stopCountdown() {
countdownTimer?.invalidate()
countdownTimer = nil
}
@objc private func handleCountdownTimer() {
guard remainingSeconds > 0 else {
updateTimeLabels()
stopCountdown()
return
}
remainingSeconds -= 1
updateTimeLabels()
}
private func updateTimeLabels() {
let hours = remainingSeconds / 3600
let minutes = (remainingSeconds % 3600) / 60
let seconds = remainingSeconds % 60
hourView.setText(String(format: "%02d", hours))
minuteView.setText(String(format: "%02d", minutes))
secondView.setText(String(format: "%02d", seconds))
}
private final class TimeBoxView: UIView {
private let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setText(_ text: String) {
label.text = text
}
private func setupView() {
backgroundColor = UIColor.fa_hex(0x0F0F0F)
layer.cornerRadius = 4
layer.masksToBounds = true
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .FFFFFF
label.textAlignment = .center
addSubview(label)
label.snp.makeConstraints { make in
make.center.equalToSuperview()
}
snp.makeConstraints { make in
make.width.height.equalTo(18)
}
}
}
}
final class PayRetainBonusTagView: UIView {
private let leftStar = UIImageView(image: UIImage(named: FAPayRetainAlertData.Assets.starIcon))
private let rightStar = UIImageView(image: UIImage(named: FAPayRetainAlertData.Assets.starIcon))
private let titleLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//
func update(text: String) {
titleLabel.text = text
}
private func setupView() {
backgroundColor = UIColor.fa_hex(0x20A1FF)
layer.cornerRadius = 10
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
titleLabel.font = .font(ofSize: 20, weight: .bold).withBoldItalic()
titleLabel.textColor = .FFFFFF
addSubview(leftStar)
addSubview(rightStar)
addSubview(titleLabel)
leftStar.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(8)
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(leftStar.snp.right).offset(5)
}
rightStar.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalTo(titleLabel.snp.right).offset(5)
make.right.equalToSuperview().offset(-8)
}
}
}
}
// MARK: - Pack Cell
final class FAPayRetainPackCell: UICollectionViewCell {
static let reuseIdentifier = "FAPayRetainPackCell"
private lazy var bgView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: FAPayRetainAlertData.Assets.cellBackground))
return imageView
}()
private let selectedBorderView = UIView()
private let coinIconView = UIImageView(image: UIImage(named: FAPayRetainAlertData.Assets.coinIcon))
private let coinLabel = UILabel()
private let bonusLabel = UILabel()
private lazy var priceView: FAGradientView = {
let view = FAGradientView()
view.fa_colors = [
UIColor.fa_hex(0x3071FF).cgColor,
UIColor.fa_hex(0x6DB6FF).cgColor
]
view.fa_locations = [0, 1]
view.fa_startPoint = .init(x: 0, y: 0.5)
view.fa_endPoint = .init(x: 1, y: 0.5)
view.layer.cornerRadius = FAPayRetainAlertData.Layout.priceSize.height / 2
view.layer.masksToBounds = true
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.fa_hex(0xFFFFFF).cgColor
return view
}()
private let priceLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//
func configure(with item: FAPayItem?, isSelected: Bool) {
selectedBorderView.isHidden = !isSelected
guard let item = item else { return }
coinLabel.text = "\(item.coins ?? 0)"
bonusLabel.text = "+\((item.ext_info?.max_total_coins_pop ?? 0) - (item.ext_info?.max_total_coins ?? 0)) " + "fableon_free_coins".localized
priceLabel.text = "\(item.currency ?? "")\(item.price ?? "")"
}
private func setupView() {
contentView.backgroundColor = .clear
bgView.layer.cornerRadius = 15
bgView.layer.masksToBounds = true
selectedBorderView.layer.cornerRadius = 15
selectedBorderView.layer.borderWidth = 2
selectedBorderView.layer.borderColor = UIColor.fa_hex(0x1F8FFF).cgColor
selectedBorderView.isHidden = true
coinIconView.contentMode = .scaleAspectFit
coinLabel.font = .font(ofSize: 22, weight: .bold)
coinLabel.textColor = UIColor.fa_hex(0x276CFF)
bonusLabel.font = .font(ofSize: 12, weight: .medium)
bonusLabel.textColor = .FFFFFF
priceLabel.font = .font(ofSize: 22, weight: .bold)
priceLabel.textColor = .FFFFFF
let coinStack = UIStackView(arrangedSubviews: [coinIconView, coinLabel])
coinStack.axis = .horizontal
coinStack.alignment = .center
coinStack.spacing = 2
contentView.addSubview(bgView)
contentView.addSubview(selectedBorderView)
bgView.addSubview(coinStack)
bgView.addSubview(bonusLabel)
bgView.addSubview(priceView)
priceView.addSubview(priceLabel)
bgView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
selectedBorderView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
coinStack.snp.makeConstraints { make in
make.left.equalToSuperview().offset(FAPayRetainAlertData.Layout.coinStackLeft)
make.top.equalToSuperview().offset(FAPayRetainAlertData.Layout.coinStackTop)
}
coinIconView.snp.makeConstraints { make in
make.width.height.equalTo(22)
}
bonusLabel.snp.makeConstraints { make in
make.left.equalTo(coinStack)
make.top.equalTo(coinStack.snp.bottom).offset(FAPayRetainAlertData.Layout.bonusTopSpacing)
}
priceView.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-14)
make.top.equalToSuperview().offset(FAPayRetainAlertData.Layout.priceTop)
make.size.equalTo(FAPayRetainAlertData.Layout.priceSize)
}
priceLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
}
private extension UIColor {
// 便稿
static func fa_hex(_ hex: UInt32, alpha: CGFloat = 1) -> UIColor {
let red = CGFloat((hex >> 16) & 0xFF) / 255.0
let green = CGFloat((hex >> 8) & 0xFF) / 255.0
let blue = CGFloat(hex & 0xFF) / 255.0
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
}
}