100Years 3 сар өмнө
parent
commit
098391b9ba

+ 26 - 4
TSLiveWallpaper.xcodeproj/project.pbxproj

@@ -98,6 +98,9 @@
 		A8F76C3C2D35026200AA6E93 /* TSPurchaseMembershipVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F76C3B2D35026100AA6E93 /* TSPurchaseMembershipVC.swift */; };
 		A8F76C422D350A9600AA6E93 /* TSPurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F76C3E2D350A9600AA6E93 /* TSPurchaseManager.swift */; };
 		A8F76C472D3510FE00AA6E93 /* TSNetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F76C462D3510FA00AA6E93 /* TSNetworkManager.swift */; };
+		A8F76C4D2D3747B400AA6E93 /* TSPurchaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F76C4C2D3747AB00AA6E93 /* TSPurchaseVC.swift */; };
+		A8F774522D3757E700AA6E93 /* Color+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774512D3757DF00AA6E93 /* Color+Ex.swift */; };
+		A8F774542D37581F00AA6E93 /* Font+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774532D37581700AA6E93 /* Font+Ex.swift */; };
 		A8F778AE2D1AC12400BF55D5 /* TSRandomWallpaperBrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F778AD2D1AC12100BF55D5 /* TSRandomWallpaperBrowseView.swift */; };
 		A8F778B02D1AC17500BF55D5 /* TSBaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F778AF2D1AC16F00BF55D5 /* TSBaseView.swift */; };
 		A8F778B22D1BA07200BF55D5 /* TSRandomWallpaperBrowseSelectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F778B12D1BA07000BF55D5 /* TSRandomWallpaperBrowseSelectView.swift */; };
