|
@@ -0,0 +1,324 @@
|
|
|
+//
|
|
|
+// TSPurchaseVC.swift
|
|
|
+// TSLiveWallpaper
|
|
|
+//
|
|
|
+// Created by 100Years on 2025/1/14.
|
|
|
+//
|
|
|
+
|
|
|
+import Combine
|
|
|
+import SwiftUI
|
|
|
+//import SwiftUIX
|
|
|
+class PurchaseViewModel : ObservableObject{
|
|
|
+
|
|
|
+ @Published var selectedType: PremiumPeriod = .lifetime
|
|
|
+
|
|
|
+ /// 订阅publisher
|
|
|
+ let buyPublisher = PassthroughSubject<Bool,Never>()
|
|
|
+ /// 隐私
|
|
|
+ let privacyPublisher = PassthroughSubject<Bool, Never>()
|
|
|
+ /// term
|
|
|
+ let termPublisher = PassthroughSubject<Bool, Never>()
|
|
|
+ /// restore
|
|
|
+ let restorePublisher = PassthroughSubject<Bool, Never>()
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+class TSPurchaseVC: TSBaseVC {
|
|
|
+
|
|
|
+ var closePageBlock:(()->Void)?
|
|
|
+
|
|
|
+ var viewModel: PurchaseViewModel = .init()
|
|
|
+ var cancellabel: [AnyCancellable] = []
|
|
|
+ var buyPeriod:PremiumPeriod = .year
|
|
|
+ lazy var purchaseManager: PurchaseManager = {
|
|
|
+ let purchaseManager = PurchaseManager.default
|
|
|
+ return purchaseManager
|
|
|
+ }()
|
|
|
+
|
|
|
+ lazy var hostVc: UIHostingController<PurchaseView> = {
|
|
|
+ let vc = UIHostingController(rootView: PurchaseView(viewModel: viewModel))
|
|
|
+ vc.view.backgroundColor = .clear
|
|
|
+ return vc
|
|
|
+ }()
|
|
|
+
|
|
|
+ override func createView() {
|
|
|
+ addNormalNavBarView()
|
|
|
+ _ = setNavigationItem("", imageName: "close_gray", direction: .left, action: #selector(closePage))
|
|
|
+ setViewBgImageNamed(named: "view_main_bg")
|
|
|
+
|
|
|
+ contentView.addSubview(hostVc.view)
|
|
|
+ hostVc.view.snp.makeConstraints { make in
|
|
|
+ make.top.equalTo(contentView.safeAreaLayoutGuide.snp.top)
|
|
|
+ make.bottom.equalTo(contentView.safeAreaLayoutGuide.snp.bottom)
|
|
|
+ make.leading.trailing.top.equalToSuperview()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ override func dealThings() {
|
|
|
+ addNotifaction()
|
|
|
+ onPurchaseStateChanged()
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func addNotifaction() {
|
|
|
+
|
|
|
+ viewModel.buyPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
|
|
|
+ guard let self = self else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ PurchaseManager.default.pay(for: self.viewModel.selectedType)
|
|
|
+ }.store(in: &cancellabel)
|
|
|
+
|
|
|
+ viewModel.privacyPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
|
|
|
+ guard let self = self else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let vc = TSBusinessWebVC(urlType: .privacy)
|
|
|
+ vc.hidesBottomBarWhenPushed = true
|
|
|
+ kPresentModalVC(target: self, modelVC: vc)
|
|
|
+
|
|
|
+ }.store(in: &cancellabel)
|
|
|
+
|
|
|
+ viewModel.termPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
|
|
|
+ guard let self = self else {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ let vc = TSBusinessWebVC(urlType: .terms)
|
|
|
+ vc.hidesBottomBarWhenPushed = true
|
|
|
+ kPresentModalVC(target: self, modelVC: vc)
|
|
|
+
|
|
|
+ }.store(in: &cancellabel)
|
|
|
+
|
|
|
+ viewModel.restorePublisher.receive(on: DispatchQueue.main).sink { _ in
|
|
|
+ PurchaseManager.default.restorePremium()
|
|
|
+ }.store(in: &cancellabel)
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ func onPurchaseStateChanged(){
|
|
|
+ purchaseManager.onPurchaseStateChanged = { [weak self] manager,state,object in
|
|
|
+ guard let self = self else { return }
|
|
|
+
|
|
|
+ DispatchQueue.main.async {
|
|
|
+ switch state {
|
|
|
+ case .none:
|
|
|
+ break
|
|
|
+ case .loading:
|
|
|
+ TSToastShared.showLoading(text: "Getting price".localized)
|
|
|
+ case .loadSuccess:
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ case .loadFail:
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ let message = "Get price failure, Will automatically retry in 5 seconds".localized
|
|
|
+ TSToastShared.showToast(text: message)
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
|
|
|
+ PurchaseManager.default.requestProducts()
|
|
|
+ }
|
|
|
+ case .paying:
|
|
|
+ TSToastShared.showLoading(text: "Purchasing now".localized)
|
|
|
+ case .paySuccess:
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ let loadingText = manager.isVip ? "Congratulation you have become VIP".localized : "Finish".localized
|
|
|
+ TSToastShared.showToast(text:loadingText)
|
|
|
+ if manager.isVip {
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
|
+ self.closePage()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ case .payFail:
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ if let str = object as? String {
|
|
|
+ TSToastShared.showToast(text: str)
|
|
|
+ }
|
|
|
+
|
|
|
+ case .restoreing:
|
|
|
+ TSToastShared.showLoading(text: "Restoring now".localized)
|
|
|
+ case .restoreSuccess:
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ let loadingText = manager.isVip ? "Congratulation you have become VIP".localized : "Couldn't Restore Subscription".localized
|
|
|
+ debugPrint(loadingText)
|
|
|
+ TSToastShared.showToast(text:loadingText)
|
|
|
+ if manager.isVip {
|
|
|
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
|
+ self.closePage()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ case .restoreFail:
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ let loadingText = (object as? String) ?? "Failed to restore subscribe, please try again".localized
|
|
|
+ debugPrint(loadingText)
|
|
|
+ TSToastShared.showToast(text: loadingText)
|
|
|
+ case .verifying:
|
|
|
+ #if DEBUG
|
|
|
+ TSToastShared.showLoading(text: "Verifying receipt...".localized)
|
|
|
+ #endif
|
|
|
+ case .verifySuccess:
|
|
|
+ break
|
|
|
+ case .verifyFail:
|
|
|
+ #if DEBUG
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ let message = (object as? String) ?? "Verify receipt failed"
|
|
|
+ TSToastShared.showToast(text:message)
|
|
|
+
|
|
|
+ #endif
|
|
|
+ }
|
|
|
+ }
|
|
|
+ debugPrint("PurchaseManager onPurchaseStateChanged=\(String(describing: state))")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @objc func closePage(){
|
|
|
+ closePageBlock?()
|
|
|
+ TSToastShared.hideLoading()
|
|
|
+ self.dismiss(animated: true)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+extension TSPurchaseVC{
|
|
|
+
|
|
|
+ static func show(target:UIViewController,closePageBlock:(()->Void)?){
|
|
|
+ let vc = TSPurchaseVC()
|
|
|
+ vc.closePageBlock = closePageBlock
|
|
|
+ let navi = TSBaseNavigationC(rootViewController: vc)
|
|
|
+ navi.modalPresentationStyle = .overFullScreen
|
|
|
+ target.present(navi, animated: true)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+struct PurchaseView :View {
|
|
|
+
|
|
|
+ @ObservedObject var viewModel: PurchaseViewModel
|
|
|
+
|
|
|
+ var body: some View {
|
|
|
+ ScrollView {
|
|
|
+ Spacer().frame(height: 20)
|
|
|
+
|
|
|
+ VStack {
|
|
|
+ Image("vip_big_icon").resizable().frame(width: 124, height: 102)
|
|
|
+ Spacer().frame(height: 12)
|
|
|
+ Image("lIvelivepro")
|
|
|
+
|
|
|
+ Spacer().frame(height: 40)
|
|
|
+
|
|
|
+ ZStack {
|
|
|
+ VStack(alignment: .leading,spacing: 8) {
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ Image("check").resizable().frame(width: 24, height: 24)
|
|
|
+ Text("All Premium Wallpapers")
|
|
|
+ }
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ Image("check").resizable().frame(width: 24, height: 24)
|
|
|
+ Text("Unlimited Video To Live")
|
|
|
+ }
|
|
|
+
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ Image("check").resizable().frame(width: 24, height: 24)
|
|
|
+ Text("100% No Ads").multilineTextAlignment(.leading)
|
|
|
+ }
|
|
|
+ }.font(.font(size: 16)).foregroundColor(UIColor.lesserText.color)
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ Spacer().frame(height: 72)
|
|
|
+
|
|
|
+
|
|
|
+ VStack(spacing: 12) {
|
|
|
+ PurchaseItemView(title: "Lifetime", type: .lifetime, selectedType: $viewModel.selectedType).onTapGesture {
|
|
|
+ viewModel.selectedType = .lifetime
|
|
|
+ }
|
|
|
+ HStack {
|
|
|
+ PurchaseItemView(title: "Yearly", type: .year, selectedType: $viewModel.selectedType).onTapGesture {
|
|
|
+ viewModel.selectedType = .year
|
|
|
+ }
|
|
|
+
|
|
|
+ PurchaseItemView(title: "Monthly", type: .month, selectedType: $viewModel.selectedType).onTapGesture {
|
|
|
+ viewModel.selectedType = .month
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Button {
|
|
|
+ viewModel.buyPublisher.send(true)
|
|
|
+ } label: {
|
|
|
+ ZStack {
|
|
|
+ Image("normal_submit_bg").resizable().aspectRatio(contentMode: .fill)
|
|
|
+ Text("Continue")
|
|
|
+ .font(.system(size: 16))
|
|
|
+ .foregroundColor(.hex("#010101"))
|
|
|
+
|
|
|
+ }.frame(maxWidth: .infinity ,maxHeight: 48.0)
|
|
|
+ .cornerRadius(24.0)
|
|
|
+ }
|
|
|
+
|
|
|
+ HStack {
|
|
|
+ Text("Recurring billing, cancel anytime")
|
|
|
+ .foregroundColor(Color.hex("#12FFF7")) +
|
|
|
+ Text(", Payment will be charged to your iTunes account at confirmation of purchase. Subscriptions automatically renew for the same applicable term and price, unless auto-renew is turned off at least 24 hours before the end of the current period.")
|
|
|
+ .foregroundColor(UIColor.lesserText.color)
|
|
|
+ }
|
|
|
+ .multilineTextAlignment(.center).font(.font(size: 8))
|
|
|
+ .onTapGesture {
|
|
|
+ viewModel.privacyPublisher.send(true)
|
|
|
+ }
|
|
|
+
|
|
|
+ HStack(spacing: 8) {
|
|
|
+ Text("Term of us")
|
|
|
+ .onTapGesture {
|
|
|
+ viewModel.termPublisher.send(true)
|
|
|
+ }
|
|
|
+ Text("|")
|
|
|
+ Text("Privacy Policy")
|
|
|
+ .onTapGesture {
|
|
|
+ viewModel.privacyPublisher.send(true)
|
|
|
+ }
|
|
|
+ Text("|")
|
|
|
+ Text("Restore")
|
|
|
+ .onTapGesture {
|
|
|
+ viewModel.restorePublisher.send(true)
|
|
|
+ }
|
|
|
+ }.font(.system(size: 12)).foregroundColor(.hex("#999999"))
|
|
|
+ }.padding(.horizontal)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+struct PurchaseItemView: View {
|
|
|
+ var title: String
|
|
|
+ var type: PremiumPeriod
|
|
|
+ @Binding var selectedType: PremiumPeriod
|
|
|
+
|
|
|
+ var body: some View {
|
|
|
+ ZStack {
|
|
|
+ Color.white.opacity(0.1)
|
|
|
+ HStack {
|
|
|
+ VStack(alignment: .leading, spacing: 8) {
|
|
|
+ Text(title).font(.font(size: 14)).foregroundColor(UIColor.textAssist.color)
|
|
|
+ Text(PurchaseManager.default.price(for: type) ?? "--").font(.font(size: 18,weight: .medium)).foregroundColor(UIColor.mainText.color)
|
|
|
+ }
|
|
|
+ Spacer()
|
|
|
+ if type == selectedType {
|
|
|
+ Image(.radioboxSelected)
|
|
|
+ }
|
|
|
+ }.padding(.horizontal)
|
|
|
+ }
|
|
|
+ .frame(height: 74) // 设置高度
|
|
|
+ .cornerRadius(16.0) // 圆角
|
|
|
+// .overlay(
|
|
|
+// RoundedRectangle(cornerRadius: 12)
|
|
|
+// .stroke(Color.hex("#6EF4F4"), lineWidth: type == selectedType ? 1 : 0) // 边框
|
|
|
+// )
|
|
|
+ }
|
|
|
+}
|