jsdw_ios/QuickLocation/Section/VipRecharge/VipRechargeView.swift

685 lines
22 KiB
Swift

//
// VipRechargeView.swift
// QuickLocation
//
// Created by on 2026/6/3.
//
import UIKit
import RxSwift
import RxCocoa
class VipRechargeView: UIView {
var disposeBag = DisposeBag()
private func setupRx() {
backBtn.rx.tap.subscribe(onNext: { _ in
AppRouter.shared.popOrDismiss()
}).disposed(by: disposeBag)
agreementLab.rx.tapGesture.subscribe { _ in
// TODO:
}.disposed(by: disposeBag)
}
private func setupUI() {
addSubview(scrollView)
scrollView.addSubview(scrollContentView)
scrollContentView.addSubview(headerBgImgView)
scrollContentView.addSubview(cornerView)
scrollContentView.addSubview(expenseCollectionView)
scrollContentView.addSubview(vipRightsView)
scrollContentView.addSubview(agreementLab)
scrollContentView.addSubview(tipsLab)
vipRightsView.addSubview(vipRightsTitleView)
addSubview(bottomView)
bottomView.addSubview(payTypeStackView)
bottomView.addSubview(payBtnView)
bottomView.addSubview(payPriceView)
addSubview(navBarView)
navBarView.addSubview(navTitleLabel)
addSubview(backBtn)
navBarView.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
navTitleLabel.layoutChain
.top(kStatusBarHeight + 12)
.centerY(backBtn)
.centerX()
backBtn.layoutChain
.top(kStatusBarHeight + 12)
.left(15)
.width(24)
.height(24)
bottomView.layoutChain
.edgesHorzontal()
.heightToWidth(144/375)
.bottom()
payTypeStackView.layoutChain
.top(25)
.edgesHorzontal(16)
.centerX()
payBtnView.layoutChain
.edgesHorzontal(16)
.heightToWidth(50/343)
.centerY()
payPriceView.layoutChain
.left(32)
.centerY(payBtnView, offset: -7)
.height(30)
scrollView.layoutChain
.edges(excludingEdge: .bottom)
.bottomToTopOfView(bottomView)
scrollContentView.layoutChain
.edges()
.widthToView(scrollView)
headerBgImgView.layoutChain
.top(-kStatusBarHeight)
.edgesHorzontal()
// .edges(excludingEdge: .bottom)
.heightToWidth(267/375)
cornerView.layoutChain
.topToBottomOfView(headerBgImgView, offset: -20)
.edges(excludingEdge: .top)
let expenseCollectionViewHeight = (kScreenWidth - 32 - 32) / 3 * (144/110) + 10
expenseCollectionView.layoutChain
.topToView(cornerView, offset: -43)
.edgesHorzontal()
.height(expenseCollectionViewHeight * 1.1)
vipRightsView.layoutChain
.topToBottomOfView(expenseCollectionView, offset: 18)
.edgesHorzontal(16)
.height(302)
vipRightsTitleView.layoutChain
.top(14)
.centerX()
agreementLab.layoutChain
.topToBottomOfView(vipRightsView, offset: 6)
.leftToView(vipRightsView)
.rightToView(vipRightsView)
tipsLab.layoutChain
.topToBottomOfView(agreementLab, offset: 4)
.leftToView(vipRightsView)
.rightToView(vipRightsView)
.bottom(10)
}
lazy var navBarView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.alpha = 0
return view
}()
lazy var navTitleLabel: UILabel = {
let label = UILabel()
label.text = "升级会员"
label.font = .systemFont(ofSize: 18, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
label.textAlignment = .center
return label
}()
lazy var backBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "Common/back"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
return btn
}()
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = UIColor(hexStr: "#F5FBFB")
view.showsVerticalScrollIndicator = false
view.delegate = self
view.bounces = false
return view
}()
lazy var scrollContentView: UIView = {
let view = UIView()
view.backgroundColor = .clear
return view
}()
lazy var headerBgImgView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "VipRecharge/header_bg")
return view
}()
lazy var cornerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#F5FBFB")
view.clipsToBounds = false
return view
}()
///
lazy var expenseCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let spacing: CGFloat = 16
let cvWidth = kScreenWidth - 32
let itemW = (cvWidth - spacing * 2) / 3
layout.itemSize = CGSize(width: itemW, height: itemW * (144/110) + 10)
layout.sectionInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
layout.minimumLineSpacing = spacing
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.showsHorizontalScrollIndicator = false
cv.register(ExpenseCell.self)
return cv
}()
///
lazy var vipRightsView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 10
return view
}()
lazy var vipRightsTitleView: UIView = {
let view = UIView()
view.backgroundColor = .clear
let titleLab = UILabel()
titleLab.text = "会员权益"
titleLab.font = .systemFont(ofSize: 14, weight: .bold)
titleLab.textColor = UIColor(hexStr: "#3D3D3D")
titleLab.textAlignment = .center
view.addSubview(titleLab)
titleLab.layoutChain
.edgesVertical()
.centerX()
let leftLine1 = UIView()
leftLine1.backgroundColor = UIColor(hexStr: "#B7F34E")
leftLine1.cornerRadius = 1
view.addSubview(leftLine1)
leftLine1.layoutChain
.left()
.width(2)
.height(10)
.centerY()
let leftLine2 = UIView()
leftLine2.backgroundColor = UIColor(hexStr: "#B7F34E")
leftLine2.cornerRadius = 1
view.addSubview(leftLine2)
leftLine2.layoutChain
.leftToRightOfView(leftLine1, offset: 4)
.width(2)
.edgesVertical()
.rightToLeftOfView(titleLab, offset: -10)
let leftLine3 = UIView()
leftLine3.backgroundColor = UIColor(hexStr: "#B7F34E")
leftLine3.cornerRadius = 1
view.addSubview(leftLine3)
leftLine3.layoutChain
.leftToRightOfView(titleLab, offset: 10)
.width(2)
.edgesVertical()
let leftLine4 = UIView()
leftLine4.backgroundColor = UIColor(hexStr: "#B7F34E")
leftLine4.cornerRadius = 1
view.addSubview(leftLine4)
leftLine4.layoutChain
.leftToRightOfView(leftLine3, offset: 4)
.width(2)
.height(10)
.centerY()
return view
}()
lazy var groupCountView: UIView = {
let view = UIView()
view.backgroundColor = .clear
//
let createCountView = UIView()
createCountView.backgroundColor = .clear
let createIcon = UIImageView(image: UIImage(named: "VipRecharge/create_count"))
let createBgImg = UIImageView(image: UIImage(named: "VipRecharge/count_bg"))
let createUnitLab = UILabel()
createUnitLab.text = ""
createUnitLab.font = .systemFont(ofSize: 10, weight: .medium)
createUnitLab.textColor = UIColor(hexStr: "#1A1A1A")
let createTitleLab = UILabel()
createTitleLab.text = "创建圈子"
createTitleLab.font = .systemFont(ofSize: 12, weight: .medium)
createTitleLab.textColor = UIColor(hexStr: "#1A1A1A")
createCountView.addSubview(createIcon)
createCountView.addSubview(createBgImg)
createCountView.addSubview(createUnitLab)
createCountView.addSubview(createTitleLab)
createCountView.addSubview(createCountLab)
createIcon.layoutChain
.top()
.centerX()
.width(34).height(34)
createBgImg.layoutChain
.top(8)
createCountLab.layoutChain
.topToBottomOfView(createIcon)
// 线
let separator1 = UIImageView(image: UIImage(named: "VipRecharge/separator"))
//
let separator2 = UIImageView(image: UIImage(named: "VipRecharge/separator"))
return view
}()
lazy var createCountLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 24, weight: .bold)
label.textColor = UIColor(hexStr: "#FF4F44")
return label
}()
lazy var agreementLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#767676")
label.isUserInteractionEnabled = true
let text = "*开通前请阅读《会员服务协议》"
let attr = NSMutableAttributedString(string: text)
let range = (text as NSString).range(of: "《会员服务协议》")
attr.addAttribute(.foregroundColor, value: UIColor(hexStr: "#2DBBFF"), range: range)
label.attributedText = attr
return label
}()
lazy var tipsLab: UILabel = {
let label = UILabel()
label.text = "*根据相关隐私保护的规定,本产品功能需双方下载并授权同意后再使用,请在自己的设备上使用,不得在未经过对方同意和授权的情况下使用,仅限家庭/亲人/朋友/情侣等熟人间使用。"
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#767676")
label.numberOfLines = 0
return label
}()
lazy var payTypeStackView: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.spacing = 30
// stack.distribution = .fillEqually
stack.alignment = .center
return stack
}()
var selectedPayTypeTag: Int = 0
/// pay_type (: "alipay,weixin")
func setupPayTypes(_ payTypeStr: String) {
payTypeStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
let types = payTypeStr.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
let payTypeMap: [(String, String)] = types.compactMap {
if $0 == "weixin" { return ("wechat", "微信支付") }
if $0 == "alipay" { return ("alipay", "支付宝支付") }
return nil
}
guard !payTypeMap.isEmpty else { return }
selectedPayTypeTag = 0
for (idx, (icon, name)) in payTypeMap.enumerated() {
let isSelected = idx == 0
let view = makePayTypeView(tag: idx, icon: icon, name: name, isSelected: isSelected)
payTypeStackView.addArrangedSubview(view)
}
}
private func makePayTypeView(tag: Int, icon: String, name: String, isSelected: Bool) -> UIView {
let view = UIView()
view.tag = tag
view.isUserInteractionEnabled = true
let checkbox = UIImageView(image: UIImage(named: isSelected ? "VipRecharge/checkbox_on" : "VipRecharge/checkbox"))
checkbox.contentMode = .scaleAspectFit
checkbox.tag = 100
view.addSubview(checkbox)
checkbox.layoutChain.left().centerY().width(18).height(18)
let payIcon = UIImageView(image: UIImage(named: "VipRecharge/\(icon)"))
payIcon.contentMode = .scaleAspectFit
view.addSubview(payIcon)
payIcon.layoutChain.leftToRightOfView(checkbox, offset: 8).centerY().width(22).height(22)
let nameLab = UILabel()
nameLab.text = name
nameLab.font = .systemFont(ofSize: 14, weight: .medium)
nameLab.textColor = UIColor(hexStr: "#1A1A1A")
view.addSubview(nameLab)
nameLab.layoutChain.leftToRightOfView(payIcon, offset: 6).centerY()
let tap = UITapGestureRecognizer(target: self, action: #selector(onPayTypeTap(_:)))
view.addGestureRecognizer(tap)
return view
}
@objc private func onPayTypeTap(_ gesture: UITapGestureRecognizer) {
guard let tag = gesture.view?.tag, tag != selectedPayTypeTag else { return }
selectedPayTypeTag = tag
for view in payTypeStackView.arrangedSubviews {
let isSelected = view.tag == tag
if let checkbox = view.viewWithTag(100) as? UIImageView {
checkbox.image = UIImage(named: isSelected ? "VipRecharge/checkbox_on" : "VipRecharge/checkbox")
}
}
}
lazy var bottomView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: -3)
view.layer.shadowOpacity = 0.06
return view
}()
lazy var payBtnView: UIView = {
let view = UIView()
view.backgroundColor = .clear
let bgImg = UIImageView()
bgImg.image = UIImage(named: "VipRecharge/pay_bg")
view.addSubview(bgImg)
bgImg.layoutChain.edges()
let unlockLab = UILabel()
unlockLab.text = "解锁会员"
unlockLab.font = .systemFont(ofSize: 16, weight: .heavy)
unlockLab.textColor = .white
view.addSubview(unlockLab)
unlockLab.layoutChain.right(21).centerY()
return view
}()
lazy var payPriceView: UIView = {
let view = UIView()
view.backgroundColor = .clear
let titleLab = UILabel()
titleLab.text = "合计"
titleLab.font = .systemFont(ofSize: 14, weight: .bold)
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
view.addSubview(titleLab)
titleLab.layoutChain
.left()
.bottom()
let symbolLab = UILabel()
symbolLab.text = ""
symbolLab.font = .systemFont(ofSize: 12, weight: .heavy)
symbolLab.textColor = UIColor(hexStr: "#FF3B05")
view.addSubview(symbolLab)
symbolLab.layoutChain
.leftToRightOfView(titleLab)
.bottomToView(titleLab)
view.addSubview(priceLab)
priceLab.layoutChain
.leftToRightOfView(symbolLab)
.bottomToView(titleLab, offset: 3)
view.addSubview(discountLab)
discountLab.layoutChain
.leftToRightOfView(priceLab, offset: 5)
.bottomToView(titleLab)
.right()
return view
}()
lazy var priceLab: UILabel = {
let label = UILabel()
label.text = "0"
label.font = .systemFont(ofSize: 24, weight: .heavy)
label.textColor = UIColor(hexStr: "#FF3B05")
return label
}()
///
func animatePrice(to newValue: String) {
guard let target = Double(newValue) else {
priceLab.text = newValue
return
}
let current = Double(priceLab.text ?? "0") ?? 0
let diff = target - current
let duration = 0.6
let steps = 30
var step = 0
Timer.scheduledTimer(withTimeInterval: duration / Double(steps), repeats: true) { [weak self] t in
step += 1
guard let self = self else { t.invalidate(); return }
if step >= steps {
t.invalidate()
self.priceLab.text = newValue
} else {
let progress = Double(step) / Double(steps)
// ease out + overshoot on increase
let eased = diff > 0
? 1 - pow(1 - progress, 3)
: pow(progress, 0.5)
let value = current + diff * eased
self.priceLab.text = String(format: "%.0f", value)
}
}
}
lazy var discountLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .bold)
label.textColor = UIColor(hexStr: "#1A1A1A")
return label
}()
override func layoutSubviews() {
super.layoutSubviews()
cornerView.setNeedsLayout()
cornerView.layoutIfNeeded()
cornerView.setCornerRadius(corners: [.topLeft, .topRight], withCornerRadii: CGSize(width: 10, height: 10))
}
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .white
setupUI()
setupRx()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - ExpenseCell
final class ExpenseCell: UICollectionViewCell {
private let tagBadgeView: UIView = {
let iv = UIView()
// iv.image = UIImage(named: "VipRecharge/expense_tips")
// iv.contentMode = .scaleAspectFill
iv.backgroundColor = UIColor(hexStr: "#FF6643")
iv.isHidden = true
return iv
}()
private let tagLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .bold)
label.textColor = .white
label.textAlignment = .center
return label
}()
private let bgImgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "VipRecharge/expense")
iv.contentMode = .scaleAspectFill
return iv
}()
private let titleLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .bold)
label.textColor = UIColor(hexStr: "#1A1A1A")
label.textAlignment = .center
return label
}()
lazy var priceLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .bold)
label.textColor = UIColor(hexStr: "#1A1A1A")
label.textAlignment = .center
return label
}()
lazy var originPriceLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 12, weight: .bold)
label.textColor = UIColor(hexStr: "#767676")
label.textAlignment = .center
return label
}()
lazy var tipsLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 10, weight: .bold)
label.textColor = UIColor(hexStr: "#1A1A1A")
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(bgImgView)
contentView.addSubview(tagBadgeView)
tagBadgeView.addSubview(tagLabel)
contentView.addSubview(titleLab)
contentView.addSubview(priceLab)
contentView.addSubview(originPriceLab)
contentView.addSubview(tipsLab)
tagBadgeView.layoutChain
.top().left()
tagLabel.layoutChain
.edgesHorzontal(10)
.edgesVertical(3)
bgImgView.layoutChain
.top(10)
.left().right().bottom()
titleLab.layoutChain
.centerX()
.top(30)
.edgesHorzontal(2)
priceLab.layoutChain
.centerY()
.edgesHorzontal(2)
originPriceLab.layoutChain
.topToBottomOfView(priceLab, offset: 5)
.edgesHorzontal(2)
tipsLab.layoutChain
.edgesHorzontal(2)
.bottom(7)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(model: VipExpenseModel, isSelected: Bool) {
titleLab.text = model.goods_name
tagBadgeView.isHidden = model.tips.isEmpty
tagLabel.text = model.tips
tipsLab.text = model.tips2
let priceAttr = NSMutableAttributedString(string: "", attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .medium)])
priceAttr.append(NSAttributedString(string: model.price, attributes: [.font: UIFont.systemFont(ofSize: 30, weight: .medium)]))
priceAttr.append(NSAttributedString(string: model.unit, attributes: [.font: UIFont.systemFont(ofSize: 12, weight: .medium)]))
priceLab.attributedText = priceAttr
originPriceLab.text = "" + model.origin_price
originPriceLab.setupStrikethroughStyle()
setSelected(isSelected, animated: false)
}
func setSelected(_ selected: Bool, animated: Bool) {
let scale: CGFloat = selected ? 1.1 : 1.0
let imageName = selected ? "VipRecharge/expense_on" : "VipRecharge/expense"
bgImgView.image = UIImage(named: imageName)
titleLab.textColor = selected ? UIColor(hexStr: "#16B3FF") : UIColor(hexStr: "#1A1A1A")
priceLab.textColor = selected ? UIColor(hexStr: "#FF4F44") : UIColor(hexStr: "#1A1A1A")
let animations = {
self.transform = CGAffineTransform(scaleX: scale, y: scale)
}
if animated {
UIView.animate(withDuration: 0.2, delay: 0, options: .curveEaseOut, animations: animations)
} else {
animations()
}
}
override func layoutSubviews() {
super.layoutSubviews()
tagBadgeView.setNeedsLayout()
tagBadgeView.layoutIfNeeded()
tagBadgeView.setCornerRadius(corners: [.topLeft, .bottomRight], withCornerRadii: CGSize(width: 10, height: 10))
}
}
extension VipRechargeView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let maxY = scrollView.contentOffset.y
let alpha = maxY / kNaviHeight
navBarView.alpha = alpha < 0.0 ? 0.0 : alpha
}
}