This commit is contained in:
zeng 2026-03-17 17:07:31 +08:00
parent 48ddd099be
commit 9738d46d1c
105 changed files with 3491 additions and 9 deletions

View File

@ -24,6 +24,7 @@ target 'XSeri' do
pod 'MJRefresh' pod 'MJRefresh'
pod 'HWPanModal' pod 'HWPanModal'
pod 'ZLPhotoBrowser' pod 'ZLPhotoBrowser'
pod 'JXIAPManager', :git => 'https://git.qinjiu8.com/zengjx/JXIAPManager.git', :tag => '0.0.3'
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|

View File

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
50DCF656E93DB15465B55F09 /* Pods_XSeri.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 161AA91CBF35A7C2C85D6649 /* Pods_XSeri.framework */; }; 50DCF656E93DB15465B55F09 /* Pods_XSeri.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 161AA91CBF35A7C2C85D6649 /* Pods_XSeri.framework */; };
F304E6102F67A9B600E9B0A6 /* XSShortDetailLockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F304E60F2F67A9B600E9B0A6 /* XSShortDetailLockView.swift */; };
F304E6122F67D74A00E9B0A6 /* XSVideoUnlockResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F304E6112F67D74A00E9B0A6 /* XSVideoUnlockResult.swift */; };
F347D28D2F03709200786648 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F347D2832F03709200786648 /* AppDelegate.swift */; }; F347D28D2F03709200786648 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F347D2832F03709200786648 /* AppDelegate.swift */; };
F347D28E2F03709200786648 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F347D28A2F03709200786648 /* SceneDelegate.swift */; }; F347D28E2F03709200786648 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F347D28A2F03709200786648 /* SceneDelegate.swift */; };
F347D2902F03709200786648 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F347D2842F03709200786648 /* Assets.xcassets */; }; F347D2902F03709200786648 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F347D2842F03709200786648 /* Assets.xcassets */; };
@ -135,6 +137,33 @@
F3B312BD2F30B0A10093B180 /* XSSearchSuggestionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B312B82F30B0A10093B180 /* XSSearchSuggestionCell.swift */; }; F3B312BD2F30B0A10093B180 /* XSSearchSuggestionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B312B82F30B0A10093B180 /* XSSearchSuggestionCell.swift */; };
F3B312BE2F30B0A10093B180 /* XSSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B312B92F30B0A10093B180 /* XSSearchResultCell.swift */; }; F3B312BE2F30B0A10093B180 /* XSSearchResultCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B312B92F30B0A10093B180 /* XSSearchResultCell.swift */; };
F3B312BF2F30B2000093B180 /* XSSearchHistoryHotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B312B52F30B2000093B180 /* XSSearchHistoryHotView.swift */; }; F3B312BF2F30B2000093B180 /* XSSearchHistoryHotView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3B312B52F30B2000093B180 /* XSSearchHistoryHotView.swift */; };
F3F388D52F67DDBC001B0E15 /* XSMineWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388D42F67DDBC001B0E15 /* XSMineWalletView.swift */; };
F3F388D72F67EDEC001B0E15 /* XSMineCoinsPackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388D62F67EDEC001B0E15 /* XSMineCoinsPackView.swift */; };
F3F388D92F67F9E1001B0E15 /* XSMineVipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388D82F67F9E1001B0E15 /* XSMineVipView.swift */; };
F3F388DF2F67FE6D001B0E15 /* XSStoreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388DE2F67FE6D001B0E15 /* XSStoreViewController.swift */; };
F3F388E12F680090001B0E15 /* XSScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388E02F68008F001B0E15 /* XSScrollView.swift */; };
F3F388E32F680316001B0E15 /* XSStoreCoinsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388E22F680316001B0E15 /* XSStoreCoinsView.swift */; };
F3F388E52F68032A001B0E15 /* XSStoreVipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388E42F68032A001B0E15 /* XSStoreVipView.swift */; };
F3F388E72F6805DE001B0E15 /* XSPayItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388E62F6805DE001B0E15 /* XSPayItem.swift */; };
F3F388E92F68062F001B0E15 /* XSPayDateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388E82F68062F001B0E15 /* XSPayDateModel.swift */; };
F3F388EC2F680CEE001B0E15 /* XSIapManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388EB2F680CEE001B0E15 /* XSIapManager.swift */; };
F3F388EE2F68E6EB001B0E15 /* XSWaitRestoreModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388ED2F68E6EB001B0E15 /* XSWaitRestoreModel.swift */; };
F3F388F02F68E737001B0E15 /* XSStoreAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388EF2F68E737001B0E15 /* XSStoreAPI.swift */; };
F3F388F22F68E9BE001B0E15 /* XSIapVerifyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388F12F68E9BE001B0E15 /* XSIapVerifyModel.swift */; };
F3F388F42F68F2CD001B0E15 /* XSIapOrderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388F32F68F2CD001B0E15 /* XSIapOrderModel.swift */; };
F3F388F62F68F746001B0E15 /* XSPayDataRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388F52F68F746001B0E15 /* XSPayDataRequest.swift */; };
F3F388F82F692031001B0E15 /* XSWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388F72F692031001B0E15 /* XSWalletViewController.swift */; };
F3F388FA2F69240E001B0E15 /* XSWalletHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388F92F69240E001B0E15 /* XSWalletHeaderView.swift */; };
F3F388FC2F692CEF001B0E15 /* XSWalletCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388FB2F692CEF001B0E15 /* XSWalletCell.swift */; };
F3F388FE2F6931D1001B0E15 /* XSConsumptionRecordsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388FD2F6931D1001B0E15 /* XSConsumptionRecordsViewController.swift */; };
F3F389002F693293001B0E15 /* XSConsumptionRecordsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F388FF2F693293001B0E15 /* XSConsumptionRecordsCell.swift */; };
F3F389022F693785001B0E15 /* XSBuyRecordsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F389012F693785001B0E15 /* XSBuyRecordsModel.swift */; };
F3F389042F6939C7001B0E15 /* XSOrderRecordsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F389032F6939C7001B0E15 /* XSOrderRecordsViewController.swift */; };
F3F389062F6939EE001B0E15 /* XSRewardCoinsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F389052F6939EE001B0E15 /* XSRewardCoinsViewController.swift */; };
F3F389082F6940BB001B0E15 /* XSRewardCoinsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F389072F6940BB001B0E15 /* XSRewardCoinsCell.swift */; };
F3F3890A2F694109001B0E15 /* XSSendCoinRecordModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F389092F694109001B0E15 /* XSSendCoinRecordModel.swift */; };
F3F3890C2F695055001B0E15 /* FACoinsRecordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F3890B2F695055001B0E15 /* FACoinsRecordViewController.swift */; };
F3F3890E2F695068001B0E15 /* FAVipRecordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F3890D2F695068001B0E15 /* FAVipRecordViewController.swift */; };
F3F683ED2F56C380008AF250 /* XSHomeHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F683EC2F56C380008AF250 /* XSHomeHistoryView.swift */; }; F3F683ED2F56C380008AF250 /* XSHomeHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F683EC2F56C380008AF250 /* XSHomeHistoryView.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -142,6 +171,8 @@
161AA91CBF35A7C2C85D6649 /* Pods_XSeri.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_XSeri.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 161AA91CBF35A7C2C85D6649 /* Pods_XSeri.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_XSeri.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3711C91B5F8043B3213F7116 /* Pods-XSeri.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XSeri.debug.xcconfig"; path = "Target Support Files/Pods-XSeri/Pods-XSeri.debug.xcconfig"; sourceTree = "<group>"; }; 3711C91B5F8043B3213F7116 /* Pods-XSeri.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XSeri.debug.xcconfig"; path = "Target Support Files/Pods-XSeri/Pods-XSeri.debug.xcconfig"; sourceTree = "<group>"; };
A9C8E3A3362E04C75537A56F /* Pods-XSeri.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XSeri.release.xcconfig"; path = "Target Support Files/Pods-XSeri/Pods-XSeri.release.xcconfig"; sourceTree = "<group>"; }; A9C8E3A3362E04C75537A56F /* Pods-XSeri.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-XSeri.release.xcconfig"; path = "Target Support Files/Pods-XSeri/Pods-XSeri.release.xcconfig"; sourceTree = "<group>"; };
F304E60F2F67A9B600E9B0A6 /* XSShortDetailLockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSShortDetailLockView.swift; sourceTree = "<group>"; };
F304E6112F67D74A00E9B0A6 /* XSVideoUnlockResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSVideoUnlockResult.swift; sourceTree = "<group>"; };
F347D26B2F03708600786648 /* XSeri.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XSeri.app; sourceTree = BUILT_PRODUCTS_DIR; }; F347D26B2F03708600786648 /* XSeri.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XSeri.app; sourceTree = BUILT_PRODUCTS_DIR; };
F347D2832F03709200786648 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; F347D2832F03709200786648 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
F347D2842F03709200786648 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; F347D2842F03709200786648 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -271,6 +302,33 @@
F3B312B62F319CBE0093B180 /* XSEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSEmpty.swift; sourceTree = "<group>"; }; F3B312B62F319CBE0093B180 /* XSEmpty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSEmpty.swift; sourceTree = "<group>"; };
F3B312B82F30B0A10093B180 /* XSSearchSuggestionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSSearchSuggestionCell.swift; sourceTree = "<group>"; }; F3B312B82F30B0A10093B180 /* XSSearchSuggestionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSSearchSuggestionCell.swift; sourceTree = "<group>"; };
F3B312B92F30B0A10093B180 /* XSSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSSearchResultCell.swift; sourceTree = "<group>"; }; F3B312B92F30B0A10093B180 /* XSSearchResultCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSSearchResultCell.swift; sourceTree = "<group>"; };
F3F388D42F67DDBC001B0E15 /* XSMineWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSMineWalletView.swift; sourceTree = "<group>"; };
F3F388D62F67EDEC001B0E15 /* XSMineCoinsPackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSMineCoinsPackView.swift; sourceTree = "<group>"; };
F3F388D82F67F9E1001B0E15 /* XSMineVipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSMineVipView.swift; sourceTree = "<group>"; };
F3F388DE2F67FE6D001B0E15 /* XSStoreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSStoreViewController.swift; sourceTree = "<group>"; };
F3F388E02F68008F001B0E15 /* XSScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSScrollView.swift; sourceTree = "<group>"; };
F3F388E22F680316001B0E15 /* XSStoreCoinsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSStoreCoinsView.swift; sourceTree = "<group>"; };
F3F388E42F68032A001B0E15 /* XSStoreVipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSStoreVipView.swift; sourceTree = "<group>"; };
F3F388E62F6805DE001B0E15 /* XSPayItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSPayItem.swift; sourceTree = "<group>"; };
F3F388E82F68062F001B0E15 /* XSPayDateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSPayDateModel.swift; sourceTree = "<group>"; };
F3F388EB2F680CEE001B0E15 /* XSIapManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSIapManager.swift; sourceTree = "<group>"; };
F3F388ED2F68E6EB001B0E15 /* XSWaitRestoreModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSWaitRestoreModel.swift; sourceTree = "<group>"; };
F3F388EF2F68E737001B0E15 /* XSStoreAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSStoreAPI.swift; sourceTree = "<group>"; };
F3F388F12F68E9BE001B0E15 /* XSIapVerifyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSIapVerifyModel.swift; sourceTree = "<group>"; };
F3F388F32F68F2CD001B0E15 /* XSIapOrderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSIapOrderModel.swift; sourceTree = "<group>"; };
F3F388F52F68F746001B0E15 /* XSPayDataRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSPayDataRequest.swift; sourceTree = "<group>"; };
F3F388F72F692031001B0E15 /* XSWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSWalletViewController.swift; sourceTree = "<group>"; };
F3F388F92F69240E001B0E15 /* XSWalletHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSWalletHeaderView.swift; sourceTree = "<group>"; };
F3F388FB2F692CEF001B0E15 /* XSWalletCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSWalletCell.swift; sourceTree = "<group>"; };
F3F388FD2F6931D1001B0E15 /* XSConsumptionRecordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSConsumptionRecordsViewController.swift; sourceTree = "<group>"; };
F3F388FF2F693293001B0E15 /* XSConsumptionRecordsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSConsumptionRecordsCell.swift; sourceTree = "<group>"; };
F3F389012F693785001B0E15 /* XSBuyRecordsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSBuyRecordsModel.swift; sourceTree = "<group>"; };
F3F389032F6939C7001B0E15 /* XSOrderRecordsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSOrderRecordsViewController.swift; sourceTree = "<group>"; };
F3F389052F6939EE001B0E15 /* XSRewardCoinsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSRewardCoinsViewController.swift; sourceTree = "<group>"; };
F3F389072F6940BB001B0E15 /* XSRewardCoinsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSRewardCoinsCell.swift; sourceTree = "<group>"; };
F3F389092F694109001B0E15 /* XSSendCoinRecordModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSSendCoinRecordModel.swift; sourceTree = "<group>"; };
F3F3890B2F695055001B0E15 /* FACoinsRecordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FACoinsRecordViewController.swift; sourceTree = "<group>"; };
F3F3890D2F695068001B0E15 /* FAVipRecordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FAVipRecordViewController.swift; sourceTree = "<group>"; };
F3F683EC2F56C380008AF250 /* XSHomeHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSHomeHistoryView.swift; sourceTree = "<group>"; }; F3F683EC2F56C380008AF250 /* XSHomeHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XSHomeHistoryView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -375,6 +433,7 @@
F347D2AD2F03AE3E00786648 /* Home */, F347D2AD2F03AE3E00786648 /* Home */,
F347D3172F0A53FE00786648 /* Discover */, F347D3172F0A53FE00786648 /* Discover */,
F347D3112F0A41FC00786648 /* Player */, F347D3112F0A41FC00786648 /* Player */,
F3F388DA2F67FDFF001B0E15 /* Store */,
); );
path = Class; path = Class;
sourceTree = "<group>"; sourceTree = "<group>";
@ -404,6 +463,7 @@
F35547F92F4E9F0A006F28CD /* XSView.swift */, F35547F92F4E9F0A006F28CD /* XSView.swift */,
F35547FB2F4EA069006F28CD /* XSButton.swift */, F35547FB2F4EA069006F28CD /* XSButton.swift */,
F355480D2F50485E006F28CD /* XSPanModalContentView.swift */, F355480D2F50485E006F28CD /* XSPanModalContentView.swift */,
F3F388E02F68008F001B0E15 /* XSScrollView.swift */,
); );
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
@ -422,6 +482,7 @@
F347D29C2F03A5A100786648 /* Libs */ = { F347D29C2F03A5A100786648 /* Libs */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
F3F388EA2F680CC7001B0E15 /* IapManager */,
F3B312B52F319CA50093B180 /* Empty */, F3B312B52F319CA50093B180 /* Empty */,
F347D3042F0A12DB00786648 /* HUD */, F347D3042F0A12DB00786648 /* HUD */,
F347D29E2F03A6B100786648 /* XSTool.swift */, F347D29E2F03A6B100786648 /* XSTool.swift */,
@ -563,6 +624,7 @@
F347D3212F0A58BE00786648 /* XSVideoAPI.swift */, F347D3212F0A58BE00786648 /* XSVideoAPI.swift */,
F347D30D2F0A39D500786648 /* XSHomeAPI.swift */, F347D30D2F0A39D500786648 /* XSHomeAPI.swift */,
F347D3002F0A0D7E00786648 /* XSUserAPI.swift */, F347D3002F0A0D7E00786648 /* XSUserAPI.swift */,
F3F388EF2F68E737001B0E15 /* XSStoreAPI.swift */,
); );
path = API; path = API;
sourceTree = "<group>"; sourceTree = "<group>";
@ -593,6 +655,7 @@
F347D3132F0A429C00786648 /* XSShortModel.swift */, F347D3132F0A429C00786648 /* XSShortModel.swift */,
F347D3152F0A430300786648 /* XSVideoInfoModel.swift */, F347D3152F0A430300786648 /* XSVideoInfoModel.swift */,
F3585C462F14EAE700EEC469 /* XSShortDetailModel.swift */, F3585C462F14EAE700EEC469 /* XSShortDetailModel.swift */,
F304E6112F67D74A00E9B0A6 /* XSVideoUnlockResult.swift */,
); );
path = Model; path = Model;
sourceTree = "<group>"; sourceTree = "<group>";
@ -652,6 +715,9 @@
F35548072F4FD703006F28CD /* XSMineUserInfoView.swift */, F35548072F4FD703006F28CD /* XSMineUserInfoView.swift */,
F35548052F4FD6DA006F28CD /* XSMinePlayHistoryView.swift */, F35548052F4FD6DA006F28CD /* XSMinePlayHistoryView.swift */,
F35548092F4FE99F006F28CD /* XSMinePlayHistoryCell.swift */, F35548092F4FE99F006F28CD /* XSMinePlayHistoryCell.swift */,
F3F388D42F67DDBC001B0E15 /* XSMineWalletView.swift */,
F3F388D62F67EDEC001B0E15 /* XSMineCoinsPackView.swift */,
F3F388D82F67F9E1001B0E15 /* XSMineVipView.swift */,
); );
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
@ -720,6 +786,7 @@
children = ( children = (
F3585C482F14EE8D00EEC469 /* XSShortDetailPlayerCell.swift */, F3585C482F14EE8D00EEC469 /* XSShortDetailPlayerCell.swift */,
F3585C4A2F14FD1000EEC469 /* XSShortDetailPlayerControlView.swift */, F3585C4A2F14FD1000EEC469 /* XSShortDetailPlayerControlView.swift */,
F304E60F2F67A9B600E9B0A6 /* XSShortDetailLockView.swift */,
F3585C3E2F14C83700EEC469 /* XSPlayerEpButton.swift */, F3585C3E2F14C83700EEC469 /* XSPlayerEpButton.swift */,
F355480F2F504D74006F28CD /* XSEpSelectorView.swift */, F355480F2F504D74006F28CD /* XSEpSelectorView.swift */,
F35548112F5129F2006F28CD /* XSEpSelectorCell.swift */, F35548112F5129F2006F28CD /* XSEpSelectorCell.swift */,
@ -761,6 +828,66 @@
path = Empty; path = Empty;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
F3F388DA2F67FDFF001B0E15 /* Store */ = {
isa = PBXGroup;
children = (
F3F388DB2F67FE13001B0E15 /* Controller */,
F3F388DC2F67FE2E001B0E15 /* View */,
F3F388DD2F67FE36001B0E15 /* Model */,
);
path = Store;
sourceTree = "<group>";
};
F3F388DB2F67FE13001B0E15 /* Controller */ = {
isa = PBXGroup;
children = (
F3F388DE2F67FE6D001B0E15 /* XSStoreViewController.swift */,
F3F388F72F692031001B0E15 /* XSWalletViewController.swift */,
F3F388FD2F6931D1001B0E15 /* XSConsumptionRecordsViewController.swift */,
F3F389052F6939EE001B0E15 /* XSRewardCoinsViewController.swift */,
F3F389032F6939C7001B0E15 /* XSOrderRecordsViewController.swift */,
F3F3890B2F695055001B0E15 /* FACoinsRecordViewController.swift */,
F3F3890D2F695068001B0E15 /* FAVipRecordViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
};
F3F388DC2F67FE2E001B0E15 /* View */ = {
isa = PBXGroup;
children = (
F3F388E22F680316001B0E15 /* XSStoreCoinsView.swift */,
F3F388E42F68032A001B0E15 /* XSStoreVipView.swift */,
F3F388F92F69240E001B0E15 /* XSWalletHeaderView.swift */,
F3F388FB2F692CEF001B0E15 /* XSWalletCell.swift */,
F3F388FF2F693293001B0E15 /* XSConsumptionRecordsCell.swift */,
F3F389072F6940BB001B0E15 /* XSRewardCoinsCell.swift */,
);
path = View;
sourceTree = "<group>";
};
F3F388DD2F67FE36001B0E15 /* Model */ = {
isa = PBXGroup;
children = (
F3F388E82F68062F001B0E15 /* XSPayDateModel.swift */,
F3F388E62F6805DE001B0E15 /* XSPayItem.swift */,
F3F389012F693785001B0E15 /* XSBuyRecordsModel.swift */,
F3F389092F694109001B0E15 /* XSSendCoinRecordModel.swift */,
);
path = Model;
sourceTree = "<group>";
};
F3F388EA2F680CC7001B0E15 /* IapManager */ = {
isa = PBXGroup;
children = (
F3F388EB2F680CEE001B0E15 /* XSIapManager.swift */,
F3F388F52F68F746001B0E15 /* XSPayDataRequest.swift */,
F3F388ED2F68E6EB001B0E15 /* XSWaitRestoreModel.swift */,
F3F388F12F68E9BE001B0E15 /* XSIapVerifyModel.swift */,
F3F388F32F68F2CD001B0E15 /* XSIapOrderModel.swift */,
);
path = IapManager;
sourceTree = "<group>";
};
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
@ -898,6 +1025,7 @@
F347D2F32F0A083500786648 /* XSDeviceId.swift in Sources */, F347D2F32F0A083500786648 /* XSDeviceId.swift in Sources */,
F35547E22F3DCB82006F28CD /* UINavigationBar+XS.swift in Sources */, F35547E22F3DCB82006F28CD /* UINavigationBar+XS.swift in Sources */,
F347D30A2F0A162800786648 /* XSNetworkPlugin.swift in Sources */, F347D30A2F0A162800786648 /* XSNetworkPlugin.swift in Sources */,
F3F388EC2F680CEE001B0E15 /* XSIapManager.swift in Sources */,
F347D3082F0A134500786648 /* XSHud.swift in Sources */, F347D3082F0A134500786648 /* XSHud.swift in Sources */,
F347D29E2F03750000786648 /* XSTabBarItemContentView.swift in Sources */, F347D29E2F03750000786648 /* XSTabBarItemContentView.swift in Sources */,
F347D2FE2F0A0D0700786648 /* XSUserDefaultsKey.swift in Sources */, F347D2FE2F0A0D0700786648 /* XSUserDefaultsKey.swift in Sources */,
@ -912,16 +1040,25 @@
F347D30C2F0A36E200786648 /* XSNetworkMonitorManager.swift in Sources */, F347D30C2F0A36E200786648 /* XSNetworkMonitorManager.swift in Sources */,
F347D2D82F04CB0300786648 /* XSLabel.swift in Sources */, F347D2D82F04CB0300786648 /* XSLabel.swift in Sources */,
F35548082F4FD703006F28CD /* XSMineUserInfoView.swift in Sources */, F35548082F4FD703006F28CD /* XSMineUserInfoView.swift in Sources */,
F3F388FC2F692CEF001B0E15 /* XSWalletCell.swift in Sources */,
F347D2E82F0A03AB00786648 /* XSNetworkModel.swift in Sources */, F347D2E82F0A03AB00786648 /* XSNetworkModel.swift in Sources */,
F35547FE2F4EC450006F28CD /* XSMyCollectViewController.swift in Sources */, F35547FE2F4EC450006F28CD /* XSMyCollectViewController.swift in Sources */,
F347D29B2F03740000786648 /* XSConfig.swift in Sources */, F347D29B2F03740000786648 /* XSConfig.swift in Sources */,
F3F388DF2F67FE6D001B0E15 /* XSStoreViewController.swift in Sources */,
F35547F42F3EFC37006F28CD /* XSMyListCollectsCell.swift in Sources */, F35547F42F3EFC37006F28CD /* XSMyListCollectsCell.swift in Sources */,
F347D2A72F03AAD700786648 /* XSViewController.swift in Sources */, F347D2A72F03AAD700786648 /* XSViewController.swift in Sources */,
F3585C342F148F0800EEC469 /* XSHomeViewModel.swift in Sources */, F3585C342F148F0800EEC469 /* XSHomeViewModel.swift in Sources */,
F347D2B02F03AE6700786648 /* XSHomeViewController.swift in Sources */, F347D2B02F03AE6700786648 /* XSHomeViewController.swift in Sources */,
F304E6102F67A9B600E9B0A6 /* XSShortDetailLockView.swift in Sources */,
F3585C3A2F14999700EEC469 /* XSProgressView.swift in Sources */, F3585C3A2F14999700EEC469 /* XSProgressView.swift in Sources */,
F3F389062F6939EE001B0E15 /* XSRewardCoinsViewController.swift in Sources */,
F3585C382F1497AF00EEC469 /* XSDiscoverControlView.swift in Sources */, F3585C382F1497AF00EEC469 /* XSDiscoverControlView.swift in Sources */,
F3F388D52F67DDBC001B0E15 /* XSMineWalletView.swift in Sources */,
F3F388D92F67F9E1001B0E15 /* XSMineVipView.swift in Sources */,
F347D2D42F04BF3D00786648 /* XSHomeNewTitleView.swift in Sources */, F347D2D42F04BF3D00786648 /* XSHomeNewTitleView.swift in Sources */,
F304E6122F67D74A00E9B0A6 /* XSVideoUnlockResult.swift in Sources */,
F3F388FE2F6931D1001B0E15 /* XSConsumptionRecordsViewController.swift in Sources */,
F3F3890C2F695055001B0E15 /* FACoinsRecordViewController.swift in Sources */,
F347D28D2F03709200786648 /* AppDelegate.swift in Sources */, F347D28D2F03709200786648 /* AppDelegate.swift in Sources */,
F3585C492F14EE8D00EEC469 /* XSShortDetailPlayerCell.swift in Sources */, F3585C492F14EE8D00EEC469 /* XSShortDetailPlayerCell.swift in Sources */,
F35548412F52BE6D006F28CD /* XSBaseWebViewController+Script.swift in Sources */, F35548412F52BE6D006F28CD /* XSBaseWebViewController+Script.swift in Sources */,
@ -931,6 +1068,7 @@
F355480E2F50485E006F28CD /* XSPanModalContentView.swift in Sources */, F355480E2F50485E006F28CD /* XSPanModalContentView.swift in Sources */,
F347D2C82F03DBD300786648 /* UIView+XS.swift in Sources */, F347D2C82F03DBD300786648 /* UIView+XS.swift in Sources */,
F355483B2F52BA4A006F28CD /* XSFeedbackViewController.swift in Sources */, F355483B2F52BA4A006F28CD /* XSFeedbackViewController.swift in Sources */,
F3F388F22F68E9BE001B0E15 /* XSIapVerifyModel.swift in Sources */,
F347D2FA2F0A0C5600786648 /* XSLoginManager.swift in Sources */, F347D2FA2F0A0C5600786648 /* XSLoginManager.swift in Sources */,
F35547DC2F3DB33C006F28CD /* XSMineHeaderView.swift in Sources */, F35547DC2F3DB33C006F28CD /* XSMineHeaderView.swift in Sources */,
F347D2B52F03B2B500786648 /* UIFont+XS.swift in Sources */, F347D2B52F03B2B500786648 /* UIFont+XS.swift in Sources */,
@ -939,18 +1077,26 @@
F347D2EA2F0A047A00786648 /* XSNetwork.swift in Sources */, F347D2EA2F0A047A00786648 /* XSNetwork.swift in Sources */,
F347D2A32F03A93200786648 /* XSDefine.swift in Sources */, F347D2A32F03A93200786648 /* XSDefine.swift in Sources */,
F3585C3C2F14BFAB00EEC469 /* XSCustomTabBar.swift in Sources */, F3585C3C2F14BFAB00EEC469 /* XSCustomTabBar.swift in Sources */,
F3F388EE2F68E6EB001B0E15 /* XSWaitRestoreModel.swift in Sources */,
F3585C422F14E99C00EEC469 /* XSShortDetailViewController.swift in Sources */, F3585C422F14E99C00EEC469 /* XSShortDetailViewController.swift in Sources */,
F347D2DE2F04F54400786648 /* XSHomeCategoriesViewController.swift in Sources */, F347D2DE2F04F54400786648 /* XSHomeCategoriesViewController.swift in Sources */,
F347D3012F0A0D8200786648 /* XSUserAPI.swift in Sources */, F347D3012F0A0D8200786648 /* XSUserAPI.swift in Sources */,
F3F388F02F68E737001B0E15 /* XSStoreAPI.swift in Sources */,
F3585C452F14EA0A00EEC469 /* XSShortDetailViewModel.swift in Sources */, F3585C452F14EA0A00EEC469 /* XSShortDetailViewModel.swift in Sources */,
F35547D02F3DA6CA006F28CD /* XSMineViewController.swift in Sources */, F35547D02F3DA6CA006F28CD /* XSMineViewController.swift in Sources */,
F347D2D22F04B97600786648 /* XSHomeNewBigCell.swift in Sources */, F347D2D22F04B97600786648 /* XSHomeNewBigCell.swift in Sources */,
F3F388F42F68F2CD001B0E15 /* XSIapOrderModel.swift in Sources */,
F3F389022F693785001B0E15 /* XSBuyRecordsModel.swift in Sources */,
F35548102F504D74006F28CD /* XSEpSelectorView.swift in Sources */, F35548102F504D74006F28CD /* XSEpSelectorView.swift in Sources */,
F3F388FA2F69240E001B0E15 /* XSWalletHeaderView.swift in Sources */,
F35548122F5129F2006F28CD /* XSEpSelectorCell.swift in Sources */, F35548122F5129F2006F28CD /* XSEpSelectorCell.swift in Sources */,
F3F388E32F680316001B0E15 /* XSStoreCoinsView.swift in Sources */,
F355480C2F50398B006F28CD /* NSNumber+XS.swift in Sources */, F355480C2F50398B006F28CD /* NSNumber+XS.swift in Sources */,
F347D2E02F04F7ED00786648 /* XSHomeCategoriesCell.swift in Sources */, F347D2E02F04F7ED00786648 /* XSHomeCategoriesCell.swift in Sources */,
F3F3890A2F694109001B0E15 /* XSSendCoinRecordModel.swift in Sources */,
F347D3032F0A10B600786648 /* XSCryptorService.swift in Sources */, F347D3032F0A10B600786648 /* XSCryptorService.swift in Sources */,
F35547D42F3DA7A8006F28CD /* XSTableViewCell.swift in Sources */, F35547D42F3DA7A8006F28CD /* XSTableViewCell.swift in Sources */,
F3F388F62F68F746001B0E15 /* XSPayDataRequest.swift in Sources */,
F347D30E2F0A39DE00786648 /* XSHomeAPI.swift in Sources */, F347D30E2F0A39DE00786648 /* XSHomeAPI.swift in Sources */,
F3B312B72F319CBE0093B180 /* XSEmpty.swift in Sources */, F3B312B72F319CBE0093B180 /* XSEmpty.swift in Sources */,
F347D2F62F0A0B0B00786648 /* XSLoginToken.swift in Sources */, F347D2F62F0A0B0B00786648 /* XSLoginToken.swift in Sources */,
@ -958,11 +1104,14 @@
F347D2BE2F03C24B00786648 /* XSCollectionView.swift in Sources */, F347D2BE2F03C24B00786648 /* XSCollectionView.swift in Sources */,
F35547DA2F3DAACB006F28CD /* XSMineItem.swift in Sources */, F35547DA2F3DAACB006F28CD /* XSMineItem.swift in Sources */,
F347D2D62F04C7D500786648 /* XSHomeNewCell.swift in Sources */, F347D2D62F04C7D500786648 /* XSHomeNewCell.swift in Sources */,
F3F388E92F68062F001B0E15 /* XSPayDateModel.swift in Sources */,
F347D2CC2F03E04400786648 /* AppDelegate+Config.swift in Sources */, F347D2CC2F03E04400786648 /* AppDelegate+Config.swift in Sources */,
F347D3202F0A57A300786648 /* XSDiscoverPlayerCell.swift in Sources */, F347D3202F0A57A300786648 /* XSDiscoverPlayerCell.swift in Sources */,
F35548022F4ECD5C006F28CD /* UIScrollView+Refresh.swift in Sources */, F35548022F4ECD5C006F28CD /* UIScrollView+Refresh.swift in Sources */,
F347D2EC2F0A060E00786648 /* XSNetworkTarget.swift in Sources */, F347D2EC2F0A060E00786648 /* XSNetworkTarget.swift in Sources */,
F3F388E12F680090001B0E15 /* XSScrollView.swift in Sources */,
F35547E92F3DDBDD006F28CD /* XSWebView.swift in Sources */, F35547E92F3DDBDD006F28CD /* XSWebView.swift in Sources */,
F3F389042F6939C7001B0E15 /* XSOrderRecordsViewController.swift in Sources */,
F347D31A2F0A545000786648 /* XSDiscoverViewController.swift in Sources */, F347D31A2F0A545000786648 /* XSDiscoverViewController.swift in Sources */,
F355483D2F52BC81006F28CD /* XSSettingAPI.swift in Sources */, F355483D2F52BC81006F28CD /* XSSettingAPI.swift in Sources */,
F3B312A42F30AC9B0093B180 /* XSSearchData.swift in Sources */, F3B312A42F30AC9B0093B180 /* XSSearchData.swift in Sources */,
@ -973,11 +1122,14 @@
F3F683ED2F56C380008AF250 /* XSHomeHistoryView.swift in Sources */, F3F683ED2F56C380008AF250 /* XSHomeHistoryView.swift in Sources */,
F347D2A12F03A84300786648 /* XSScreen.swift in Sources */, F347D2A12F03A84300786648 /* XSScreen.swift in Sources */,
F35548062F4FD6DA006F28CD /* XSMinePlayHistoryView.swift in Sources */, F35548062F4FD6DA006F28CD /* XSMinePlayHistoryView.swift in Sources */,
F3F3890E2F695068001B0E15 /* FAVipRecordViewController.swift in Sources */,
F3F388E72F6805DE001B0E15 /* XSPayItem.swift in Sources */,
F347D2992F03730E00786648 /* XSTabBarController.swift in Sources */, F347D2992F03730E00786648 /* XSTabBarController.swift in Sources */,
F35547D22F3DA757006F28CD /* XSTableView.swift in Sources */, F35547D22F3DA757006F28CD /* XSTableView.swift in Sources */,
F355480A2F4FE99F006F28CD /* XSMinePlayHistoryCell.swift in Sources */, F355480A2F4FE99F006F28CD /* XSMinePlayHistoryCell.swift in Sources */,
F3B312AD2F30ACF60093B180 /* XSSearchHotSectionView.swift in Sources */, F3B312AD2F30ACF60093B180 /* XSSearchHotSectionView.swift in Sources */,
F3B312AE2F30ACF60093B180 /* XSSearchTagsView.swift in Sources */, F3B312AE2F30ACF60093B180 /* XSSearchTagsView.swift in Sources */,
F3F389082F6940BB001B0E15 /* XSRewardCoinsCell.swift in Sources */,
F3B312AF2F30ACF60093B180 /* XSSearchTagCell.swift in Sources */, F3B312AF2F30ACF60093B180 /* XSSearchTagCell.swift in Sources */,
F3B312B02F30ACF60093B180 /* XSSearchRecentView.swift in Sources */, F3B312B02F30ACF60093B180 /* XSSearchRecentView.swift in Sources */,
F35548042F4EDF27006F28CD /* UIStackView+XS.swift in Sources */, F35548042F4EDF27006F28CD /* UIStackView+XS.swift in Sources */,
@ -995,17 +1147,21 @@
F35547EB2F3DDCCE006F28CD /* XSBaseWebViewController.swift in Sources */, F35547EB2F3DDCCE006F28CD /* XSBaseWebViewController.swift in Sources */,
F35547EF2F3EF606006F28CD /* XSMyListViewController.swift in Sources */, F35547EF2F3EF606006F28CD /* XSMyListViewController.swift in Sources */,
F347D2D02F04B5BB00786648 /* XSHomeNewViewController.swift in Sources */, F347D2D02F04B5BB00786648 /* XSHomeNewViewController.swift in Sources */,
F3F389002F693293001B0E15 /* XSConsumptionRecordsCell.swift in Sources */,
F3B312A22F30A7DA0093B180 /* XSSearchViewController.swift in Sources */, F3B312A22F30A7DA0093B180 /* XSSearchViewController.swift in Sources */,
F347D3222F0A58C300786648 /* XSVideoAPI.swift in Sources */, F347D3222F0A58C300786648 /* XSVideoAPI.swift in Sources */,
F347D29F2F03A6B100786648 /* XSTool.swift in Sources */, F347D29F2F03A6B100786648 /* XSTool.swift in Sources */,
F35548372F52B86C006F28CD /* XSAppWebViewController.swift in Sources */, F35548372F52B86C006F28CD /* XSAppWebViewController.swift in Sources */,
F3585C472F14EAE700EEC469 /* XSShortDetailModel.swift in Sources */, F3585C472F14EAE700EEC469 /* XSShortDetailModel.swift in Sources */,
F347D2B32F03AF7500786648 /* XSHomeSearchButton.swift in Sources */, F347D2B32F03AF7500786648 /* XSHomeSearchButton.swift in Sources */,
F3F388D72F67EDEC001B0E15 /* XSMineCoinsPackView.swift in Sources */,
F35547E42F3DCCDE006F28CD /* XSAboutCell.swift in Sources */, F35547E42F3DCCDE006F28CD /* XSAboutCell.swift in Sources */,
F3F388F82F692031001B0E15 /* XSWalletViewController.swift in Sources */,
F347D2C22F03C59B00786648 /* XSWaterfallFlowLayout.swift in Sources */, F347D2C22F03C59B00786648 /* XSWaterfallFlowLayout.swift in Sources */,
F35547DE2F3DBAF6006F28CD /* XSAboutViewController.swift in Sources */, F35547DE2F3DBAF6006F28CD /* XSAboutViewController.swift in Sources */,
F35547D72F3DA8B5006F28CD /* XSMineCell.swift in Sources */, F35547D72F3DA8B5006F28CD /* XSMineCell.swift in Sources */,
F347D2AC2F03ADC800786648 /* String+XS.swift in Sources */, F347D2AC2F03ADC800786648 /* String+XS.swift in Sources */,
F3F388E52F68032A001B0E15 /* XSStoreVipView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -48,6 +48,7 @@ extension AppDelegate {
Task { Task {
await XSLoginManager.manager.updateUserInfo() await XSLoginManager.manager.updateUserInfo()
} }
XSIapManager.manager.preloadingProducts()
} }
} }

View File

@ -9,3 +9,5 @@
let kXSLoginTokenDefaultsKey = "kXSLoginTokenDefaultsKey" let kXSLoginTokenDefaultsKey = "kXSLoginTokenDefaultsKey"
/// ///
let kXSUserInfoDefaultsKey = "kXSUserInfoDefaultsKey" let kXSUserInfoDefaultsKey = "kXSUserInfoDefaultsKey"
let kXSWaitRestoreIAPDefaultsKey = "kXSWaitRestoreIAPDefaultsKey"

View File

@ -14,6 +14,9 @@ class XSTabBarController: ESTabBarController {
super.viewDidLoad() super.viewDidLoad()
self.view.backgroundColor = .black self.view.backgroundColor = .black
///
XSIapManager.manager.preloadingProducts()
setupCustomTabBar() setupCustomTabBar()
setupTabBarAppearance() setupTabBarAppearance()
setupViewControllers() setupViewControllers()

View File

@ -0,0 +1,131 @@
//
// XSStoreAPI.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SmartCodable
import JXIAPManager
struct XSStoreAPI {
enum BuyType: String, SmartCaseDefaultable {
case coins = "coins"
case subVip = "sub_vip"
case subCoins = "sub_coins"
case vip = "vip"
}
///
static func requestCreateOrder(payId: String, shortPlayId: String, videoId: String, isDiscount: Bool = false, identifierDiscount: String? = nil) async -> XSIapOrderModel? {
let parameters: [String : Any] = [
"payment_channel" : "apple",
"short_play_id" : shortPlayId,
"video_id" : videoId,
"pay_setting_id" : payId,
"is_discount" : isDiscount ? 1 : 0,
"product_discount" : identifierDiscount ?? "",
]
var param = XSNetwork.Parameters(path: "/createOrder")
param.method = .post
param.isLoding = false
param.isToast = false
param.parameters = parameters
let response: XSNetwork.Response<XSIapOrderModel> = await XSNetwork.request(parameters: param)
guard let data = response.data else {
XSToast.show("network_error_2".localized)
return nil
}
if let message = data.message, message.count > 0 {
if response.data?.code == 30007 {
XSToast.show("pay_error_1".localized)
} else {
XSToast.show(message)
}
return nil
} else {
return response.data
}
}
///
static func requestVerifyOrder(_ parameters: [String : Any]) async -> XSNetwork.Response<XSIapVerifyModel> {
var param = XSNetwork.Parameters(path: "/applePaid")
param.method = .post
param.isLoding = false
param.parameters = parameters
let response: XSNetwork.Response<XSIapVerifyModel> = await XSNetwork.request(parameters: param)
return response
}
///
static func requestPayTemplate(isLoding: Bool = false, isToast: Bool = true) async -> XSPayDateModel? {
let parameters = [
"discount" : "1",
"purchases_token" : JXIAPManager.manager.getAppStoreReceipt() ?? "",
]
var param = XSNetwork.Parameters(path: "/paySettingsV4")
param.method = .post
param.isLoding = isLoding
param.isToast = isToast
param.parameters = parameters
let response: XSNetwork.Response<XSPayDateModel> = await XSNetwork.request(parameters: param)
return response.data
}
///
static func requestPayRetainInfo() async -> XSPayDateModel? {
let parameters = [
"purchases_token" : JXIAPManager.manager.getAppStoreReceipt() ?? "",
"discount" : "1",
]
var param = XSNetwork.Parameters(path: "/payRetrieveSettings")
param.method = .post
param.isLoding = false
param.isToast = false
param.parameters = parameters
let response: XSNetwork.Response<XSPayDateModel> = await XSNetwork.request(parameters: param)
if (response.data?.list_coins?.count ?? 0) > 0 || (response.data?.list_sub_vip?.count ?? 0) > 0 {
return response.data
} else {
return nil
}
}
static func requestBuyRecords(page: Int) async -> XSNetwork.List<XSBuyRecordsModel>? {
let parameters = [
"page_size" : 20,
"current_page" : page,
]
var param = XSNetwork.Parameters(path: "/getCustomerBuyRecords")
param.method = .get
param.parameters = parameters
let response: XSNetwork.Response<XSNetwork.List<XSBuyRecordsModel>> = await XSNetwork.request(parameters: param)
return response.data
}
static func reuqestSendCoinRecord(page: Int) async -> XSNetwork.List<XSSendCoinRecordModel>? {
let parameters = [
"page_size" : 20,
"current_page" : page,
]
var param = XSNetwork.Parameters(path: "/sendCoinList")
param.method = .post
param.parameters = parameters
let response: XSNetwork.Response<XSNetwork.List<XSSendCoinRecordModel>> = await XSNetwork.request(parameters: param)
return response.data
}
}

View File

@ -105,6 +105,23 @@ struct XSVideoAPI {
return nil return nil
} }
} }
static func requestCoinUnlockVideo(shortPlayId: String, videoId: String) async -> XSVideoUnlockResult? {
let parameters: [String : Any] = [
"short_play_id" : shortPlayId,
"video_id" : videoId,
]
var param = XSNetwork.Parameters(path: "/buy_video")
param.method = .post
param.isLoding = true
param.parameters = parameters
let response: XSNetwork.Response<XSVideoUnlockResult> = await XSNetwork.request(parameters: param)
if response.isSuccess {
return response.data
} else {
return nil
}
}
} }
extension XSVideoAPI { extension XSVideoAPI {

View File

@ -0,0 +1,21 @@
//
// XSScrollView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
class XSScrollView: UIScrollView {
override init(frame: CGRect) {
super.init(frame: frame)
self.contentInsetAdjustmentBehavior = .never
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -25,6 +25,7 @@ class XSMineViewController: XSCommonViewController {
tableView.dataSource = self tableView.dataSource = self
tableView.separatorStyle = .none tableView.separatorStyle = .none
tableView.rowHeight = 44 tableView.rowHeight = 44
tableView.contentInset = .init(top: 0, left: 0, bottom: XSScreen.customTabBarHeight, right: 0)
tableView.xs_addRefreshHeader { [weak self] in tableView.xs_addRefreshHeader { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.handleHeaderRefresh(nil) self.handleHeaderRefresh(nil)

View File

@ -15,6 +15,12 @@ struct XSMineItem {
case feedback case feedback
case web case web
case safari case safari
///
case consumptionRecords
///
case purchaseRecords
///
case rewardCoins
} }

View File

@ -0,0 +1,93 @@
//
// XSMineCoinsPackView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SnapKit
import YYCategories
class XSMineCoinsPackView: UIView {
private lazy var bgView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "coins_pack_bg_image_01"))
imageView.isUserInteractionEnabled = true
imageView.layer.cornerRadius = 12
imageView.layer.masksToBounds = true
return imageView
}()
private lazy var giftsIconImageView = UIImageView(image: UIImage(named: "gifts_icon_01"))
private lazy var titleLabel: XSLabel = {
let label = XSLabel()
label.textColors = [UIColor.F_6564_A.cgColor, UIColor.F_98_C_36.cgColor]
label.textStartPoint = .init(x: 0, y: 0.5)
label.textEndPoint = .init(x: 1, y: 0.5)
label.font = .font(ofSize: 16, weight: .bold).withBoldItalic()
label.text = "me_daily_1".localized
return label
}()
private lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .regular).withBoldItalic()
label.textColor = ._523927
label.text = "me_daily_2".localized
return label
}()
private lazy var indicatorImageView = UIImageView(image: UIImage(named: "arrow_right_icon_05"))
override init(frame: CGRect) {
super.init(frame: frame)
xs_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSMineCoinsPackView {
private func xs_setupUI() {
addSubview(bgView)
bgView.addSubview(giftsIconImageView)
bgView.addSubview(titleLabel)
bgView.addSubview(subtitleLabel)
bgView.addSubview(indicatorImageView)
bgView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.height.equalTo(60)
make.top.bottom.equalToSuperview()
}
giftsIconImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.centerY.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(12)
make.left.equalTo(giftsIconImageView.snp.right).offset(8)
}
subtitleLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.bottom.equalToSuperview().offset(-12)
}
indicatorImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-12)
}
}
}

View File

@ -13,6 +13,8 @@ class XSMineHeaderView: UITableViewHeaderFooterView {
var userInfo: XSUserInfo? { var userInfo: XSUserInfo? {
didSet { didSet {
userInfoView.userInfo = userInfo userInfoView.userInfo = userInfo
walletView.userInfo = userInfo
vipView.userInfo = userInfo
} }
} }
@ -41,6 +43,21 @@ class XSMineHeaderView: UITableViewHeaderFooterView {
return view return view
}() }()
private lazy var walletView: XSMineWalletView = {
let view = XSMineWalletView()
return view
}()
private lazy var coinsPackView: XSMineCoinsPackView = {
let view = XSMineCoinsPackView()
return view
}()
private lazy var vipView: XSMineVipView = {
let view = XSMineVipView()
return view
}()
override init(reuseIdentifier: String?) { override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier) super.init(reuseIdentifier: reuseIdentifier)
@ -55,6 +72,10 @@ class XSMineHeaderView: UITableViewHeaderFooterView {
stackView.xs_removeAllArrangedSubview() stackView.xs_removeAllArrangedSubview()
stackView.addArrangedSubview(userInfoView) stackView.addArrangedSubview(userInfoView)
stackView.addArrangedSubview(walletView)
stackView.addArrangedSubview(coinsPackView)
stackView.addArrangedSubview(vipView)
if !historyDataArr.isEmpty { if !historyDataArr.isEmpty {
stackView.addArrangedSubview(historyView) stackView.addArrangedSubview(historyView)

View File

@ -0,0 +1,55 @@
//
// XSMineVipView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SnapKit
class XSMineVipView: UIView {
override var intrinsicContentSize: CGSize {
return .init(width: XSScreen.width, height: 15)
}
var userInfo: XSUserInfo? {
didSet {
titleLabel.text = "VIP Expiration Time2023-11-23"
}
}
private lazy var iconImageView = UIImageView(image: UIImage(named: "time_icon_01"))
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .regular)
label.textColor = .FFDAA_4
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(iconImageView)
addSubview(titleLabel)
iconImageView.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.left.equalToSuperview().offset(16)
}
titleLabel.snp.makeConstraints { make in
make.left.equalTo(iconImageView.snp.right).offset(5)
make.top.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -0,0 +1,248 @@
//
// XSMineWalletView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SnapKit
class XSMineWalletView: UIView {
override var intrinsicContentSize: CGSize {
return .init(width: XSScreen.width, height: 36 + 87)
}
var userInfo: XSUserInfo? {
didSet {
coinsView.coins = userInfo?.coin_left_total
sendCoinsView.coins = userInfo?.send_coin_left_total
}
}
private lazy var contentView: UIView = {
let view = UIView()
view.layer.cornerRadius = 12
view.layer.masksToBounds = true
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.white.withAlphaComponent(0.25).cgColor
return view
}()
private lazy var titleBgView: UIView = {
let view = UIImageView(image: UIImage(named: "wallet_bg_image_01"))
view.isUserInteractionEnabled = true
view.layer.cornerRadius = 12
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
view.layer.masksToBounds = true
view.layer.borderColor = UIColor.FFDAA_4.cgColor
view.layer.borderWidth = 1
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .bold)
label.textColor = .white
label.text = "My Wallet".localized
return label
}()
private lazy var walletButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.contentInsets = .zero
configuration.imagePadding = 4
configuration.imagePlacement = .trailing
configuration.image = UIImage(named: "arrow_right_icon_04")
configuration.attributedTitle = AttributedString("Details".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 12, weight: .regular),
.foregroundColor : UIColor.white.withAlphaComponent(0.5)
]))
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
let vc = XSWalletViewController()
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}))
return button
}()
private lazy var storeButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.background.image = UIImage(named: "gradient_color_image_01")
configuration.background.cornerRadius = 18
configuration.attributedTitle = AttributedString("Top Up".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 14, weight: .semibold),
.foregroundColor : UIColor._482_B_00
]))
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
let vc = XSStoreViewController()
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}))
return button
}()
private lazy var coinsView: CoinsView = {
let view = CoinsView()
view.title = "Coins".localized
return view
}()
private lazy var coinsLineView: UIView = {
let view = UIView()
view.backgroundColor = .white.withAlphaComponent(0.1)
return view
}()
private lazy var sendCoinsView: CoinsView = {
let view = CoinsView()
view.title = "Bonus".localized
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
xs_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSMineWalletView {
private func xs_setupUI() {
addSubview(contentView)
addSubview(titleBgView)
titleBgView.addSubview(titleLabel)
titleBgView.addSubview(walletButton)
contentView.addSubview(storeButton)
contentView.addSubview(coinsView)
contentView.addSubview(coinsLineView)
contentView.addSubview(sendCoinsView)
contentView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.bottom.equalToSuperview()
}
titleBgView.snp.makeConstraints { make in
make.left.right.top.equalTo(contentView)
make.height.equalTo(36)
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(12)
}
walletButton.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.right.equalToSuperview().offset(-12)
}
storeButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-12)
make.width.equalTo(108)
make.height.equalTo(36)
make.bottom.equalToSuperview().offset(-26)
}
coinsView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.centerY.equalTo(storeButton)
}
coinsLineView.snp.makeConstraints { make in
make.centerY.equalTo(storeButton)
make.left.equalTo(coinsView.snp.right).offset(25)
make.width.equalTo(1)
make.height.equalTo(36)
}
sendCoinsView.snp.makeConstraints { make in
make.left.equalTo(coinsLineView.snp.right).offset(25)
make.centerY.equalTo(storeButton)
}
}
}
extension XSMineWalletView {
private class CoinsView: UIView {
var title: String? {
set {
titleLabel.text = newValue
}
get {
return titleLabel.text
}
}
var coins: Int? {
didSet {
coinsLabel.text = "\(coins ?? 0)"
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .bold)
label.textColor = .white.withAlphaComponent(0.5)
return label
}()
private lazy var coinsImageView = UIImageView(image: UIImage(named: "coins_icon_01"))
private lazy var coinsLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 18, weight: .regular)
label.textColor = .FFDAA_4
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
addSubview(coinsImageView)
addSubview(coinsLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview()
make.top.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
}
coinsImageView.snp.makeConstraints { make in
make.left.equalToSuperview()
make.bottom.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(6)
}
coinsLabel.snp.makeConstraints { make in
make.centerY.equalTo(coinsImageView)
make.left.equalTo(coinsImageView.snp.right).offset(6)
make.right.lessThanOrEqualToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -72,12 +72,32 @@ class XSShortDetailViewController: JXPlayerListViewController {
} }
override func play() { override func play() {
super.play()
let videoInfo = self.xs_viewModel.currentCell?.model as? XSVideoInfoModel let videoInfo = self.xs_viewModel.currentCell?.model as? XSVideoInfoModel
Task { if videoInfo?.is_lock != true {
await XSVideoAPI.requestAddPlayHistory(shortPlayId: videoInfo?.short_play_id, videoId: videoInfo?.short_play_video_id) super.play()
Task {
await XSVideoAPI.requestAddPlayHistory(shortPlayId: videoInfo?.short_play_id, videoId: videoInfo?.short_play_video_id)
}
return
} }
self.pause()
let myCoins = XSLoginManager.manager.userInfo?.totalCoins ?? 0
let lockCoins = videoInfo?.coins ?? 0
if myCoins < lockCoins, (self.xs_viewModel.currentCell as? XSShortDetailPlayerCell)?.hasLastEpisodeUnlocked != true {
self.xs_viewModel.openRechargeView()
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let self = self else { return }
if (self.xs_viewModel.currentCell as? XSShortDetailPlayerCell)?.hasLastEpisodeUnlocked != true {
self.xs_viewModel.handleUnlockVideo()
}
}
}
} }
} }

View File

@ -0,0 +1,25 @@
//
// XSVideoUnlockResult.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SmartCodable
struct XSVideoUnlockResult: SmartCodable {
enum Status: String, SmartCaseDefaultable {
///
case jump = "jump"
///
case noPlay = "no_play"
///
case notEnough = "not_enough"
///
case success = "success"
}
var status: Status?
}

View File

@ -14,6 +14,7 @@ class XSEpSelectorCell: UICollectionViewCell {
var model: XSVideoInfoModel? { var model: XSVideoInfoModel? {
didSet { didSet {
epLabel.text = model?.episode epLabel.text = model?.episode
lockImageView.isHidden = !(model?.is_lock ?? false)
} }
} }
@ -51,6 +52,11 @@ class XSEpSelectorCell: UICollectionViewCell {
private lazy var playImageView = UIImageView(image: UIImage(named: "play_icon_02")) private lazy var playImageView = UIImageView(image: UIImage(named: "play_icon_02"))
private lazy var lockImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "lock_icon_01"))
return imageView
}()
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
xs_setupUI() xs_setupUI()
@ -66,8 +72,9 @@ extension XSEpSelectorCell {
private func xs_setupUI() { private func xs_setupUI() {
contentView.addSubview(bgView) contentView.addSubview(bgView)
contentView.addSubview(epLabel) bgView.addSubview(epLabel)
contentView.addSubview(playImageView) bgView.addSubview(playImageView)
bgView.addSubview(lockImageView)
bgView.snp.makeConstraints { make in bgView.snp.makeConstraints { make in
make.edges.equalToSuperview() make.edges.equalToSuperview()
@ -81,6 +88,10 @@ extension XSEpSelectorCell {
make.left.equalToSuperview().offset(6) make.left.equalToSuperview().offset(6)
make.bottom.equalToSuperview().offset(-5) make.bottom.equalToSuperview().offset(-5)
} }
lockImageView.snp.makeConstraints { make in
make.right.top.equalToSuperview()
}
} }
} }

View File

@ -0,0 +1,84 @@
//
// XSShortDetailLockView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SnapKit
class XSShortDetailLockView: UIView {
var clickUnlockButton: (() -> Void)?
var videoInfo: XSVideoInfoModel? {
didSet {
unlockButton.setNeedsUpdateConfiguration()
}
}
var hasLastEpisodeUnlocked = false {
didSet {
unlockButton.setNeedsUpdateConfiguration()
}
}
private lazy var lockIconImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "lock_icon_03"))
return imageView
}()
private lazy var unlockButton: UIButton = {
var config = UIButton.Configuration.plain()
config.background.image = UIImage(named: "button_bg_image_01")
config.image = UIImage(named: "lock_icon_02")
config.imagePadding = 8
let button = UIButton(configuration: config, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
self.clickUnlockButton?()
}))
button.configurationUpdateHandler = { [weak self] button in
guard let self = self else { return }
let attributeContainer = AttributeContainer([
.font : UIFont.font(ofSize: 16, weight: .medium),
.foregroundColor : UIColor._712_A_03
])
if hasLastEpisodeUnlocked {
button.configuration?.attributedTitle = .init("video_lock_tip_text".localized, attributes: attributeContainer)
} else {
button.configuration?.attributedTitle = .init("unlocking_coins_notice".localizedReplace(text: "\(videoInfo?.coins ?? 0)"), attributes: attributeContainer)
}
}
button.layer.cornerRadius = 27
button.layer.masksToBounds = true
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .black.withAlphaComponent(0.6)
addSubview(unlockButton)
addSubview(lockIconImageView)
unlockButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.left.equalToSuperview().offset(18)
make.height.equalTo(54)
make.centerY.equalToSuperview().offset(30)
}
lockIconImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(unlockButton.snp.top).offset(-34)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View File

@ -7,6 +7,7 @@
import UIKit import UIKit
import JXPlayer import JXPlayer
import SnapKit
class XSShortDetailPlayerCell: JXPlayerListCell { class XSShortDetailPlayerCell: JXPlayerListCell {
@ -20,8 +21,8 @@ class XSShortDetailPlayerCell: JXPlayerListCell {
let model = self.model as? XSVideoInfoModel let model = self.model as? XSVideoInfoModel
self.player.setPlayUrl(url: model?.video_url ?? "") self.player.setPlayUrl(url: model?.video_url ?? "")
// self.lockView.isHidden = !(model?.is_lock ?? true) self.lockView.isHidden = !(model?.is_lock ?? true)
// lockView.videoInfo = model lockView.videoInfo = model
} }
} }
@ -35,11 +36,32 @@ class XSShortDetailPlayerCell: JXPlayerListCell {
var hasLastEpisodeUnlocked: Bool = false { var hasLastEpisodeUnlocked: Bool = false {
didSet { didSet {
// self.lockView.hasLastEpisodeUnlocked = hasLastEpisodeUnlocked self.lockView.hasLastEpisodeUnlocked = hasLastEpisodeUnlocked
} }
} }
var xs_controlView: XSShortDetailPlayerControlView { var xs_controlView: XSShortDetailPlayerControlView {
return self.controlView as! XSShortDetailPlayerControlView return self.controlView as! XSShortDetailPlayerControlView
} }
private lazy var lockView: XSShortDetailLockView = {
let view = XSShortDetailLockView()
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(lockView)
lockView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
} }

View File

@ -94,4 +94,53 @@ extension XSShortDetailViewModel {
self.popView = view self.popView = view
} }
///
func handleUnlockVideo() {
Task {
if await self.unlockVideo() {
await self.playerListVC?.reloadData { [weak self] in
self?.playerListVC?.play()
}
}
}
}
///
func openRechargeView() {
}
}
extension XSShortDetailViewModel {
private func unlockVideo() async -> Bool {
guard let videoInfo = self.currentCell?.model as? XSVideoInfoModel else { return false }
guard let shortPlayId = videoInfo.short_play_id else { return false }
guard let videoId = videoInfo.short_play_video_id else { return false }
guard let model = await XSVideoAPI.requestCoinUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) else { return false }
switch model.status {
case .jump:
XSToast.show("buy_fail_toast_02".localized)
case .noPlay:
XSToast.show("buy_fail_toast_01".localized)
case .notEnough:
self.openRechargeView()
default: break
}
if model.status == .success {
await XSLoginManager.manager.updateUserInfo()
videoInfo.is_lock = false
XSToast.show("Success".localized)
return true
} else {
return false
}
}
} }

View File

@ -0,0 +1,29 @@
//
// FACoinsRecordViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
class FACoinsRecordViewController: XSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

View File

@ -0,0 +1,29 @@
//
// FAVipRecordViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
class FAVipRecordViewController: XSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

View File

@ -0,0 +1,123 @@
//
// XSConsumptionRecordsViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSConsumptionRecordsViewController: XSViewController {
private lazy var dataArr: [XSBuyRecordsModel] = []
private lazy var page = 1
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
return imageView
}()
private lazy var tableView: XSTableView = {
let tableView = XSTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 71
tableView.separatorColor = .white.withAlphaComponent(0.1)
tableView.separatorInset = .init(top: 0, left: 32, bottom: 0, right: 32)
tableView.contentInset = .init(top: 10, left: 0, bottom: XSScreen.safeBottom + 10, right: 0)
tableView.register(XSConsumptionRecordsCell.self, forCellReuseIdentifier: "cell")
tableView.ly_emptyView = XSEmpty.xs_emptyView()
tableView.xs_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.xs_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Consumption Records".localized
self.view.backgroundColor = .black
xs_setupUI()
Task {
await requestDataArr(page: 1)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
xs_setNavigationStyle()
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
Task {
await requestDataArr(page: 1)
self.tableView.xs_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
Task {
await requestDataArr(page: self.page + 1)
self.tableView.xs_endFooterRefreshing()
}
}
}
extension XSConsumptionRecordsViewController {
private func xs_setupUI() {
view.addSubview(bgImageView)
view.addSubview(tableView)
bgImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
tableView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide)
}
}
}
//MARK: UITableViewDelegate UITableViewDataSource
extension XSConsumptionRecordsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! XSConsumptionRecordsCell
cell.model = dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArr.count
}
}
extension XSConsumptionRecordsViewController {
private func requestDataArr(page: Int) async {
guard let listModel = await XSStoreAPI.requestBuyRecords(page: page) else { return }
guard let list = listModel.list else { return }
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.tableView.reloadData()
}
}

View File

@ -0,0 +1,104 @@
//
// XSOrderRecordsViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
import JXSegmentedView
class XSOrderRecordsViewController: XSViewController {
private lazy var viewControllers: [XSViewController] = {
return [FACoinsRecordViewController(), FAVipRecordViewController()]
}()
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
return imageView
}()
/// Segmented
private lazy var segmentedDataSource: JXSegmentedTitleDataSource = {
let dataSource = JXSegmentedTitleDataSource()
dataSource.titles = ["Coin Record".localized, "VIP Record".localized]
dataSource.titleNormalColor = UIColor.white.withAlphaComponent(0.55)
dataSource.titleSelectedColor = .white
dataSource.titleNormalFont = .font(ofSize: 16, weight: .medium)
dataSource.titleSelectedFont = .font(ofSize: 16, weight: .semibold)
dataSource.isTitleColorGradientEnabled = true
dataSource.itemSpacing = 24
return dataSource
}()
/// Segmented (Tab )
private lazy var segmentedView: JXSegmentedView = {
let view = JXSegmentedView()
view.dataSource = segmentedDataSource
view.delegate = self
view.listContainer = listContainerView
view.contentEdgeInsetLeft = 16
view.contentEdgeInsetRight = 16
view.backgroundColor = .clear
return view
}()
/// ()
private lazy var listContainerView: JXSegmentedListContainerView = {
return JXSegmentedListContainerView(dataSource: self)
}()
override func viewDidLoad() {
super.viewDidLoad()
xs_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
xs_setNavigationStyle()
}
}
extension XSOrderRecordsViewController {
private func xs_setupUI() {
view.addSubview(bgImageView)
view.addSubview(listContainerView)
bgImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
listContainerView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide)
}
}
}
// MARK: - JXSegmentedViewDelegate
extension XSOrderRecordsViewController: JXSegmentedViewDelegate {
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
}
}
// MARK: - JXSegmentedListContainerViewDataSource
extension XSOrderRecordsViewController: JXSegmentedListContainerViewDataSource {
func numberOfLists(in listContainerView: JXSegmentedListContainerView) -> Int {
return segmentedDataSource.titles.count
}
func listContainerView(_ listContainerView: JXSegmentedListContainerView, initListAt index: Int) -> JXSegmentedListContainerViewListDelegate {
return viewControllers[index]
}
}

View File

@ -0,0 +1,124 @@
//
// XSRewardCoinsViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSRewardCoinsViewController: XSViewController {
private lazy var dataArr: [XSSendCoinRecordModel] = []
private lazy var page = 1
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
return imageView
}()
private lazy var tableView: XSTableView = {
let tableView = XSTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 96
tableView.separatorColor = .white.withAlphaComponent(0.1)
tableView.separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16)
tableView.contentInset = .init(top: 10, left: 0, bottom: XSScreen.safeBottom + 10, right: 0)
tableView.register(XSRewardCoinsCell.self, forCellReuseIdentifier: "cell")
tableView.ly_emptyView = XSEmpty.xs_emptyView()
tableView.xs_addRefreshHeader(insetTop: tableView.contentInset.top) { [weak self] in
self?.handleHeaderRefresh(nil)
}
tableView.xs_addRefreshFooter(insetBottom: tableView.contentInset.bottom) { [weak self] in
self?.handleFooterRefresh(nil)
}
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Reward Coins".localized
self.view.backgroundColor = .black
xs_setupUI()
Task {
await requestDataArr(page: 1)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
xs_setNavigationStyle()
}
override func handleHeaderRefresh(_ completer: (() -> Void)?) {
Task {
await requestDataArr(page: 1)
self.tableView.xs_endHeaderRefreshing()
}
}
override func handleFooterRefresh(_ completer: (() -> Void)?) {
Task {
await requestDataArr(page: self.page + 1)
self.tableView.xs_endFooterRefreshing()
}
}
}
extension XSRewardCoinsViewController {
private func xs_setupUI() {
view.addSubview(bgImageView)
view.addSubview(tableView)
bgImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
tableView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide).offset(10)
}
}
}
//MARK: UITableViewDelegate UITableViewDataSource
extension XSRewardCoinsViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! XSRewardCoinsCell
cell.model = dataArr[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArr.count
}
}
extension XSRewardCoinsViewController {
private func requestDataArr(page: Int) async {
guard let listModel = await XSStoreAPI.reuqestSendCoinRecord(page: page) else { return }
guard let list = listModel.list else { return }
if page == 1 {
self.dataArr.removeAll()
}
self.dataArr += list
self.page = page
self.tableView.reloadData()
}
}

View File

@ -0,0 +1,60 @@
//
// XSStoreViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
class XSStoreViewController: XSViewController {
private lazy var scrollView: XSScrollView = {
let scrollView = XSScrollView()
return scrollView
}()
private lazy var stackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 12
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Store".localized
xs_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
xs_setNavigationStyle()
}
}
extension XSStoreViewController {
private func xs_setupUI() {
view.addSubview(scrollView)
scrollView.addSubview(stackView)
scrollView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalToSuperview().offset(XSScreen.navBarHeight)
}
stackView.snp.makeConstraints { make in
make.left.centerX.equalToSuperview()
make.top.equalToSuperview().offset(12)
make.bottom.equalToSuperview().offset(-(XSScreen.safeBottom + 10))
}
}
}

View File

@ -0,0 +1,135 @@
//
// XSWalletViewController.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSWalletViewController: XSViewController {
private lazy var dataArr: [XSMineItem] = [
XSMineItem(type: .consumptionRecords, iconImage: UIImage(named: "order_icon_01"), title: "Consumption records".localized),
XSMineItem(type: .purchaseRecords, iconImage: UIImage(named: "order_icon_02"), title: "Purchase records".localized),
XSMineItem(type: .rewardCoins, iconImage: UIImage(named: "order_icon_03"), title: "Reward Coins".localized),
XSMineItem(type: .feedback, iconImage: UIImage(named: "feedback_icon_03"), title: "feedback".localized),
]
private lazy var bgImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "bg_image_01"))
return imageView
}()
private lazy var headerView: XSWalletHeaderView = {
let view = XSWalletHeaderView()
view.userInfo = XSLoginManager.manager.userInfo
return view
}()
private lazy var tableView: XSTableView = {
let tableView = XSTableView(frame: .zero, style: .plain)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 52
tableView.separatorInset = .init(top: 0, left: 16, bottom: 0, right: 16)
tableView.contentInset = .init(top: 0, left: 0, bottom: XSScreen.safeBottom, right: 0)
tableView.register(XSWalletCell.self, forCellReuseIdentifier: "cell")
return tableView
}()
deinit {
NotificationCenter.default.removeObserver(self)
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Details".localized
NotificationCenter.default.addObserver(self, selector: #selector(loginStateDidChangeNotification), name: XSLoginManager.loginStateDidChangeNotification, object: nil)
xs_setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: true)
xs_setNavigationStyle()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Task {
await XSLoginManager.manager.updateUserInfo()
}
}
@objc private func loginStateDidChangeNotification() {
headerView.userInfo = XSLoginManager.manager.userInfo
}
}
extension XSWalletViewController {
private func xs_setupUI() {
view.addSubview(bgImageView)
view.addSubview(headerView)
view.addSubview(tableView)
bgImageView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
}
headerView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.top.equalTo(self.view.safeAreaLayoutGuide).offset(10)
}
tableView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(headerView.snp.bottom).offset(18)
}
}
}
//MARK: UITableViewDelegate & UITableViewDataSource
extension XSWalletViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let item = self.dataArr[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! XSWalletCell
cell.item = item
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArr.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = self.dataArr[indexPath.row]
var vc: UIViewController?
switch item.type {
case .consumptionRecords:
vc = XSConsumptionRecordsViewController()
case .purchaseRecords:
vc = XSOrderRecordsViewController()
case .rewardCoins:
vc = XSRewardCoinsViewController()
default:
break
}
if let vc = vc {
self.navigationController?.pushViewController(vc, animated: true)
}
}
}

View File

@ -0,0 +1,21 @@
//
// XSBuyRecordsModel.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SmartCodable
struct XSBuyRecordsModel: SmartCodable {
var short_play_video_id: String?
var created_at: String?
var short_play_id: String?
var coins: Int?
var image_url: String?
var name: String?
var episode: String?
var coin_type: Int?
}

View File

@ -0,0 +1,46 @@
//
// XSPayDateModel.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SmartCodable
class XSPayDateModel: NSObject, SmartCodable {
required override init() {
super.init()
}
enum SortName: String, SmartCaseDefaultable {
case coin = "list_coins"
case vip = "list_sub_vip"
}
var list_coins: [XSPayItem]?
var list_sub_vip: [XSPayItem]?
var list_sub_coins: [XSPayItem]?
var sort: [SortName]?
///0: 1
var pay_mode: Int?
///0: 1
var show_type: Int?
var retrieve_lang: XSPayRetrieveLang?
}
class XSPayRetrieveLang: NSObject, SmartCodable {
required override init() {
super.init()
}
var title: String?
var remaining_time: String?
var miss_out: String?
var subtitle: String?
var claim_reward: String?
}

View File

@ -0,0 +1,112 @@
//
// XSPayItem.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SmartCodable
import StoreKit
class XSPayItem: NSObject, SmartCodable {
required override init() {
super.init()
}
enum VipTypeKey: String, SmartCaseDefaultable {
case week = "week"
case month = "month"
case quarter = "quarter"
case year = "year"
}
enum SizeType: String, SmartCaseDefaultable {
case big = "big"
case small = "small"
case spread = "spread"
}
var id: String?
var backhaul_price: String?
var coins: Int?
var status: String?
var price: String?
var send_coins: Int?
var buy_type: XSStoreAPI.BuyType?
var origin_price: String?
var brief: String?
var title: String?
var auto_sub: String?
var vip_type: String?
var vip_type_key: VipTypeKey?
var sort: String?
var xs_description: String?
var send_coin_ttl: Int?
var size: SizeType?
var ios_template_id: String?
///
var corner_marker: String?
///
var platform: String?
///
var currency: String?
var ext_info: XSPayExtInfo?
///0 1 2
var discount_type: Int?
@IgnoredKey
var product: SKProduct?
///
var introductionaryOffer: SKProductDiscount? {
return product?.introductoryPrice
}
///
var promotionalOffers: [SKProductDiscount]? {
return product?.discounts
}
var discount_price: String? {
var price: String? = nil
if self.discount_type == 1, let introductoryPrice = self.introductionaryOffer {
price = introductoryPrice.price.stringValue
} else if self.discount_type == 2, let discount = self.promotionalOffers?.first {
price = discount.price.stringValue
}
return price
}
static func mappingForKey() -> [SmartKeyTransformer]? {
return [
CodingKeys.xs_description <--- ["description"]
]
}
}
class XSPayExtInfo: NSObject, SmartCodable {
required override init() {
super.init()
}
var extra_day_coins: Int?
var receive_coins_rate: String?
var max_total_coins: Int?
var max_total_coins_pop: Int?
var sub_coins_txt_list: [String]?
}

View File

@ -0,0 +1,21 @@
//
// XSSendCoinRecordModel.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SmartCodable
struct XSSendCoinRecordModel: SmartCodable {
var id: String?
var diff_datetime: String?
var expired_time: TimeInterval?
var coins: Int?
var type: String?
var left_coins: String?
var created_at: String?
var is_effective: Int?
}

View File

@ -0,0 +1,96 @@
//
// XSConsumptionRecordsCell.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSConsumptionRecordsCell: XSTableViewCell {
var model: XSBuyRecordsModel? {
didSet {
subtitleLabel.text = "EP.##".localizedReplace(text: "\(model?.episode ?? "")") + " " + "\(model?.name ?? "")"
dateLabel.text = model?.created_at
coinsLabel.text = "-\(model?.coins ?? 0)" + "Coins".localized
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .FFDAA_4
label.text = "Purchase Single Episode".localized
return label
}()
private lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .regular)
label.textColor = .white
label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentCompressionResistancePriority(.required, for: .horizontal)
return label
}()
private lazy var dateLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .white
return label
}()
private lazy var coinsLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .FFDAA_4
label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentCompressionResistancePriority(.required, for: .horizontal)
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
xs_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSConsumptionRecordsCell {
private func xs_setupUI() {
contentView.addSubview(titleLabel)
contentView.addSubview(subtitleLabel)
contentView.addSubview(dateLabel)
contentView.addSubview(coinsLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(32)
make.top.equalToSuperview().offset(16)
make.right.lessThanOrEqualTo(dateLabel.snp.left).offset(-10)
}
subtitleLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.bottom.equalToSuperview().offset(-16)
make.right.lessThanOrEqualTo(coinsLabel.snp.left).offset(-20)
}
dateLabel.snp.makeConstraints { make in
make.centerY.equalTo(titleLabel)
make.right.equalToSuperview().offset(-36)
}
coinsLabel.snp.makeConstraints { make in
make.right.equalTo(dateLabel)
make.centerY.equalTo(subtitleLabel)
}
}
}

View File

@ -0,0 +1,142 @@
//
// XSRewardCoinsCell.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSRewardCoinsCell: XSTableViewCell {
var model: XSSendCoinRecordModel? {
didSet {
nameLabel.text = model?.type
dateLabel.text = model?.created_at
countLabel.text = "+\(model?.coins ?? 0)"
remainingLabel.text = model?.left_coins
if model?.is_effective == 1 {
expiresIconImageView.isHidden = false
expiresLabel.isHidden = false
expiredLabel.isHidden = true
expiresLabel.text = "Expires in ## days".localizedReplace(text: model?.diff_datetime ?? "")
} else {
expiresIconImageView.isHidden = true
expiresLabel.isHidden = true
expiredLabel.isHidden = false
}
}
}
private lazy var dateLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .medium)
label.textColor = .white
return label
}()
private lazy var nameLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .semibold)
label.textColor = .white
return label
}()
private lazy var expiresIconImageView = UIImageView(image: UIImage(named: "expires_icon_01"))
private lazy var expiresLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .regular)
label.textColor = .FFDAA_4
return label
}()
private lazy var expiredLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .white
label.text = "Expired".localized
return label
}()
private lazy var countLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .semibold)
label.textColor = .FFDAA_4
return label
}()
private lazy var remainingLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 12, weight: .medium)
label.textColor = .white
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.dateLabel.text = "2024-6-10 23:41:18"
self.nameLabel.text = "Check in"
self.countLabel.text = "+50"
self.remainingLabel.text = "Remaining0"
xs_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSRewardCoinsCell {
private func xs_setupUI() {
contentView.addSubview(dateLabel)
contentView.addSubview(nameLabel)
contentView.addSubview(expiresIconImageView)
contentView.addSubview(expiresLabel)
contentView.addSubview(expiredLabel)
contentView.addSubview(countLabel)
contentView.addSubview(remainingLabel)
dateLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(32)
make.top.equalTo(17)
}
nameLabel.snp.makeConstraints { make in
make.left.equalTo(dateLabel)
make.top.equalTo(dateLabel.snp.bottom).offset(6)
}
expiresIconImageView.snp.makeConstraints { make in
make.left.equalTo(dateLabel)
make.bottom.equalToSuperview().offset(-17)
}
expiresLabel.snp.makeConstraints { make in
make.centerY.equalTo(expiresIconImageView)
make.left.equalTo(expiresIconImageView.snp.right).offset(4)
}
expiredLabel.snp.makeConstraints { make in
make.left.equalTo(expiresIconImageView)
make.centerY.equalTo(expiresIconImageView)
}
countLabel.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-32)
make.top.equalToSuperview().offset(28)
}
remainingLabel.snp.makeConstraints { make in
make.right.equalTo(countLabel)
make.centerY.equalTo(expiresIconImageView)
}
}
}

View File

@ -0,0 +1,77 @@
//
// XSStoreCoinsView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import SnapKit
class XSStoreCoinsView: UIView {
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .bold)
label.textColor = .white
label.text = "Coins Purchase".localized
return label
}()
// private lazy var collectionViewLayout: UICollectionViewCompositionalLayout = {
// let config = UICollectionViewCompositionalLayoutConfiguration()
// config.interSectionSpacing = 10
//
// let layout = UICollectionViewCompositionalLayout { [weak self] section, _ in
// guard let self = self else { return nil}
// guard let model = dataArr[section].first else { return nil }
//
// if model.buy_type == .subCoins {
// return self.coinsBigLayoutSection()
// } else if model.size == .big {
// return self.bigLayoutSection()
// } else {
// return self.smallLayoutSection()
// }
// }
// layout.configuration = config
//
// return layout
// }()
//
// private lazy var collectionView: FACollectionView = {
// let collectionView = FACollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
// collectionView.delegate = self
// collectionView.dataSource = self
// collectionView.clipsToBounds = false
// collectionView.isScrollEnabled = false
// collectionView.register(FAStoreCoinsBigCell.self, forCellWithReuseIdentifier: "FAStoreCoinsBigCell")
// collectionView.register(FAStoreCoinsSmallCell.self, forCellWithReuseIdentifier: "FAStoreCoinsSmallCell")
// collectionView.register(FAStoreCoinsPackCell.self, forCellWithReuseIdentifier: "FAStoreCoinsPackCell")
// collectionView.addObserver(self, forKeyPath: "contentSize", context: nil)
// return collectionView
// }()
override init(frame: CGRect) {
super.init(frame: frame)
xs_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSStoreCoinsView {
private func xs_setupUI() {
addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.top.equalToSuperview()
}
}
}

View File

@ -0,0 +1,14 @@
//
// XSStoreVipView.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
class XSStoreVipView: UIView {
}

View File

@ -0,0 +1,69 @@
//
// XSWalletCell.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSWalletCell: XSTableViewCell {
var item: XSMineItem? {
didSet {
iconImageView.image = item?.iconImage
titleLabel.text = item?.title
}
}
private lazy var iconImageView: UIImageView = {
let imageView = UIImageView()
return imageView
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 16, weight: .medium)
label.textColor = .white
return label
}()
private lazy var indicatorImageView = UIImageView(image: UIImage(named: "arrow_right_icon_06"))
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
xs_setupUI()
}
@MainActor required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSWalletCell {
private func xs_setupUI() {
contentView.addSubview(iconImageView)
contentView.addSubview(titleLabel)
contentView.addSubview(indicatorImageView)
iconImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(16)
}
titleLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(42)
}
indicatorImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-14)
}
}
}

View File

@ -0,0 +1,204 @@
//
// XSWalletHeaderView.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SnapKit
class XSWalletHeaderView: UIView {
override var intrinsicContentSize: CGSize {
return .init(width: XSScreen.width, height: 119)
}
var userInfo: XSUserInfo? {
didSet {
coinsView.coins = userInfo?.coin_left_total
sendCoinsView.coins = userInfo?.send_coin_left_total
}
}
private lazy var bgView: UIView = {
let view = UIView()
view.backgroundColor = .white.withAlphaComponent(0.1)
view.layer.cornerRadius = 8
view.layer.masksToBounds = true
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.white.withAlphaComponent(0.14).cgColor
return view
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .semibold)
label.textColor = .white
label.text = "My Wallet".localized
return label
}()
private lazy var lineView: UIView = {
let view = UIView()
view.backgroundColor = .white.withAlphaComponent(0.12)
return view
}()
private lazy var storeButton: UIButton = {
var configuration = UIButton.Configuration.plain()
configuration.background.image = UIImage(named: "store_button_bg_image")
configuration.attributedTitle = AttributedString("Store".localized, attributes: AttributeContainer([
.font : UIFont.font(ofSize: 16, weight: .bold),
.foregroundColor : UIColor._482_B_00
]))
let button = UIButton(configuration: configuration, primaryAction: UIAction(handler: { [weak self] _ in
guard let self = self else { return }
let vc = XSStoreViewController()
self.viewController?.navigationController?.pushViewController(vc, animated: true)
}))
return button
}()
private lazy var coinsView: CoinsView = {
let view = CoinsView()
view.title = "Coins".localized
return view
}()
private lazy var sendCoinsView: CoinsView = {
let view = CoinsView()
view.title = "Bonus".localized
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
xs_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension XSWalletHeaderView {
private func xs_setupUI() {
addSubview(bgView)
bgView.addSubview(titleLabel)
bgView.addSubview(lineView)
bgView.addSubview(storeButton)
bgView.addSubview(coinsView)
bgView.addSubview(sendCoinsView)
bgView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerX.equalToSuperview()
make.top.bottom.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.height.equalTo(36)
make.top.equalToSuperview()
}
lineView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(12)
make.centerX.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom)
make.height.equalTo(1)
}
storeButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-12)
make.bottom.equalToSuperview().offset(-22)
make.width.equalTo(102)
make.height.equalTo(36)
}
coinsView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.bottom.equalToSuperview().offset(-18)
}
sendCoinsView.snp.makeConstraints { make in
make.centerY.equalTo(coinsView)
make.left.equalToSuperview().offset(119)
}
}
}
extension XSWalletHeaderView {
class CoinsView: UIView {
var title: String? {
didSet {
titleLabel.text = title
}
}
var coins: Int? {
didSet {
coinsLabel.text = "\(coins ?? 0)"
}
}
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 14, weight: .semibold)
label.textColor = .white.withAlphaComponent(0.52)
return label
}()
private lazy var coinsIconView = UIImageView(image: UIImage(named: "coins_icon_02"))
private lazy var coinsLabel: UILabel = {
let label = UILabel()
label.font = .font(ofSize: 18, weight: .bold)
label.textColor = .white
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(titleLabel)
addSubview(coinsIconView)
addSubview(coinsLabel)
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(3)
make.top.equalToSuperview()
make.right.lessThanOrEqualToSuperview()
}
coinsIconView.snp.makeConstraints { make in
make.bottom.left.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(11)
}
coinsLabel.snp.makeConstraints { make in
make.centerY.equalTo(coinsIconView)
make.left.equalTo(coinsIconView.snp.right).offset(2)
make.right.lessThanOrEqualToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}

View File

@ -0,0 +1,297 @@
//
// XSIapManager.swift
// XSeri
//
// Created by 鸿 on 2026/3/16.
//
import UIKit
import StoreKit
import JXIAPManager
class XSIapManager {
typealias CompletionHandler = ((_ finish: Bool) -> Void)
///
static let IAPPrefix = "xseri"
static let manager = XSIapManager()
private lazy var iapManager: JXIAPManager = {
let manager = JXIAPManager.manager
manager.delegate = self
return manager
}()
///
private var completionHandler: CompletionHandler?
private var shortPlayId: String?
private var videoId: String?
private var orderCode: String?
private var payId: String?
///使
///
private var waitRestoreModel: XSWaitRestoreModel? = UserDefaults.xs_object(forKey: kXSWaitRestoreIAPDefaultsKey, as: XSWaitRestoreModel.self)
///
private var payRequest: XSPayDataRequest?
var payDateModel: XSPayDateModel?
///
@MainActor
func start(model: XSPayItem, shortPlayId: String? = nil, videoId: String? = nil, hudShowView: UIView? = nil, handler: CompletionHandler? = nil) {
if let _ = self.waitRestoreModel {
XSToast.show("pay_error_6".localized)
handler?(false)
return
}
guard let payId = model.id else {
handler?(false)
return
}
self.shortPlayId = shortPlayId
self.videoId = videoId
self.completionHandler = handler
self.waitRestoreModel = XSWaitRestoreModel()
self.waitRestoreModel?.buyType = model.buy_type
let productId = getProductId(templateId: model.ios_template_id) ?? ""
var isDiscount = false
var identifierDiscount: String? = nil
if model.discount_type == 1, let _ = model.introductionaryOffer {
isDiscount = true
} else if model.discount_type == 2, let discount = model.promotionalOffers?.first {
isDiscount = true
identifierDiscount = discount.identifier
}
XSHud.show(containerView: hudShowView)
Task {
guard let orderModel = await XSStoreAPI.requestCreateOrder(payId: payId, shortPlayId: shortPlayId ?? "0", videoId: videoId ?? "0", isDiscount: isDiscount, identifierDiscount: identifierDiscount) else {
XSHud.dismiss()
self.waitRestoreModel = nil
self.completionHandler?(false)
self.clean()
return
}
self.orderCode = orderModel.order_code
self.payId = payId
self.waitRestoreModel?.payId = payId
self.waitRestoreModel?.orderCode = orderModel.order_code
var discount: SKPaymentDiscount? = nil
if let identifierDiscount = identifierDiscount,
let signData = orderModel.discount?.sign_data,
let keyIdentifier = signData.keyIdentifier,
let nonce = UUID(uuidString: signData.nonce ?? ""),
let signature = signData.signature,
let timestamp = signData.timestamp
{
discount = SKPaymentDiscount(identifier: identifierDiscount,
keyIdentifier: keyIdentifier,
nonce: nonce,
signature: signature,
timestamp: NSNumber(value: timestamp))
}
self.iapManager.start(productId: productId, orderId: self.orderCode ?? "", applicationUsername: orderModel.discount?.sign_data?.applicationUsername, discount: discount)
}
}
@MainActor
func restore(isLoding: Bool = true, shortPlayId: String? = nil, videoId: String? = nil, completer: ((_ isFinish: Bool, _ buyType: XSStoreAPI.BuyType?) -> Void)?) {
let buyType = self.waitRestoreModel?.buyType
guard let waitRestoreModel = self.waitRestoreModel,
let orderCode = waitRestoreModel.orderCode,
let payId = waitRestoreModel.payId,
let receipt = waitRestoreModel.receipt,
let transactionId = waitRestoreModel.transactionId
else {
if isLoding {
XSToast.show("pay_error_5".localized)
}
completer?(false, buyType)
return
}
if isLoding {
XSHud.show()
}
let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionId, purchaseToken: receipt)
let statParamenters: [String : Any] = [
"type" : isLoding ? "manual" : "auto",
"pay_data" : verifyData.toJsonString() ?? ""
]
// FAStatAPI.requestEventStat(orderCode: orderCode, shortPlayId: shortPlayId, videoId: videoId, eventKey: .payRestore, errorMsg: "restore", otherParamenters: statParamenters)
Task {
let response = await XSStoreAPI.requestVerifyOrder(verifyData)
if isLoding {
XSHud.dismiss()
}
guard let model = response.data else {
completer?(false, buyType)
return
}
self.waitRestoreModel = nil
UserDefaults.xs_setObject(nil, forKey: kXSWaitRestoreIAPDefaultsKey)
if model.status == "success" {
if buyType == .subVip {
XSLoginManager.manager.userInfo?.is_vip = true
}
if isLoding {
XSToast.show("Success".localized)
}
completer?(true, buyType)
if buyType == .subVip {
NotificationCenter.default.post(name: XSIapManager.buyVipFinishNotification, object: nil)
}
} else {
completer?(false, buyType)
}
}
}
func getProductId(templateId: String?) -> String? {
guard let templateId = templateId else { return nil }
return XSIapManager.IAPPrefix + "." + templateId
}
func clean() {
self.orderCode = nil
self.payId = nil
self.shortPlayId = nil
self.videoId = nil
self.completionHandler = nil
}
}
//MARK: JXIAPManagerDelegate
extension XSIapManager: JXIAPManagerDelegate {
func jx_iapPaySuccess(productId: String, receipt: String, transactionIdentifier: String) {
guard let orderCode = self.orderCode, let payId = self.payId else {
self.waitRestoreModel = nil
self.completionHandler?(false)
self.clean()
XSHud.dismiss()
return
}
self.waitRestoreModel?.productId = productId
self.waitRestoreModel?.receipt = receipt
self.waitRestoreModel?.transactionId = transactionIdentifier
UserDefaults.xs_setObject(self.waitRestoreModel, forKey: kXSWaitRestoreIAPDefaultsKey)
#if DEBUG
let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionIdentifier, purchaseToken: receipt)
#else
let verifyData = self.getVerifyOrderParameters(orderCode: orderCode, payId: payId, transactionId: transactionIdentifier, purchaseToken: receipt)
#endif
Task {
let response = await XSStoreAPI.requestVerifyOrder(verifyData)
XSHud.dismiss()
guard let model = response.data else {
// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCallback, errorMsg: verifyData.toJsonString())
self.completionHandler?(false)
self.clean()
return
}
let buyType = self.waitRestoreModel?.buyType
self.waitRestoreModel = nil
UserDefaults.xs_setObject(nil, forKey: kXSWaitRestoreIAPDefaultsKey)
if model.status == "success" {
if buyType == .subVip {
XSLoginManager.manager.userInfo?.is_vip = true
}
XSToast.show("Success".localized)
self.completionHandler?(true)
if buyType == .subVip {
NotificationCenter.default.post(name: XSIapManager.buyVipFinishNotification, object: nil)
}
} else {
XSToast.show("pay_error_4".localized)
// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCallback, errorMsg: verifyData.toJsonString())
self.completionHandler?(false)
}
self.clean()
}
}
func jx_iapPayFailed(productId: String, code: JXIAPManager.ErrorCode, msg: String?) {
XSHud.dismiss()
if code == .noProduct {
XSToast.show("pay_error_2".localized)
} else if code == .cancelled {
XSToast.show("pay_error_3".localized)
}
// if code == .cancelled {
// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payCancel, errorMsg: "user cancel")
// } else {
// FAStatAPI.requestEventStat(orderCode: self.orderCode, shortPlayId: self.shortPlayId, videoId: self.videoId, eventKey: .payError, errorMsg: msg)
// }
self.completionHandler?(false)
self.waitRestoreModel = nil
self.clean()
}
}
extension XSIapManager {
func getVerifyOrderParameters(orderCode: String, payId: String, transactionId: String, purchaseToken: String) -> [String : Any] {
let parameters: [String : Any] = [
"order_code" : orderCode,
"pay_setting_id" : payId,
"pkg_name" : kXSBundleIdentifier,
"transaction_id": transactionId,
"purchases_token" : purchaseToken
]
return parameters
}
///
func preloadingProducts() {
JXIAPManager.manager.fetchReceipt { _ in
self.payRequest = XSPayDataRequest()
self.payRequest?.requestProducts(isToast: false) { model in
}
}
}
}
extension XSIapManager {
///
@objc static let buyVipFinishNotification = Notification.Name(rawValue: "XSIapManager.buyVipFinishNotification")
}

View File

@ -0,0 +1,37 @@
//
// XSIapOrderModel.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SmartCodable
struct XSIapOrderModel: SmartCodable {
var code: Int?
var message: String?
var order_code: String?
var is_backhaul: String?
var money: String?
var discount: XSIapOrderDiscountModel?
}
struct XSIapOrderDiscountModel: SmartCodable {
var discount_code: String?
var is_discount: Bool?
var sign_data: XSIapOrderDiscountSign?
}
struct XSIapOrderDiscountSign: SmartCodable {
var keyIdentifier: String?
var nonce: String?
var timestamp: TimeInterval?
var applicationUsername: String?
var signature: String?
}

View File

@ -0,0 +1,16 @@
//
// XSIapVerifyModel.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import SmartCodable
struct XSIapVerifyModel: SmartCodable {
var money: String?
var status: String?
var is_backhaul: String?
var code: String?
}

View File

@ -0,0 +1,165 @@
//
// XSPayDataRequest.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
import StoreKit
class XSPayDataRequest: NSObject {
private var oldTemplateModel: XSPayDateModel?
private var completerBlock: ((_ model: XSPayDateModel?) -> Void)?
private var payRetainBlock: ((_ model: XSPayDateModel?) -> Void)?
private var isLoding = false
private var isToast = false
func requestProducts(isLoding: Bool = false, isToast: Bool = true, completer: @escaping ((_ model: XSPayDateModel?) -> Void)) {
self.completerBlock = completer
self.isLoding = isLoding
self.isToast = isToast
if isLoding {
XSHud.show()
}
Task {
guard let model = await XSStoreAPI.requestPayTemplate(isLoding: false, isToast: isToast) else {
if isLoding {
XSHud.dismiss()
}
return
}
self.oldTemplateModel = model
var productIdArr: [String] = []
model.list_sub_vip?.forEach { item in
productIdArr.append(XSIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "")
}
model.list_coins?.forEach { item in
productIdArr.append(XSIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "")
}
let set = Set(productIdArr)
let productsRequest = SKProductsRequest(productIdentifiers: set)
productsRequest.delegate = self
productsRequest.start()
}
}
func requestPayRetainInfo(completer: ((_ model: XSPayDateModel?) -> Void)?) {
self.payRetainBlock = completer
self.isLoding = true
XSHud.show()
Task {
guard let model = await XSStoreAPI.requestPayRetainInfo() else {
XSHud.dismiss()
self.payRetainBlock?(nil)
return
}
self.oldTemplateModel = model
var productIdArr: [String] = []
model.list_coins?.forEach { item in
productIdArr.append(XSIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "")
}
model.list_sub_vip?.forEach { item in
productIdArr.append(XSIapManager.manager.getProductId(templateId: item.ios_template_id) ?? "")
}
let set = Set(productIdArr)
let productsRequest = SKProductsRequest(productIdentifiers: set)
productsRequest.delegate = self
productsRequest.start()
}
}
}
//MARK: SKProductsRequestDelegate
extension XSPayDataRequest: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if isLoding {
XSHud.dismiss()
}
let products = response.products
if self.completerBlock != nil || self.payRetainBlock != nil {
guard let templateModel = self.oldTemplateModel else { return }
var newCoinList: [XSPayItem] = []
var newVipList: [XSPayItem] = []
templateModel.list_coins?.forEach { item in
let productId = XSIapManager.manager.getProductId(templateId: item.ios_template_id) ?? ""
for product in products {
if productId == product.productIdentifier {
item.price = product.price.stringValue
item.currency = product.priceLocale.currencySymbol
item.product = product
newCoinList.append(item)
break
}
}
}
templateModel.list_sub_vip?.forEach { item in
let productId = XSIapManager.manager.getProductId(templateId: item.ios_template_id) ?? ""
for product in products {
if productId == product.productIdentifier {
item.price = product.price.stringValue
item.currency = product.priceLocale.currencySymbol
item.product = product
newVipList.append(item)
break
}
}
}
templateModel.list_coins = newCoinList
templateModel.list_sub_vip = newVipList
if let block = self.completerBlock {
XSIapManager.manager.payDateModel = templateModel
DispatchQueue.main.async {
block(templateModel)
}
} else if let block = self.payRetainBlock {
DispatchQueue.main.async {
block(templateModel)
}
}
}
// else if let block = self.payAlertBlock {
// guard let coinalertModel = self.payAlertModel else { return }
// let productId = FAIapManager.manager.getProductId(templateId: coinalertModel.info?.ios_template_id) ?? ""
//
// for product in products {
// if productId == product.productIdentifier {
// coinalertModel.info?.price = product.price.stringValue
// coinalertModel.info?.currency = product.priceLocale.currencySymbol
// coinalertModel.info?.product = product
// break
// }
// }
//
// DispatchQueue.main.async {
// block(coinalertModel)
// }
// }
}
}

View File

@ -0,0 +1,48 @@
//
// XSWaitRestoreModel.swift
// XSeri
//
// Created by 鸿 on 2026/3/17.
//
import UIKit
class XSWaitRestoreModel: NSObject, NSSecureCoding {
var orderCode: String?
var payId: String?
var productId: String?
var receipt: String?
var buyType: XSStoreAPI.BuyType?
var transactionId: String?
required override init() { }
static var supportsSecureCoding: Bool {
get {
return true
}
}
func encode(with coder: NSCoder) {
coder.encode(orderCode, forKey: "orderCode")
coder.encode(payId, forKey: "payId")
coder.encode(productId, forKey: "productId")
coder.encode(receipt, forKey: "receipt")
coder.encode(buyType?.rawValue, forKey: "buyType")
coder.encode(transactionId, forKey: "transactionId")
}
required init?(coder: NSCoder) {
super.init()
orderCode = coder.decodeObject(of: NSString.self, forKey: "orderCode") as? String
payId = coder.decodeObject(of: NSString.self, forKey: "payId") as? String
productId = coder.decodeObject(of: NSString.self, forKey: "productId") as? String
receipt = coder.decodeObject(of: NSString.self, forKey: "receipt") as? String
transactionId = coder.decodeObject(of: NSString.self, forKey: "transactionId") as? String
if let type = coder.decodeObject(of: NSString.self, forKey: "buyType") as? String {
buyType = XSStoreAPI.BuyType(rawValue: type)
}
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x00",
"green" : "0x2B",
"red" : "0x48"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x27",
"green" : "0x39",
"red" : "0x52"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x03",
"green" : "0x2A",
"red" : "0x71"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x4A",
"green" : "0x56",
"red" : "0xF6"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x36",
"green" : "0x8C",
"red" : "0xF9"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 677 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Some files were not shown because too many files have changed in this diff Show More