@@ -201,6 +204,9 @@
 		A8F76C3B2D35026100AA6E93 /* TSPurchaseMembershipVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseMembershipVC.swift; sourceTree = "<group>"; };
 		A8F76C3E2D350A9600AA6E93 /* TSPurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseManager.swift; sourceTree = "<group>"; };
 		A8F76C462D3510FA00AA6E93 /* TSNetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSNetworkManager.swift; sourceTree = "<group>"; };
+		A8F76C4C2D3747AB00AA6E93 /* TSPurchaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVC.swift; sourceTree = "<group>"; };
+		A8F774512D3757DF00AA6E93 /* Color+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Ex.swift"; sourceTree = "<group>"; };
+		A8F774532D37581700AA6E93 /* Font+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Ex.swift"; sourceTree = "<group>"; };
 		A8F778AD2D1AC12100BF55D5 /* TSRandomWallpaperBrowseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRandomWallpaperBrowseView.swift; sourceTree = "<group>"; };
 		A8F778AF2D1AC16F00BF55D5 /* TSBaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBaseView.swift; sourceTree = "<group>"; };
 		A8F778B12D1BA07000BF55D5 /* TSRandomWallpaperBrowseSelectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRandomWallpaperBrowseSelectView.swift; sourceTree = "<group>"; };
@@ -280,6 +286,7 @@
 		A81CA4752D15778800A3AAC8 /* Ex */ = {
 			isa = PBXGroup;
 			children = (
+				A8F774502D3757D300AA6E93 /* SwiftUI */,
 				A8C4C01C2D2397B4003C46FC /* UIViewController+Ex.swift */,
 				A8477C982D2291F100DF0B93 /* UserDefault+Ex.swift */,
 				A81F5B4E2D19673500740085 /* AVAsset+Ex.swift */,
@@ -635,6 +642,7 @@
 		A8F76C3A2D35022300AA6E93 /* TSPurchaseMembershipVC */ = {
 			isa = PBXGroup;
 			children = (
+				A8F76C4C2D3747AB00AA6E93 /* TSPurchaseVC.swift */,
 				A8F76C3B2D35026100AA6E93 /* TSPurchaseMembershipVC.swift */,
 				A81CA4A22D16747900A3AAC8 /* TSViewTool */,
 			);
@@ -657,6 +665,15 @@
 			path = NetworkManager;
 			sourceTree = "<group>";
 		};
+		A8F774502D3757D300AA6E93 /* SwiftUI */ = {
+			isa = PBXGroup;
+			children = (
+				A8F774512D3757DF00AA6E93 /* Color+Ex.swift */,
+				A8F774532D37581700AA6E93 /* Font+Ex.swift */,
+			);
+			path = SwiftUI;
+			sourceTree = "<group>";
+		};
 		A8F778B52D1BE98D00BF55D5 /* TSLiveWallpaperBrowseVC */ = {
 			isa = PBXGroup;
 			children = (
@@ -721,6 +738,8 @@
 			);
 			mainGroup = A8E56BCB2D1520DD003C54AF;
 			minimizedProjectReferenceProxies = 1;
+			packageReferences = (
+			);
 			preferredProjectObjectVersion = 77;
 			productRefGroup = A8E56BD52D1520DD003C54AF /* Products */;
 			projectDirPath = "";
@@ -800,6 +819,7 @@
 				A8F76C3C2D35026200AA6E93 /* TSPurchaseMembershipVC.swift in Sources */,
 				A81CA46E2D156C7000A3AAC8 /* GlobalImports.swift in Sources */,
 				A87833202D293EEC00E47F2C /* TSSimpleTableView.swift in Sources */,
+				A8F774522D3757E700AA6E93 /* Color+Ex.swift in Sources */,
 				A81F5B4D2D1965F800740085 /* UIImage+Ex.swift in Sources */,
 				A81CA4832D157F5C00A3AAC8 /* UIImageView+Ex.swift in Sources */,
 				A81F5B322D18FA2E00740085 /* Component.swift in Sources */,
@@ -808,6 +828,7 @@
 				A8C4C0E62D268D02003C46FC /* LivePhotoCreater.swift in Sources */,
 				A8C4C0E72D268D02003C46FC /* VideoRecorder.swift in Sources */,
 				A81F5B342D18FA2E00740085 /* CollectionViewComponent.swift in Sources */,
+				A8F76C4D2D3747B400AA6E93 /* TSPurchaseVC.swift in Sources */,
 				A81CA4722D1575B900A3AAC8 /* TSBaseNavigationBarView.swift in Sources */,
 				A81F5B5B2D1A5F2300740085 /* TSHomeTopBannerCell.swift in Sources */,
 				A83946312D1D66A000ABFF0D /* TSTermsServiceVC.swift in Sources */,
@@ -877,6 +898,7 @@
 				A8C4C0A72D24218A003C46FC /* LivePhotoUtil.m in Sources */,
 				A8C4C0A82D24218A003C46FC /* Converter4Image.swift in Sources */,
 				A83946432D1D701500ABFF0D /* TSLiveWallpaperCopyrightVC.swift in Sources */,
+				A8F774542D37581F00AA6E93 /* Font+Ex.swift in Sources */,
 				A8C4C0AB2D2427E7003C46FC /* LivePhotoConverter.swift in Sources */,
 				A81F5B3C2D19087100740085 /* TSRandomWallpaperCell.swift in Sources */,
 				A81F5B442D19559C00740085 /* EditorVideoControlViewCell.swift in Sources */,
@@ -908,7 +930,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 4;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_APP_SANDBOX = NO;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -924,7 +946,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0.1;
+				MARKETING_VERSION = 1.0.2;
 				PRODUCT_BUNDLE_IDENTIFIER = musicplayer.offline.com;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -947,7 +969,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 4;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_APP_SANDBOX = NO;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -963,7 +985,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0.1;
+				MARKETING_VERSION = 1.0.2;
 				PRODUCT_BUNDLE_IDENTIFIER = musicplayer.offline.com;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";

+ 22 - 0
TSLiveWallpaper/Assets.xcassets/Vip/lIvelivepro.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "lIvelivepro@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "lIvelivepro@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
TSLiveWallpaper/Assets.xcassets/Vip/lIvelivepro.imageset/lIvelivepro@2x.png


BIN
TSLiveWallpaper/Assets.xcassets/Vip/lIvelivepro.imageset/lIvelivepro@3x.png


+ 15 - 6
TSLiveWallpaper/Business/TSEditLiveVC/TSEditLiveVC.swift

@@ -47,7 +47,7 @@ class TSEditLiveVC: TSBaseVC, UINavigationControllerDelegate {
             
             //判断 vip
             if kPurchaseDefault.freeNumAvailable() == false{
-                TSPurchaseMembershipVC.show(target: self) {[weak self] in
+                TSPurchaseVC.show(target: self) {[weak self] in
                     guard let self = self else { return }
                     reloadView()
                 }
@@ -122,13 +122,13 @@ extension TSEditLiveVC: UIImagePickerControllerDelegate {
         picker.delegate = self
         picker.videoMaximumDuration = 3.0
         present(picker, animated: true) {
-            TSToastShared.hideLoading()
+            self.hideLoading()
         }
     }
     // 用户完成选择
     func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
         
-        TSToastShared.hideLoading()
+        hideLoading()
         if let editedURL = info[.mediaURL] as? URL {
             debugPrint("Selected video: \(editedURL)")
             // 在这里处理选中的视频(例如上传或保存)
@@ -153,9 +153,18 @@ extension TSEditLiveVC: UIImagePickerControllerDelegate {
 
     // 用户取消选择
     func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
-        TSToastShared.hideLoading()
+        hideLoading()
         picker.dismiss(animated: true, completion: nil)
     }
+    
+    
+    func hideLoading(){
+        
+        kDelayMainShort {
+            TSToastShared.hideLoading()
+        }
+    
+    }
 }
 
 extension TSEditLiveVC{
@@ -233,7 +242,7 @@ extension TSEditLiveVC{
 //                if let resources = resources {
 //                    LivePhoto.saveToLibrary(resources, completion: { (success) in
 //                        kExecuteOnMainThread {
-//                            TSToastShared.hideLoading()
+//                            hideLoading()
 //                            if success {
 //                                debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.")
 //                                kSavePhotoSuccesswShared.show(atView: self.view)
@@ -261,7 +270,7 @@ extension TSEditLiveVC{
 //                    if let resources = resources {
 //                        LivePhoto.saveToLibrary(resources, completion: { (success) in
 //                            kExecuteOnMainThread {
-//                                TSToastShared.hideLoading()
+//                                hideLoading()
 //                                if success {
 //                                    debugPrint("Live Photo Saved,The live photo was successfully saved to Photos.")
 //                                    kSavePhotoSuccesswShared.show(atView: self.view)

+ 1 - 1
TSLiveWallpaper/Business/TSHomeVC/TSLiveWallpaperBrowseVC/TSLiveWallpaperBrowseVC.swift

@@ -88,7 +88,7 @@ class TSLiveWallpaperBrowseVC: TSBaseVC {
                PurchaseManager.default.isVip == false
             {
                 cell?.stopPlayLive()
-                TSPurchaseMembershipVC.show(target: self) {[weak self]  in
+                TSPurchaseVC.show(target: self) {[weak self]  in
                     guard let self = self else { return }
                     cell?.stratPlayLive()
                 }

+ 5 - 5
TSLiveWallpaper/Business/TSMineVC/TSMineVC.swift

@@ -36,7 +36,7 @@ class TSMineVC: TSBaseVC {
     lazy var upgradeBtn: UIButton = {
         let upgradeBtn = TSViewTool.createNormalSubmitBtn(title: "Upgrade".localized) { [weak self]  in
             guard let self = self else { return }
-            TSPurchaseMembershipVC.show(target: self) {[weak self]  in
+            TSPurchaseVC.show(target: self) {[weak self]  in
                 guard let self = self else { return }
                 updateVipView()
             }
@@ -188,11 +188,11 @@ class TSMineVC: TSBaseVC {
             rectCorner:.allCorners,
             tapBlock: {[weak self] itemModel, index, view  in
                 guard let self = self else { return }
-                TSToastShared.showToast(text: "版本号")
+
 #if DEBUG
-//                let vc = TSPurchaseMembershipVC()
-//                vc.hidesBottomBarWhenPushed = true
-//                kPresentModalVC(target: self, modelVC: vc)
+                let vc = TSPurchaseVC()
+                vc.hidesBottomBarWhenPushed = true
+                kPresentModalVC(target: self, modelVC: vc)
 #endif
                 
             }))

+ 8 - 8
TSLiveWallpaper/Business/TSPurchaseMembershipVC/TSPurchaseMembershipVC.swift

@@ -5,7 +5,7 @@
 //  Created by 100Years on 2025/1/13.
 //
 
-class TSPurchaseMembershipVC: TSBaseVC {
+class TSPurchaseMembership111VC: TSBaseVC {
     
     var closePageBlock:(()->Void)?
     var buyPeriod:PremiumPeriod = .year
@@ -211,7 +211,7 @@ class TSPurchaseMembershipVC: TSBaseVC {
 }
 
 
-extension TSPurchaseMembershipVC {
+extension TSPurchaseMembership111VC {
     
 
     func creatTopView() -> UIView {
@@ -411,14 +411,14 @@ class TSPurchaseMembershipCell: TSBaseView {
     
 }
 
-extension TSPurchaseMembershipVC{
+extension TSPurchaseMembership111VC{
     
     static func show(target:UIViewController,closePageBlock:(()->Void)?){
-        let vc = TSPurchaseMembershipVC()
-        vc.closePageBlock = closePageBlock
-        let navi = TSBaseNavigationC(rootViewController: vc)
-        navi.modalPresentationStyle = .overFullScreen
-        target.present(navi, animated: true)
+//        let vc = TSPurchaseMembershipVC()
+//        vc.closePageBlock = closePageBlock
+//        let navi = TSBaseNavigationC(rootViewController: vc)
+//        navi.modalPresentationStyle = .overFullScreen
+//        target.present(navi, animated: true)
     }
 
 }

+ 324 - 0
TSLiveWallpaper/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift

@@ -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) // 边框
+//        )
+    }
+}

+ 1 - 1
TSLiveWallpaper/Business/TSRandomWallpaperVC/TSRandomWallpaperBrowseVC/TSRandomWallpaperBrowseVC.swift

@@ -122,7 +122,7 @@ class TSRandomWallpaperBrowseVC: TSBaseVC {
             guard let self = self else { return }
             //判断 vip
             if self.dataModel.vip == true,PurchaseManager.default.isVip == false{
-                TSPurchaseMembershipVC.show(target: self) {[weak self]  in
+                TSPurchaseVC.show(target: self) {[weak self]  in
                     guard let self = self else { return }
                 }
                 return

+ 47 - 0
TSLiveWallpaper/Common/Ex/SwiftUI/Color+Ex.swift

@@ -0,0 +1,47 @@
+//
+//  Color+Ex.swift
+//  TSLiveWallpaper
+//
+//  Created by 100Years on 2025/1/14.
+//
+
+import SwiftUI
+
+extension Color {
+    static func hex(_ hex: String) -> Color {
+        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
+        var int: UInt64 = 0
+        Scanner(string: hex).scanHexInt64(&int)
+
+        let a, r, g, b: UInt64
+        switch hex.count {
+        case 3: // RGB (12-bit)
+            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
+        case 6: // RGB (24-bit)
+            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
+        case 8: // ARGB (32-bit)
+            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
+        default:
+            (a, r, g, b) = (255, 0, 0, 0)
+        }
+
+        return Self(
+            .sRGB,
+            red: Double(r) / 255,
+            green: Double(g) / 255,
+            blue: Double(b) / 255,
+            opacity: Double(a) / 255
+        )
+    }
+
+    // 随机生成一个颜色
+    static var randomColor: Color {
+        // 随机生成 R, G, B 和 alpha(透明度)值
+        let red = Double.random(in: 0 ... 1)
+        let green = Double.random(in: 0 ... 1)
+        let blue = Double.random(in: 0 ... 1)
+        let alpha = Double.random(in: 0.5 ... 1) // 可选,透明度的范围
+
+        return Color(red: red, green: green, blue: blue, opacity: alpha)
+    }
+}

+ 16 - 0
TSLiveWallpaper/Common/Ex/SwiftUI/Font+Ex.swift

@@ -0,0 +1,16 @@
+//
+//  Font+Ex.swift
+//  TSLiveWallpaper
+//
+//  Created by 100Years on 2025/1/14.
+//
+
+
+import SwiftUI
+extension Font {
+    static func font(name: FontName = .PingFangSC, size: CGFloat, weight: UIFont.Weight = .regular) -> Font {
+        let uiFont =  UIFont.font(name: name,size: size,weight: weight)
+        return Font(uiFont as CTFont)
+    }
+}
+

+ 6 - 1
TSLiveWallpaper/Common/Ex/UIColor+Ex.swift

@@ -6,7 +6,7 @@
 //
 
 import UIKit
-
+import SwiftUI
 extension UIColor {
     /// 返回一个随机颜色
     static var random: UIColor {
@@ -41,4 +41,9 @@ extension UIColor {
         
         return UIColor(red: red, green: green, blue: blue, alpha: alpha)
     }
+    
+    var color: Color {
+        return Color(self)
+    }
+
 }

+ 77 - 17
TSLiveWallpaper/Common/Purchase/TSPurchaseManager.swift

@@ -9,9 +9,10 @@ import Foundation
 import StoreKit
 
 public enum PremiumPeriod: String, CaseIterable {
-    case none   = "none"
-    case year   = "Year"
-    case month  = "Month"
+    case none           = ""
+    case month          = "Monthly"
+    case year           = "Yearly"
+    case lifetime       = "Lifetime"
 }
 
 public struct PurchaseProduct {
@@ -60,11 +61,15 @@ let kPurchaseDefault = PurchaseManager.default
 public class PurchaseManager: NSObject {
     @objc public static let `default` = PurchaseManager()
 
+    //苹果共享密钥
     private let AppleSharedKey:String = "155c8104e2b041c0abae43ace199124c"
     
     //商品信息
     public lazy var purchaseProducts:[PurchaseProduct] = {
-        return [PurchaseProduct(productId: "1001", period:.month), PurchaseProduct(productId: "1002", period: .year)]
+        return [PurchaseProduct(productId: "1001", period:.month),
+                PurchaseProduct(productId: "1002", period: .year),
+                PurchaseProduct(productId: "003", period: .lifetime),
+        ]
     }()
 
     struct Config {
@@ -104,13 +109,17 @@ public class PurchaseManager: NSObject {
     }
 
     public var expiredDateString: String {
-        if let expDate = expiredDate {
-            let format = DateFormatter()
-            format.locale = .current
-            format.dateFormat = "yyyy-MM-dd"
-            return format.string(from: expDate)
+        if vipType == .lifetime{
+            return "Life Time"
         } else {
-            return "--"
+            if let expDate = expiredDate {
+                let format = DateFormatter()
+                format.locale = .current
+                format.dateFormat = "yyyy-MM-dd"
+                return format.string(from: expDate)
+            } else {
+                return "--"
+            }
         }
     }
 
@@ -187,11 +196,34 @@ extension PurchaseManager {
         return formatter.string(from: product.price)
     }
 
+    
+//    public func originalPrice(for period: PremiumPeriod) -> String? {
+//        guard let product = product(for: period) else {
+//            return nil
+//        }
+//        switch period {
+//        case .year, .lifetime:
+//            // 5折
+//            let price = product.price.doubleValue
+//            let calculatePrice = price * 2
+//            let originStr = String(format: "%.2f", calculatePrice)
+//            let originPrice = NSDecimalNumber(string: originStr, locale: product.priceLocale)
+//
+//            let formatter = NumberFormatter()
+//            formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
+//            formatter.numberStyle = .currency
+//            formatter.locale = product.priceLocale
+//            return formatter.string(from: originPrice)
+//        default:
+//            return nil
+//        }
+//    }
 }
 
 // MARK: 商品 & 订阅请求
 
 extension PurchaseManager {
+    ///请求商品
     public func requestProducts() {
         if !products.isEmpty {
             purchase(self, didChaged: .loadSuccess, object: nil)
@@ -211,7 +243,7 @@ extension PurchaseManager {
         debugPrint("PurchaseManager restoreCompletedTransactions")
     }
 
-    /// 支付
+    /// 购买支付
     public func pay(for period: PremiumPeriod) {
         guard SKPaymentQueue.canMakePayments() else {
             purchase(self, didChaged: .payFail, object: "Payment failed, please check your payment account")
@@ -375,11 +407,33 @@ extension PurchaseManager {
     }
 
     func handlerPayResult(transaction: SKPaymentTransaction, resp: [String: Any]) {
-        let info = resp["latest_receipt_info"] as? [[String: Any]]
-        if let firstItem = info?.first,
-           let expires_date_ms = firstItem["expires_date_ms"] as? String,
-           let productId = firstItem["product_id"] as? String {
-            updateExpireTime(expires_date_ms, for: productId)
+        var isLifetime = false
+        // 终生会员
+        if let receipt = resp["receipt"] as? [String: Any],
+           let in_app = receipt["in_app"] as? [[String: Any]] {
+            if let lifetimeProductId = purchaseProducts.first(where: { $0.period == .lifetime })?.productId,
+               let _ = in_app.filter({ ($0["product_id"] as? String) == lifetimeProductId }).first(where: { item in
+                   if let purchase_date = item["purchase_date"] as? String,
+                      !purchase_date.isEmpty {
+                       return true
+                   } else if let purchase_date_ms = item["purchase_date_ms"] as? String,
+                             !purchase_date_ms.isEmpty {
+                       return true
+                   }
+                   return false
+               }) {
+                updateExpireTime(lifetimeExpireTime, for: lifetimeProductId)
+                isLifetime = true
+            }
+        }
+
+        if !isLifetime {
+            let info = resp["latest_receipt_info"] as? [[String: Any]]
+            if let firstItem = info?.first,
+               let expires_date_ms = firstItem["expires_date_ms"] as? String,
+               let productId = firstItem["product_id"] as? String {
+                updateExpireTime(expires_date_ms, for: productId)
+            }
         }
 
         SKPaymentQueue.default().finishTransaction(transaction)
@@ -392,6 +446,12 @@ extension PurchaseManager {
             }
         }
     }
+    
+    // 终生会员过期时间:100年
+    var lifetimeExpireTime: String {
+        let date = Date().addingTimeInterval(100 * 365 * 24 * 60 * 60)
+        return "\(date.timeIntervalSince1970 * 1000)"
+    }
 
 }
 
@@ -409,7 +469,7 @@ public extension PurchaseManager {
 }
 
 
-
+/// 免费次数
 extension PurchaseManager {
     /// 使用一次免费次数
     func useOnceForFree(){