ソースを参照

vip功能开发完毕

100Years 3 週間 前
コミット
64f73c1946
81 ファイル変更2025 行追加215 行削除
  1. 74 4
      AIRingtone.xcodeproj/project.pbxproj
  2. 15 0
      AIRingtone.xcworkspace/xcshareddata/swiftpm/Package.resolved
  3. 21 0
      AIRingtone/Assets.xcassets/Common/more_info_white.imageset/Contents.json
  4. BIN
      AIRingtone/Assets.xcassets/Common/more_info_white.imageset/more_info_white@2x.png
  5. 6 0
      AIRingtone/Assets.xcassets/VIP/Contents.json
  6. 22 0
      AIRingtone/Assets.xcassets/VIP/check.imageset/Contents.json
  7. BIN
      AIRingtone/Assets.xcassets/VIP/check.imageset/check@2x.png
  8. BIN
      AIRingtone/Assets.xcassets/VIP/check.imageset/check@3x.png
  9. 22 0
      AIRingtone/Assets.xcassets/VIP/close_gray.imageset/Contents.json
  10. BIN
      AIRingtone/Assets.xcassets/VIP/close_gray.imageset/close_gray@2x.png
  11. BIN
      AIRingtone/Assets.xcassets/VIP/close_gray.imageset/close_gray@3x.png
  12. 6 0
      AIRingtone/Assets.xcassets/VIP/listIcon/Contents.json
  13. 22 0
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_ads.imageset/Contents.json
  14. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_ads.imageset/vip_ads@2x.png
  15. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_ads.imageset/vip_ads@3x.png
  16. 22 0
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_photo.imageset/Contents.json
  17. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_photo.imageset/vip_photo@2x.png
  18. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_photo.imageset/vip_photo@3x.png
  19. 22 0
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_pic.imageset/Contents.json
  20. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_pic.imageset/vip_pic@2x.png
  21. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_pic.imageset/vip_pic@3x.png
  22. 22 0
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_ringtone.imageset/Contents.json
  23. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_ringtone.imageset/vip_ringtone@2x.png
  24. BIN
      AIRingtone/Assets.xcassets/VIP/listIcon/vip_ringtone.imageset/vip_ringtone@3x.png
  25. 22 0
      AIRingtone/Assets.xcassets/VIP/nav_vip.imageset/Contents.json
  26. BIN
      AIRingtone/Assets.xcassets/VIP/nav_vip.imageset/nav_vip@2x.png
  27. BIN
      AIRingtone/Assets.xcassets/VIP/nav_vip.imageset/nav_vip@3x.png
  28. 22 0
      AIRingtone/Assets.xcassets/VIP/purchase_bj.imageset/Contents.json
  29. BIN
      AIRingtone/Assets.xcassets/VIP/purchase_bj.imageset/purchase_bj@2x.png
  30. BIN
      AIRingtone/Assets.xcassets/VIP/purchase_bj.imageset/purchase_bj@3x.png
  31. 22 0
      AIRingtone/Assets.xcassets/VIP/radioboxSelected.imageset/Contents.json
  32. BIN
      AIRingtone/Assets.xcassets/VIP/radioboxSelected.imageset/radioboxSelected@2x.png
  33. BIN
      AIRingtone/Assets.xcassets/VIP/radioboxSelected.imageset/radioboxSelected@3x.png
  34. 22 0
      AIRingtone/Assets.xcassets/VIP/setting_vip_bj.imageset/Contents.json
  35. BIN
      AIRingtone/Assets.xcassets/VIP/setting_vip_bj.imageset/setting_vip_bj@2x.png
  36. BIN
      AIRingtone/Assets.xcassets/VIP/setting_vip_bj.imageset/setting_vip_bj@3x.png
  37. 22 0
      AIRingtone/Assets.xcassets/VIP/upvote_black.imageset/Contents.json
  38. BIN
      AIRingtone/Assets.xcassets/VIP/upvote_black.imageset/upvote_black@2x.png
  39. BIN
      AIRingtone/Assets.xcassets/VIP/upvote_black.imageset/upvote_black@3x.png
  40. 22 0
      AIRingtone/Assets.xcassets/VIP/vip_big_icon.imageset/Contents.json
  41. BIN
      AIRingtone/Assets.xcassets/VIP/vip_big_icon.imageset/vip_big_icon@2x.png
  42. BIN
      AIRingtone/Assets.xcassets/VIP/vip_big_icon.imageset/vip_big_icon@3x.png
  43. 22 0
      AIRingtone/Assets.xcassets/VIP/vip_icon.imageset/Contents.json
  44. BIN
      AIRingtone/Assets.xcassets/VIP/vip_icon.imageset/vip_icon@2x.png
  45. BIN
      AIRingtone/Assets.xcassets/VIP/vip_icon.imageset/vip_icon@3x.png
  46. 22 0
      AIRingtone/Assets.xcassets/VIP/vip_icon_black.imageset/Contents.json
  47. BIN
      AIRingtone/Assets.xcassets/VIP/vip_icon_black.imageset/vip_icon_black@2x.png
  48. BIN
      AIRingtone/Assets.xcassets/VIP/vip_icon_black.imageset/vip_icon_black@3x.png
  49. 22 0
      AIRingtone/Assets.xcassets/VIP/vip_icon_white.imageset/Contents.json
  50. BIN
      AIRingtone/Assets.xcassets/VIP/vip_icon_white.imageset/vip_icon_white@2x.png
  51. BIN
      AIRingtone/Assets.xcassets/VIP/vip_icon_white.imageset/vip_icon_white@3x.png
  52. 22 0
      AIRingtone/Assets.xcassets/VIP/vip_side_icon.imageset/Contents.json
  53. BIN
      AIRingtone/Assets.xcassets/VIP/vip_side_icon.imageset/vip_side_icon@2x.png
  54. BIN
      AIRingtone/Assets.xcassets/VIP/vip_side_icon.imageset/vip_side_icon@3x.png
  55. 2 2
      AIRingtone/Business/LaunchVC/TSLaunchVC.swift
  56. 8 1
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGeneralPicVC+Event.swift
  57. 8 0
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGeneralPicVC.swift
  58. 18 12
      AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/TSTextGeneralPicVC.swift
  59. 16 30
      AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/View/TSCreatBtnView.swift
  60. 3 2
      AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/View/TSPromptTextView.swift
  61. 1 1
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC+Event.swift
  62. 8 0
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift
  63. 47 47
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVM.swift
  64. 11 11
      AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/TSTextGeneralRintoneVC.swift
  65. 383 0
      AIRingtone/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift
  66. 1 1
      AIRingtone/Business/TSSetingVC/SetingVC/TSSetingModel.swift
  67. 8 8
      AIRingtone/Business/TSSetingVC/SetingVC/TSSetingVC.swift
  68. 6 6
      AIRingtone/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift
  69. 78 54
      AIRingtone/Business/TSSetingVC/SetingVC/View/SettingPurchaseTopView.swift
  70. 4 10
      AIRingtone/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift
  71. 27 1
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/TSThemeBrowseVC.swift
  72. 4 0
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/VM/TSThemeBrowseVM.swift
  73. 0 4
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/View/TSTBBtnView.swift
  74. 9 1
      AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetItemView.swift
  75. 2 2
      AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetRingToneView.swift
  76. 49 0
      AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetVC.swift
  77. 12 14
      AIRingtone/Business/TSThemeVC/TSThemeTutorialsVC/TSThemeTutorialsVC.swift
  78. 180 0
      AIRingtone/Business/TSTutorialsVC/TSTutorialPopupVC.swift
  79. 26 0
      AIRingtone/Business/VIewTool/TSButton.swift
  80. 28 4
      AIRingtone/Business/VIewTool/TSViewTool.swift
  81. 612 0
      AIRingtone/Common/Purchase/TSPurchaseManager.swift

+ 74 - 4
AIRingtone.xcodeproj/project.pbxproj

@@ -97,6 +97,13 @@
 		A868A91A2D78559800F6D884 /* TSProgressState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868A9192D78559600F6D884 /* TSProgressState.swift */; };
 		A868A91C2D78592600F6D884 /* rotatingAnimation.gif in Resources */ = {isa = PBXBuildFile; fileRef = A868A91B2D78592600F6D884 /* rotatingAnimation.gif */; };
 		A868A91E2D785CEA00F6D884 /* TSGeneralPicVC+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868A91D2D785CE600F6D884 /* TSGeneralPicVC+Event.swift */; };
+		A899D34D2D82C61C00AB9C1C /* TSPurchaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D34B2D82C61C00AB9C1C /* TSPurchaseVC.swift */; };
+		A899D3572D82C73100AB9C1C /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = A899D3562D82C73100AB9C1C /* SwiftUIX */; };
+		A899D35A2D82C75B00AB9C1C /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = A899D3592D82C75B00AB9C1C /* SwiftUIX */; };
+		A899D35D2D82C88900AB9C1C /* SwiftUIX in Frameworks */ = {isa = PBXBuildFile; productRef = A899D35C2D82C88900AB9C1C /* SwiftUIX */; };
+		A899D3602D82C8D800AB9C1C /* TSPurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D35E2D82C8D800AB9C1C /* TSPurchaseManager.swift */; };
+		A899D3622D82D89C00AB9C1C /* TSButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3612D82D89000AB9C1C /* TSButton.swift */; };
+		A899D36F2D83C3DC00AB9C1C /* TSTutorialPopupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D36E2D83C3D500AB9C1C /* TSTutorialPopupVC.swift */; };
 		A8C6436C2D79A8C8001068D0 /* TSAIRintoneHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */; };
 		A8CC55822D797720002E0CAA /* TSGeneralPicBrowseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */; };
 		A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */; };
@@ -207,6 +214,11 @@
 		A868A9192D78559600F6D884 /* TSProgressState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSProgressState.swift; sourceTree = "<group>"; };
 		A868A91B2D78592600F6D884 /* rotatingAnimation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = rotatingAnimation.gif; sourceTree = "<group>"; };
 		A868A91D2D785CE600F6D884 /* TSGeneralPicVC+Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSGeneralPicVC+Event.swift"; sourceTree = "<group>"; };
+		A899D34B2D82C61C00AB9C1C /* TSPurchaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVC.swift; sourceTree = "<group>"; };
+		A899D3512D82C67900AB9C1C /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+		A899D35E2D82C8D800AB9C1C /* TSPurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseManager.swift; sourceTree = "<group>"; };
+		A899D3612D82D89000AB9C1C /* TSButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSButton.swift; sourceTree = "<group>"; };
+		A899D36E2D83C3D500AB9C1C /* TSTutorialPopupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTutorialPopupVC.swift; sourceTree = "<group>"; };
 		A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistoryCell.swift; sourceTree = "<group>"; };
 		A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralPicBrowseVC.swift; sourceTree = "<group>"; };
 		A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTextGeneralRintoneVC.swift; sourceTree = "<group>"; };
@@ -221,8 +233,11 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				A899D3572D82C73100AB9C1C /* SwiftUIX in Frameworks */,
 				F5FF0EC10B0056B65FDB9C78 /* Pods_AIRingtone.framework in Frameworks */,
 				A868A8F22D770A9200F6D884 /* libmp3lame.a in Frameworks */,
+				A899D35D2D82C88900AB9C1C /* SwiftUIX in Frameworks */,
+				A899D35A2D82C75B00AB9C1C /* SwiftUIX in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -232,6 +247,7 @@
 		229F5991134C992950FFF4DF /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3512D82C67900AB9C1C /* SwiftUI.framework */,
 				39C823AB7F3D49E8B2356E07 /* Pods_AIRingtone.framework */,
 			);
 			name = Frameworks;
@@ -300,6 +316,7 @@
 			isa = PBXGroup;
 			children = (
 				A83F871B2D79408300D29B1B /* Data */,
+				A899D34C2D82C61C00AB9C1C /* TSPurchaseMembershipVC */,
 				A868A8A12D7560B900F6D884 /* VIewTool */,
 				A80EDF192D71AC2F003CD332 /* TSCollectionViewVM */,
 				A80EDF162D7193E4003CD332 /* TSTutorialsVC */,
@@ -376,6 +393,7 @@
 		A80EDEC22D718CEA003CD332 /* Common */ = {
 			isa = PBXGroup;
 			children = (
+				A899D35F2D82C8D800AB9C1C /* Purchase */,
 				A868A8C02D76A28A00F6D884 /* Ex */,
 				A80EDE622D718B0F003CD332 /* Res */,
 				A80EDE5F2D718AEF003CD332 /* Config */,
@@ -478,6 +496,7 @@
 		A80EDF162D7193E4003CD332 /* TSTutorialsVC */ = {
 			isa = PBXGroup;
 			children = (
+				A899D36E2D83C3D500AB9C1C /* TSTutorialPopupVC.swift */,
 				A80EDF172D7193ED003CD332 /* TSTutorialsVC.swift */,
 			);
 			path = TSTutorialsVC;
@@ -580,6 +599,7 @@
 		A868A8A12D7560B900F6D884 /* VIewTool */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3612D82D89000AB9C1C /* TSButton.swift */,
 				A868A9152D784E6500F6D884 /* TSBottomAlertVC.swift */,
 				A868A90D2D7846D400F6D884 /* TSSavePhotoSuccessTool.swift */,
 				A868A8CF2D76D02300F6D884 /* TSRingToneCellView.swift */,
@@ -761,6 +781,22 @@
 			path = Model;
 			sourceTree = "<group>";
 		};
+		A899D34C2D82C61C00AB9C1C /* TSPurchaseMembershipVC */ = {
+			isa = PBXGroup;
+			children = (
+				A899D34B2D82C61C00AB9C1C /* TSPurchaseVC.swift */,
+			);
+			path = TSPurchaseMembershipVC;
+			sourceTree = "<group>";
+		};
+		A899D35F2D82C8D800AB9C1C /* Purchase */ = {
+			isa = PBXGroup;
+			children = (
+				A899D35E2D82C8D800AB9C1C /* TSPurchaseManager.swift */,
+			);
+			path = Purchase;
+			sourceTree = "<group>";
+		};
 		A8C6436A2D79A8BD001068D0 /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -868,6 +904,9 @@
 			);
 			mainGroup = A80EDE2D2D7184D9003CD332;
 			minimizedProjectReferenceProxies = 1;
+			packageReferences = (
+				A899D35B2D82C88800AB9C1C /* XCRemoteSwiftPackageReference "SwiftUIX" */,
+			);
 			preferredProjectObjectVersion = 77;
 			productRefGroup = A80EDE372D7184D9003CD332 /* Products */;
 			projectDirPath = "";
@@ -950,6 +989,7 @@
 				A868A9002D77E55800F6D884 /* TSPromptStyleView.swift in Sources */,
 				A83F87202D794FF000D29B1B /* TSAIPhotoImageCell.swift in Sources */,
 				A868A8DB2D76F00C00F6D884 /* TSBandRingTool.swift in Sources */,
+				A899D3602D82C8D800AB9C1C /* TSPurchaseManager.swift in Sources */,
 				A868A8F12D77081C00F6D884 /* TSContactsTool.swift in Sources */,
 				A80EDF2D2D71C2CA003CD332 /* TSThemeModel.swift in Sources */,
 				A80EDE582D718623003CD332 /* AppDelegate.swift in Sources */,
@@ -964,6 +1004,7 @@
 				A868A9032D77E5A500F6D884 /* TSPTPStyleModel.swift in Sources */,
 				A80EDEC62D718CEA003CD332 /* TSRandomTextPicker.swift in Sources */,
 				A80EDEC82D718CEA003CD332 /* TSNetworkManager+Loading.swift in Sources */,
+				A899D34D2D82C61C00AB9C1C /* TSPurchaseVC.swift in Sources */,
 				A868A9182D78555200F6D884 /* TSGenneralPicVM.swift in Sources */,
 				A80EDECB2D718CEA003CD332 /* TSNetWork+Business.swift in Sources */,
 				A8CC558B2D79905A002E0CAA /* TSCreatBtnView.swift in Sources */,
@@ -1013,6 +1054,7 @@
 				A80EDF272D71C13D003CD332 /* TSColVVMHeaderModel.swift in Sources */,
 				A83F87222D7953C000D29B1B /* Notification+TSEx.swift in Sources */,
 				A83503CD2D7ED1440067002A /* TSThemeTutorialsVC.swift in Sources */,
+				A899D3622D82D89C00AB9C1C /* TSButton.swift in Sources */,
 				A868A8AD2D758B6B00F6D884 /* TSTBCallPreviewView.swift in Sources */,
 				A868A9162D784E6500F6D884 /* TSBottomAlertVC.swift in Sources */,
 				A80EDF012D718DF1003CD332 /* TSBusinessWebVC.swift in Sources */,
@@ -1022,6 +1064,7 @@
 				A8CC55902D799F9F002E0CAA /* TSAIRintoneVM.swift in Sources */,
 				A80EDF032D718DF1003CD332 /* ShareActivityItemProvider.swift in Sources */,
 				A80EDF042D718DF1003CD332 /* TSSetingModel.swift in Sources */,
+				A899D36F2D83C3DC00AB9C1C /* TSTutorialPopupVC.swift in Sources */,
 				A8CC55892D798E5D002E0CAA /* TSTextGeneralRintoneVM.swift in Sources */,
 				A80EDF052D718DF1003CD332 /* TSSetingVC.swift in Sources */,
 				A868A8FA2D77E35E00F6D884 /* TSPromptTextView.swift in Sources */,
@@ -1056,7 +1099,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 7;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = AIRingtone/Info.plist;
@@ -1075,7 +1118,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/AIRingtone/Common/Tool/TSBandRingTool/libmp3",
 				);
-				MARKETING_VERSION = 1.0;
+				MARKETING_VERSION = 1.1;
 				PRODUCT_BUNDLE_IDENTIFIER = ai.ringtones.com;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -1098,7 +1141,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 7;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				GENERATE_INFOPLIST_FILE = YES;
 				INFOPLIST_FILE = AIRingtone/Info.plist;
@@ -1117,7 +1160,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/AIRingtone/Common/Tool/TSBandRingTool/libmp3",
 				);
-				MARKETING_VERSION = 1.0;
+				MARKETING_VERSION = 1.1;
 				PRODUCT_BUNDLE_IDENTIFIER = ai.ringtones.com;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -1272,6 +1315,33 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+		A899D35B2D82C88800AB9C1C /* XCRemoteSwiftPackageReference "SwiftUIX" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/SwiftUIX/SwiftUIX.git";
+			requirement = {
+				kind = upToNextMajorVersion;
+				minimumVersion = 0.2.3;
+			};
+		};
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		A899D3562D82C73100AB9C1C /* SwiftUIX */ = {
+			isa = XCSwiftPackageProductDependency;
+			productName = SwiftUIX;
+		};
+		A899D3592D82C75B00AB9C1C /* SwiftUIX */ = {
+			isa = XCSwiftPackageProductDependency;
+			productName = SwiftUIX;
+		};
+		A899D35C2D82C88900AB9C1C /* SwiftUIX */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = A899D35B2D82C88800AB9C1C /* XCRemoteSwiftPackageReference "SwiftUIX" */;
+			productName = SwiftUIX;
+		};
+/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = A80EDE2E2D7184D9003CD332 /* Project object */;
 }

+ 15 - 0
AIRingtone.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -0,0 +1,15 @@
+{
+  "originHash" : "81d2e580ba9932bbf94c74a60282a1d23a575ec90655203f2e82c84e3edc604c",
+  "pins" : [
+    {
+      "identity" : "swiftuix",
+      "kind" : "remoteSourceControl",
+      "location" : "https://github.com/SwiftUIX/SwiftUIX.git",
+      "state" : {
+        "revision" : "e984fd2e08140ad5a95d084be38fe02b774bc15d",
+        "version" : "0.2.3"
+      }
+    }
+  ],
+  "version" : 3
+}

+ 21 - 0
AIRingtone/Assets.xcassets/Common/more_info_white.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/Common/more_info_white.imageset/more_info_white@2x.png


+ 6 - 0
AIRingtone/Assets.xcassets/VIP/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 22 - 0
AIRingtone/Assets.xcassets/VIP/check.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/check.imageset/check@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/check.imageset/check@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/close_gray.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/close_gray.imageset/close_gray@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/close_gray.imageset/close_gray@3x.png


+ 6 - 0
AIRingtone/Assets.xcassets/VIP/listIcon/Contents.json

@@ -0,0 +1,6 @@
+{
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

+ 22 - 0
AIRingtone/Assets.xcassets/VIP/listIcon/vip_ads.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_ads.imageset/vip_ads@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_ads.imageset/vip_ads@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/listIcon/vip_photo.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_photo.imageset/vip_photo@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_photo.imageset/vip_photo@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/listIcon/vip_pic.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_pic.imageset/vip_pic@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_pic.imageset/vip_pic@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/listIcon/vip_ringtone.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_ringtone.imageset/vip_ringtone@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/listIcon/vip_ringtone.imageset/vip_ringtone@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/nav_vip.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/nav_vip.imageset/nav_vip@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/nav_vip.imageset/nav_vip@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/purchase_bj.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/purchase_bj.imageset/purchase_bj@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/purchase_bj.imageset/purchase_bj@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/radioboxSelected.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/radioboxSelected.imageset/radioboxSelected@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/radioboxSelected.imageset/radioboxSelected@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/setting_vip_bj.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/setting_vip_bj.imageset/setting_vip_bj@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/setting_vip_bj.imageset/setting_vip_bj@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/upvote_black.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/upvote_black.imageset/upvote_black@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/upvote_black.imageset/upvote_black@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/vip_big_icon.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/vip_big_icon.imageset/vip_big_icon@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/vip_big_icon.imageset/vip_big_icon@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/vip_icon.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/vip_icon.imageset/vip_icon@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/vip_icon.imageset/vip_icon@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/vip_icon_black.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/vip_icon_black.imageset/vip_icon_black@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/vip_icon_black.imageset/vip_icon_black@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/vip_icon_white.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/vip_icon_white.imageset/vip_icon_white@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/vip_icon_white.imageset/vip_icon_white@3x.png


+ 22 - 0
AIRingtone/Assets.xcassets/VIP/vip_side_icon.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/VIP/vip_side_icon.imageset/vip_side_icon@2x.png


BIN
AIRingtone/Assets.xcassets/VIP/vip_side_icon.imageset/vip_side_icon@3x.png


+ 2 - 2
AIRingtone/Business/LaunchVC/TSLaunchVC.swift

@@ -37,14 +37,14 @@ class TSLaunchVC: UIViewController {
         TSNetworkShared.startListenNetStatus { status, manager in
             switch status {
             case .reachable:
-//                PurchaseManager.default.requestProducts()
+                kPurchaseDefault.requestProducts()
 //                AppDelegate.requestAdTrack()
                 manager?.stopListening()
 //                self.initAdMob()
                 break
             default:
 //                AppDelegate.requestAdTrack()
-//                PurchaseManager.default.requestProducts()
+                kPurchaseDefault.requestProducts()
 //                self.initAdMob()
                 break
             }

+ 8 - 1
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGeneralPicVC+Event.swift

@@ -56,6 +56,13 @@ extension TSGeneralPicVC {
         return nil
     }
     
+    
+    var vipFreeNumType:VipFreeNumType{
+        if gennerateType == .photo{
+            return .photo
+        }
+        return .posetr
+    }
 }
 extension TSGeneralPicVC {
     func upDateView(state:TSProgressState,model:TSActionInfoModel?){
@@ -134,7 +141,7 @@ extension TSGeneralPicVC {
             guard let self = self else { return }
         }
         
-//        kPurchaseDefault.useOnceForFree(type: .generatePic)
+        kPurchaseDefault.useOnceForFree(type: vipFreeNumType)
         
         if let model = imageModel {
             complete(model)

+ 8 - 0
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGeneralPicVC.swift

@@ -86,6 +86,10 @@ class TSGeneralPicVC: TSBottomAlertVC {
     }
     
     override func clickAgainBtn() {
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: vipFreeNumType) == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         viewModel.creatImageEmoji(text:aiText)
     }
     
@@ -111,6 +115,10 @@ class TSGeneralPicVC: TSBottomAlertVC {
     }
 
     override func dealThings() {
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: vipFreeNumType) == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         viewModel.creatImageEmoji(text: self.aiText)
         viewModel.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
             guard let self = self else { return }

+ 18 - 12
AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/TSTextGeneralPicVC.swift

@@ -114,16 +114,15 @@ class TSTextGeneralPicVC: TSBaseVC {
     }
     
     override func dealThings() {
-        
-//        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+        self.creatBtnView.setVip(vip: kPurchaseDefault.isVip)
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
     }
     
-//    @objc func vipInfoChanged() {
-//        kExecuteOnMainThread {
-//            self.vipBtn.isHidden = PurchaseManager.default.isVip
-//            self.collectionComponent.reloadData()
-//        }
-//    }
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.creatBtnView.setVip(vip: kPurchaseDefault.isVip)
+        }
+    }
     
     @objc func clickView() {
         view.endEditing(true)
@@ -138,10 +137,17 @@ class TSTextGeneralPicVC: TSBaseVC {
 extension TSTextGeneralPicVC {
     func generateImage() {
         
-//        //判断 vip
-//        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .textGeneratePic) == false, vc: self) {[weak self] in
-//            guard let self = self else { return }
-//        }{ return }
+        var vipFreeNumType:VipFreeNumType = .posetr
+        if viewModel.gennerateType == .poster {
+            vipFreeNumType = VipFreeNumType.posetr
+        }else if viewModel.gennerateType == .photo {
+            vipFreeNumType = VipFreeNumType.photo
+        }
+        
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: vipFreeNumType) == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         
         let gennerateVC = TSGeneralPicVC(aiText: viewModel.prompt, gennerateType: viewModel.gennerateType)
         {[weak self] model in

+ 16 - 30
AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/View/TSCreatBtnView.swift

@@ -23,54 +23,40 @@ class TSCreatBtnView: TSBaseView {
     //###################################### Button ######################################
     lazy var creatBtn:UIButton  = {
         
-        let btn = UIButton.createButton(title:"Create Now",backgroundColor: .black,font: UIFont.font(size: 16,weight: .regular),titleColor:.themeColor,corner: 24)
+        let btn = UIButton.createButton(title:"Create Now",font: UIFont.font(size: 16,weight: .regular),titleColor:.white,corner: 24)
         {[weak self] in
             guard let self = self else { return }
             clickBlock()
         }
-        
-        btn.layer.borderWidth = 1
-        btn.layer.borderColor = UIColor.themeColor.cgColor
-        btn.frame = CGRectMake(0, 0, 200, 48)
+        btn.frame = CGRectMake(0, 0, k_ScreenWidth - 32, 48)
+//        btn.contentEdgeInsets = UIEdgeInsets(top: 4, left: 7, bottom: 4, right: 7)
+        btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: -8, bottom: 0, right: 0)
+        btn.addGradientBg(colors: ["#E961F6".uiColor.cgColor,"#7E57F4".uiColor.cgColor])
         return btn
     }()
-    
-    lazy var shadowView: UIView = {
-        let shadowView = UIView()
-        shadowView.frame = creatBtn.frame
-        shadowView.layer.shadowColor = UIColor.themeColor.cgColor
-        shadowView.layer.shadowOffset = CGSize(width: 0, height: 4)
-        shadowView.layer.shadowOpacity = 0.5
-        shadowView.layer.shadowRadius = 5
-        return shadowView
-    }()
-
+     
     override func creatUI() {
         
-        contentView.addSubview(shadowView)
-        shadowView.snp.makeConstraints { make in
+        contentView.addSubview(creatBtn)
+        creatBtn.snp.makeConstraints { make in
             make.center.equalToSuperview()
             make.leading.equalTo(16)
             make.trailing.equalTo(-16)
             make.height.equalTo(creatBtn.height)
         }
-
-        shadowView.addSubview(creatBtn)
-        creatBtn.snp.makeConstraints { make in
-            make.edges.equalToSuperview()
-        }
+        setVip(vip:true)
     }
 
     func setBtnEnabled(isEnabled:Bool) {
         creatBtn.isEnabled = isEnabled
-        if isEnabled {
-            shadowView.layer.shadowColor = UIColor.themeColor.cgColor
-            creatBtn.layer.borderColor = UIColor.themeColor.withAlphaComponent(1.0).cgColor
-            creatBtn.setTitleColor(UIColor.themeColor.withAlphaComponent(1.0), for: .normal)
+        creatBtn.alpha = isEnabled ? 1.0 : 0.6
+    }
+    
+    func setVip(vip:Bool) {
+        if vip {
+            creatBtn.setImage(nil, for: .normal)
         }else{
-            shadowView.layer.shadowColor = UIColor.clear.cgColor
-            creatBtn.layer.borderColor = UIColor.themeColor.withAlphaComponent(0.4).cgColor
-            creatBtn.setTitleColor(UIColor.themeColor.withAlphaComponent(0.6), for: .normal)
+            creatBtn.setImage(UIImage(named: "vip_icon_white"), for: .normal)
         }
     }
 }

+ 3 - 2
AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/View/TSPromptTextView.swift

@@ -189,12 +189,13 @@ class TSPromptTextView : TSBaseView{
     
     }
 
-//    func getVipText()->String{
+    func getVipText()->String{
+        return "Generate"
 //        if kPurchaseDefault.isVip {
 //            return "Generate"
 //        }
 //        return "Generate (\(kPurchaseDefault.freeNum(type: .generatePic)))"
-//    }
+    }
     
 }
 

+ 1 - 1
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC+Event.swift

@@ -86,7 +86,7 @@ extension TSGeneralRintoneVC {
         ringView.isHidden = false
         regenerateBtn.isHidden = false
         
-//        kPurchaseDefault.useOnceForFree(type: .generatePic)
+        kPurchaseDefault.useOnceForFree(type: .ringtones)
         
         if let model = infoModel {
             complete(model)

+ 8 - 0
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift

@@ -88,6 +88,10 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
     }
     
     override func clickAgainBtn() {
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .ringtones) == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         viewModel.creatRintone(text:aiText)
     }
     
@@ -112,6 +116,10 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
     }
     
     override func dealThings() {
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .ringtones) == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         viewModel.creatRintone(text: self.aiText)
         viewModel.$stateDatauPblished.receive(on: DispatchQueue.main).sink {[weak self]  (state,model) in
             guard let self = self else { return }

+ 47 - 47
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVM.swift

@@ -30,61 +30,61 @@ class TSGeneralRintoneVM {
     var aiText:String = ""
     var generatingProgress = 0
     
-//    func creatRintone(text:String) {
-//
-//        stateDatauPblished = (.start,nil)
-//        stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-//
-//        kDelayOnMainThread(0.2) {
-//            self.stateDatauPblished = (.progressString(self.generating(progress: 0.2)),nil)
-//        }
-//        
-//        kDelayOnMainThread(0.4) {
-//            self.stateDatauPblished = (.progressString(self.generating(progress: 0.4)),nil)
-//        }
-//        
-//        kDelayOnMainThread(0.6) {
-//            self.stateDatauPblished = (.progressString(self.generating(progress: 0.6)),nil)
-//        }
-//        
-//        kDelayOnMainThread(0.8) {
-//            self.stateDatauPblished = (.progressString(self.generating(progress: 0.8)),nil)
-//        }
-//        
-//        kDelayOnMainThread(1.0) {
-//            if Bool.random() {
-//                let infoModel = TSActionInfoModel(JSON: actionInfoDict)
-//                self.stateDatauPblished = (.success(nil),infoModel)
-//            }else{
-//                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
-//            }
-//        }
-//    }
-    
- 
     func creatRintone(text:String) {
-        generatingProgress = 0
-        aiText = text
-        let postDict:[String : Any] = [
-            "prompt":text,
-            "duration":10
-        ]
+
         stateDatauPblished = (.start,nil)
         stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-        creatRequest = TSNetworkShared.post(urlType: .musicCreate,parameters: postDict) { [weak self] data,error in
-            guard let self = self else { return }
-            if let dataDict = data as? [String:Any] ,
-               dataDict.safeInt(forKey: "code") == 200,
-               let actionId = dataDict["actionId"] as? Int{
-                if stopNetwork == false {
-                    self.getActionInfo(action_id:actionId)
-                }
+
+        kDelayOnMainThread(0.2) {
+            self.stateDatauPblished = (.progressString(self.generating(progress: 0.2)),nil)
+        }
+        
+        kDelayOnMainThread(0.4) {
+            self.stateDatauPblished = (.progressString(self.generating(progress: 0.4)),nil)
+        }
+        
+        kDelayOnMainThread(0.6) {
+            self.stateDatauPblished = (.progressString(self.generating(progress: 0.6)),nil)
+        }
+        
+        kDelayOnMainThread(0.8) {
+            self.stateDatauPblished = (.progressString(self.generating(progress: 0.8)),nil)
+        }
+        
+        kDelayOnMainThread(1.0) {
+            if Bool.random() {
+                let infoModel = TSActionInfoModel(JSON: actionInfoDict)
+                self.stateDatauPblished = (.success(nil),infoModel)
             }else{
-                self.stateDatauPblished = (.failed(error?.localizedDescription ?? ""),nil)
+                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
             }
         }
     }
     
+ 
+//    func creatRintone(text:String) {
+//        generatingProgress = 0
+//        aiText = text
+//        let postDict:[String : Any] = [
+//            "prompt":text,
+//            "duration":10
+//        ]
+//        stateDatauPblished = (.start,nil)
+//        stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
+//        creatRequest = TSNetworkShared.post(urlType: .musicCreate,parameters: postDict) { [weak self] data,error in
+//            guard let self = self else { return }
+//            if let dataDict = data as? [String:Any] ,
+//               dataDict.safeInt(forKey: "code") == 200,
+//               let actionId = dataDict["actionId"] as? Int{
+//                if stopNetwork == false {
+//                    self.getActionInfo(action_id:actionId)
+//                }
+//            }else{
+//                self.stateDatauPblished = (.failed(error?.localizedDescription ?? ""),nil)
+//            }
+//        }
+//    }
+    
     func getActionInfo(action_id:Int){
         queryRequest = TSNetworkShared.get(urlType: .actionInfo,parameters: ["action_id":action_id]) { [weak self] data,error in
             guard let self = self else { return }

+ 11 - 11
AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/TSTextGeneralRintoneVC.swift

@@ -99,15 +99,15 @@ class TSTextGeneralRintoneVC: TSBaseVC {
     }
     
     override func dealThings() {
-//        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+        self.creatBtnView.setVip(vip: kPurchaseDefault.isVip)
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
     }
     
-//    @objc func vipInfoChanged() {
-//        kExecuteOnMainThread {
-//            self.vipBtn.isHidden = PurchaseManager.default.isVip
-//            self.collectionComponent.reloadData()
-//        }
-//    }
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.creatBtnView.setVip(vip: kPurchaseDefault.isVip)
+        }
+    }
     
     @objc func clickView() {
         view.endEditing(true)
@@ -122,10 +122,10 @@ class TSTextGeneralRintoneVC: TSBaseVC {
 extension TSTextGeneralRintoneVC {
     func generateImage() {
         
-//        //判断 vip
-//        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .textGeneratePic) == false, vc: self) {[weak self] in
-//            guard let self = self else { return }
-//        }{ return }
+        //判断 vip
+        if kJudgeVip(externalBool: kPurchaseDefault.freeNumAvailable(type: .ringtones) == false, vc: self) {[weak self] in
+            guard let self = self else { return }
+        }{ return }
         
         let gennerateVC = TSGeneralRintoneVC(aiText:viewModel.prompt)
         {[weak self] model in

+ 383 - 0
AIRingtone/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift

@@ -0,0 +1,383 @@
+//
+//  TSPurchaseVC.swift
+//  TSLiveWallpaper
+//
+//  Created by 100Years on 2025/1/14.
+//
+
+import Combine
+import SwiftUI
+import SwiftUIX
+class PurchaseViewModel : ObservableObject{
+    
+    @Published var selectedType: PremiumPeriod = .month
+    
+    /// 订阅publisher
+    let buyPublisher  = PassthroughSubject<Bool,Never>()
+    /// 隐私
+    let privacyPublisher = PassthroughSubject<Bool, Never>()
+    /// term
+    let termPublisher = PassthroughSubject<Bool, Never>()
+    /// restore
+    let restorePublisher = PassthroughSubject<Bool, Never>()
+}
+
+
+class TSPurchaseVC: TSBaseVC {
+    
+    var closePageBlock:(()->Void)?
+    
+    var viewModel: PurchaseViewModel = .init()
+    var cancellabel: [AnyCancellable] = []
+    var buyPeriod:PremiumPeriod = .year
+    lazy var purchaseManager: PurchaseManager = {
+        let purchaseManager = kPurchaseDefault
+        return purchaseManager
+    }()
+    
+    lazy var hostVc: UIHostingController<PurchaseView> = {
+        let vc = UIHostingController(rootView: PurchaseView(viewModel: viewModel))
+        vc.view.backgroundColor = .clear
+        return vc
+    }()
+    
+    override func createView() {
+        addNormalNavBarView()
+        _ = setNavigationItem("", imageName: "close_gray", direction: .left, action: #selector(closePage))
+        setViewBgImageNamed(named: "purchase_bj")
+
+        contentView.addSubview(hostVc.view)
+        hostVc.view.snp.makeConstraints { make in
+            make.leading.trailing.bottom.top.equalToSuperview()
+        }
+    }
+    
+    override func dealThings() {
+        addNotifaction()
+        onPurchaseStateChanged()
+    }
+    
+    
+    func addNotifaction() {
+
+        viewModel.buyPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
+            guard let self = self else {
+                return
+            }
+            kPurchaseDefault.pay(for: self.viewModel.selectedType)
+        }.store(in: &cancellabel)
+
+        viewModel.privacyPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
+            guard let self = self else {
+                return
+            }
+            
+            let vc = TSBusinessWebVC(urlType: .privacy)
+            vc.hidesBottomBarWhenPushed = true
+            kPresentModalVC(target: self, modelVC: vc)
+            
+        }.store(in: &cancellabel)
+
+        viewModel.termPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
+            guard let self = self else {
+                return
+            }
+            
+            let vc = TSBusinessWebVC(urlType: .terms)
+            vc.hidesBottomBarWhenPushed = true
+            kPresentModalVC(target: self, modelVC: vc)
+
+        }.store(in: &cancellabel)
+
+        viewModel.restorePublisher.receive(on: DispatchQueue.main).sink { _ in
+            kPurchaseDefault.restorePremium()
+        }.store(in: &cancellabel)
+    }
+    
+    
+    func onPurchaseStateChanged(){
+        purchaseManager.onPurchaseStateChanged = { [weak self] manager,state,object in
+            guard let self = self else { return }
+        
+            DispatchQueue.main.async {
+                switch state {
+                case .none:
+                    break
+                case .loading:
+                    TSToastShared.showLoading(text: "Getting price".localized,containerView: self.view)
+                case .loadSuccess:
+                    TSToastShared.hideLoading()
+                case .loadFail:
+                    TSToastShared.hideLoading()
+                    let message = "Get price failure, Will automatically retry in 5 seconds".localized
+                    TSToastShared.showToast(text: message)
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
+                        kPurchaseDefault.requestProducts()
+                    }
+                case .paying:
+                    TSToastShared.showLoading(text: "Purchasing now".localized,containerView: self.view)
+                case .paySuccess:
+                    TSToastShared.hideLoading()
+                    let loadingText = manager.isVip ? "Congratulation you have become VIP".localized : "Finish".localized
+                    TSToastShared.showToast(text:loadingText)
+                    if manager.isVip {
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                            self.closePage()
+                        }
+                    }
+
+                case .payFail:
+                    TSToastShared.hideLoading()
+                    if let str = object as? String {
+                        TSToastShared.showToast(text: str)
+                    }
+                    
+                case .restoreing:
+                    TSToastShared.showLoading(text: "Restoring now".localized,containerView: self.view)
+                case .restoreSuccess:
+                    TSToastShared.hideLoading()
+                    let loadingText = manager.isVip ? "Congratulation you have become VIP".localized : "Couldn't Restore Subscription".localized
+                    debugPrint(loadingText)
+                    TSToastShared.showToast(text:loadingText)
+                    if manager.isVip {
+                        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+                            self.closePage()
+                        }
+                    }
+
+                case .restoreFail:
+                    TSToastShared.hideLoading()
+                    let loadingText = (object as? String) ?? "Failed to restore subscribe, please try again".localized
+                    debugPrint(loadingText)
+                    TSToastShared.showToast(text: loadingText)
+                case .verifying:
+                    #if DEBUG
+                    TSToastShared.showLoading(text: "Verifying receipt...".localized,containerView: self.view)
+                    #endif
+                case .verifySuccess:
+                    break
+                case .verifyFail:
+                #if DEBUG
+                    TSToastShared.hideLoading()
+                    let message = (object as? String) ?? "Verify receipt failed"
+                    TSToastShared.showToast(text:message)
+
+                #endif
+                }
+            }
+            debugPrint("PurchaseManager onPurchaseStateChanged=\(String(describing: state))")
+        }
+    }
+    
+    @objc func closePage(){
+        closePageBlock?()
+        TSToastShared.hideLoading()
+        self.dismiss(animated: true)
+    }
+    
+    
+    deinit {
+        cancellabel.removeAll()
+    }
+}
+
+
+
+
+
+
+func kJudgeVip(externalBool:Bool,
+               vc:UIViewController,
+               closePageBlock:(()->Void)? = nil) -> Bool {
+    //判断 vip
+    if externalBool,
+       kPurchaseDefault.isVip == false
+    {
+        TSPurchaseVC.show(target: vc, closePageBlock: nil)
+        return true
+    }
+    return false
+}
+
+
+extension TSPurchaseVC{
+    
+    static func show(target:UIViewController,closePageBlock:(()->Void)?){
+        let vc = TSPurchaseVC()
+        vc.closePageBlock = closePageBlock
+        let navi = TSBaseNavigationC(rootViewController: vc)
+        navi.modalPresentationStyle = .overFullScreen
+        target.present(navi, animated: true)
+    }
+
+}
+
+
+struct PurchaseView :View {
+    
+    @ObservedObject var viewModel: PurchaseViewModel
+    
+    var body: some View {
+        ScrollView {
+            Spacer().frame(height: 44)
+            
+            VStack {
+                Image("vip_big_icon").resizable().frame(width: 131, height: 114)
+                Spacer().frame(height: 13)
+                
+                Text("AI Ringtone Pro")
+                    .font(.font(name: .PoppinsBlackItalic,size: 30))
+                    .gradientForeground(
+                        colors: [Color.white, "#F7B7FF".uiColor.color],
+                        startPoint: UnitPoint.top,
+                        endPoint: UnitPoint.bottom
+                    )
+                    .frame(height: 30)
+   
+                Spacer().frame(height: 36)
+                
+                ZStack {
+                    VStack(alignment: .leading,spacing: 20) {
+                        
+                        HStack(spacing: 16) {
+                            Image("vip_ringtone").resizable().frame(width: 32, height: 32)
+                            Text("Unlimited ringtones generation")
+                        }
+                        
+                        HStack(spacing: 16) {
+                            Image("vip_pic").resizable().frame(width: 32, height: 32)
+                            Text("Generate unlimited contact poster&photo")
+                        }
+                        
+                        HStack(spacing: 16) {
+                            Image("vip_photo").resizable().frame(width: 32, height: 32)
+                            Text("Unlock all premium calling theme")
+                        }
+                        
+                        HStack(spacing: 16) {
+                            Image("vip_ads").resizable().frame(width: 32, height: 32)
+                            Text("100% No Ads").multilineTextAlignment(.leading)
+                        }
+                        
+                    }.font(.font(size: 14)).foregroundColor(UIColor.white.color)
+                }
+         
+            }
+            
+            Spacer().frame(height: 25)
+            
+            
+            VStack(spacing: 12) {
+                
+                ZStack(alignment: .topTrailing) {
+                    PurchaseItemView(title: "One Month", type: .month, selectedType: $viewModel.selectedType).onTapGesture {
+                        viewModel.selectedType = .month
+                    }
+//                    TSVipRecView()
+//                        .offset(y:-14)
+                }
+          
+//                HStack {
+//                    PurchaseItemView(title: "One Year", type: .year, selectedType: $viewModel.selectedType).onTapGesture {
+//                        viewModel.selectedType = .year
+//                    }
+//                    
+//                    PurchaseItemView(title: "One Week", type: .week, selectedType: $viewModel.selectedType).onTapGesture {
+//                        viewModel.selectedType = .week
+//                    }
+//                }
+
+                Button {
+                    viewModel.buyPublisher.send(true)
+                } label: {
+                    ZStack {
+                        Color.hex("#E661F6").frame(height: 48)
+//                        "#E661F6".uiColor.color
+//                        Image("submit_btn_bg").resizable().aspectRatio(contentMode: .fill)
+                        Text("Continue")
+                            .font(.system(size: 16))
+                            .foregroundColor(.white)
+                            
+                    }.frame(maxWidth: .infinity ,maxHeight: 48.0)
+                        .cornerRadius(24.0)
+                }
+                
+                HStack {
+                    Text("Recurring billing, cancel anytime")
+                        .foregroundColor(Color.hex("#E661F6")) +
+                        Text(", Payment will be charged to your iTunes account at confirmation of purchase. Subscriptions automatically renew for the same applicable term and price, unless auto-renew is turned off at least 24 hours before the end of the current period.")
+                        .foregroundColor(UIColor.lesserText.color)
+                }
+                .multilineTextAlignment(.center).font(.font(size: 8))
+                .onTapGesture {
+                    viewModel.privacyPublisher.send(true)
+                }
+
+                HStack(spacing: 8) {
+                    Text("Term of us")
+                        .onTapGesture {
+                            viewModel.termPublisher.send(true)
+                        }
+                    Text("|")
+                    Text("Privacy Policy")
+                        .onTapGesture {
+                            viewModel.privacyPublisher.send(true)
+                        }
+                    Text("|")
+                    Text("Restore")
+                        .onTapGesture {
+                            viewModel.restorePublisher.send(true)
+                        }
+                }.font(.system(size: 12)).foregroundColor(.hex("#999999"))
+            }.padding(.horizontal)
+        }
+    }
+}
+
+
+struct PurchaseItemView: View {
+    var title: String
+    var type: PremiumPeriod
+    @Binding var selectedType: PremiumPeriod
+
+    var body: some View {
+        ZStack {
+            Color.clear
+            HStack {
+                VStack(alignment: .leading, spacing: 8) {
+                    Text(title).font(.font(size: 14)).foregroundColor(UIColor.textAssist.color)
+                    Text(kPurchaseDefault.price(for: type) ?? "--").font(.font(size: 18,weight: .medium)).foregroundColor(UIColor.mainText.color)
+                }
+                Spacer()
+                if type == selectedType {
+                    Image(.radioboxSelected)
+                }
+            }.padding(.horizontal)
+        }
+        .frame(height: 74) // 设置高度
+        .cornerRadius(16.0) // 圆角
+       
+        .overlay(
+            RoundedRectangle(cornerRadius: 16)
+                .stroke(Color.hex("#E661F6"), lineWidth: type == selectedType ? 1 : 0) // 边框
+        )
+        .shadow(color: Color.hex("#E661F6"), radius: 4, x: 0, y: 1)
+    }
+}
+
+//推荐选择view
+struct TSVipRecView: View {
+    var body: some View {
+        
+        let corner = 16.0
+        
+        HStack(spacing: 4) {
+            Image("upvote_black").resizable().frame(width: 16, height: 16)
+            Text("80% Choose").font(.font(size: 12,weight: .medium)).foregroundColor(.hex("#111111"))
+        }
+        .padding(EdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 6))
+        .background(Color.hex("#FECB34"))
+        .frame(height: 28) // 设置高度
+        .cornerRadius([.topLeading, .topTrailing, .bottomLeading], 16.0)
+    }
+}

+ 1 - 1
AIRingtone/Business/TSSetingVC/SetingVC/TSSetingModel.swift

@@ -6,7 +6,7 @@
 //
 
 enum SettingType: String, CaseIterable {
-//    case howToUse = "How to use?"
+    case tutorial = "Tutorial"
     case rateus = "Rate us"
     case shareus = "Share us"
     case privacy = "Privacy Policy"

+ 8 - 8
AIRingtone/Business/TSSetingVC/SetingVC/TSSetingVC.swift

@@ -74,8 +74,8 @@ class TSSetingVC: TSBaseVC {
             }
             
             switch type {
-//            case .howToUse:
-//                viewModel.pushTutorials(parent: self)
+            case .tutorial:
+                viewModel.pushTutorials(parent: self)
             case .shareus:
                 viewModel.shareApp(parent: self)
             case .agreement:
@@ -90,12 +90,12 @@ class TSSetingVC: TSBaseVC {
             
         }.store(in: &cancellable)
         
-//        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
     }
     
-//    @objc func vipInfoChanged() {
-//        kExecuteOnMainThread {
-//            self.viewModel.isViper = PurchaseManager.default.isVip
-//        }
-//    }
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.viewModel.isViper = kPurchaseDefault.isVip
+        }
+    }
 }

+ 6 - 6
AIRingtone/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift

@@ -10,7 +10,7 @@ import StoreKit
 class TSSetingViewModel: ObservableObject {
     
     @Published var settingTypes: [SettingType] = SettingType.allCases
-//    @Published var isViper: Bool = PurchaseManager.default.isVip
+    @Published var isViper: Bool = kPurchaseDefault.isVip
     
     var appid = "6741563341"
     // todo.kailen-privacy
@@ -78,11 +78,11 @@ class TSSetingViewModel: ObservableObject {
     
     // todo.kailen-logo
     func pushVipPurchase(parent: UIViewController) {
-//        TSPurchaseVC.show(target: parent) {[weak self]  in
-//            guard let self = self else { return }
-//            isViper = PurchaseManager.default.isVip
-//        }
-        pushTutorials(parent: parent)
+        TSPurchaseVC.show(target: parent) {[weak self]  in
+            guard let self = self else { return }
+            isViper = kPurchaseDefault.isVip
+        }
+//        pushTutorials(parent: parent)
     }
 }
 

+ 78 - 54
AIRingtone/Business/TSSetingVC/SetingVC/View/SettingPurchaseTopView.swift

@@ -11,61 +11,85 @@ struct SettingPurchaseTopView: View {
     var body: some View {
         ZStack {
             
-//            Image(.settingVipBj).resizable().frame(width: 343*kDesignScale, height: 118*kDesignScale)
+            Image(.settingVipBj).resizable().frame(width: 343*kDesignScale, height: 109*kDesignScale)
             
-//            if isViper {
-//                
-//                VStack {
-//                    Spacer().frame(height: 20)
-//                    
-//                    Image(.settingVip).resizable().frame(width: 210*kDesignScale, height: 50*kDesignScale)
-//                    
-//                    Spacer()
-//                    
-//                    Text("Due Date:".localized + " \(PurchaseManager.default.expiredDateString)")
-//                        .foregroundColor(.white.opacity(0.6))
-//                        .font(.font(size: 14,weight: .medium))
-//                        .frame(height: 14)
-//
-//                    Spacer().frame(height: 22)
-//                }
-//            }else {
-//                
-//                HStack {
-//                    Spacer().frame(width:20)
-//                    
-//                    VStack(alignment: .leading,spacing: 12) {
-//                        Image(.settingNoVip).resizable().frame(width: 186*kDesignScale, height: 44*kDesignScale)
-//                        
-//                        HStack {
-//                            Text("Limited Time Discount")
-//                                .font(.font(size: 14))
-//                                .frame(height: 14)
-//                                .foregroundColor(UIColor.white.withAlphaComponent(0.6).color)
-//                            
-//                            Spacer()
-//                        }
-//                    }
-//                    
-//                    Spacer()
-//                    
-//                    Text("Upgrade")
-//                        .font(.font(size: 14,weight: .medium))
-//                        .padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
-//                        .frame(height: 26) // 设置高度
-//                        .foregroundColor("#010101".uiColor.color)
-//                        .background(UIColor.themeColor.color)
-//                        .cornerRadius(13.0) // 圆角
-//                        .onTapGesture {
-//                            if PurchaseManager.default.isVip {
-//                                return
-//                            }
-//                            eventPublisher.enterPurchasePublisher.send(true)
-//                        }
-//
-//                        Spacer().frame(width: 15)
-//                }
-//            }
+            if isViper {
+                HStack {
+                    Spacer().frame(width:20)
+                    VStack(alignment: .leading,spacing: 12) {
+                        HStack {
+                            Text("AI Ringtone Pro")
+                                .font(.font(name: .PoppinsBlackItalic,size: 20))
+                                .gradientForeground(
+                                    colors: [Color.white, "#F7B7FF".uiColor.color],
+                                    startPoint: UnitPoint.top,
+                                    endPoint: UnitPoint.bottom
+                                )
+                                .frame(height: 20)
+                            
+                            Spacer()
+                        }
+                        
+                        HStack {
+                            Text("Due Date:".localized + " \(kPurchaseDefault.expiredDateString)")
+                                .foregroundColor(.white.opacity(0.6))
+                                .font(.font(size: 14,weight: .medium))
+                                .frame(height: 14)
+                            Spacer()
+                        }
+                    }
+                    
+                    Spacer()
+                    
+                    Image(.vipBigIcon).resizable().frame(width: 88, height: 78)
+                }
+            }else {
+                
+                HStack {
+                    Spacer().frame(width:20)
+                    VStack(alignment: .leading,spacing: 12) {
+                        HStack {
+                            Text("Get Pro")
+                                .font(.font(name: .PoppinsBlackItalic,size: 20))
+                                .gradientForeground(
+                                    colors: [Color.white, "#F7B7FF".uiColor.color],
+                                    startPoint: UnitPoint.top,
+                                    endPoint: UnitPoint.bottom
+                                )
+                                .frame(height: 20)
+                            
+                            Spacer()
+                        }
+                        
+                        HStack {
+                            Text("Limited Time Discount")
+                                .font(.font(size: 14))
+                                .frame(height: 14)
+                                .foregroundColor(UIColor.white.withAlphaComponent(0.6).color)
+                            
+                            Spacer()
+                        }
+                    }
+                    
+                    Spacer()
+                    
+                    Text("Upgrade")
+                        .font(.font(size: 14,weight: .medium))
+                        .padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
+                        .frame(height: 26) // 设置高度
+                        .foregroundColor("#010101".uiColor.color)
+                        .background(UIColor.themeColor.color)
+                        .cornerRadius(13.0) // 圆角
+                        .onTapGesture {
+                            if kPurchaseDefault.isVip {
+                                return
+                            }
+                            eventPublisher.enterPurchasePublisher.send(true)
+                        }
+
+                        Spacer().frame(width: 15)
+                }
+            }
         }
     }
 }

+ 4 - 10
AIRingtone/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift

@@ -16,21 +16,15 @@ struct TSSettingListView: View {
             VStack(spacing: 0) {
                 Spacer().frame(height: 12)
                 
-                Image("theme_banner").resizable()
+                SettingPurchaseTopView(eventPublisher: publisher, isViper: $viewModel.isViper)
                     .frame(height: 109*kDesignScale)
                     .onTapGesture {
+                        if kPurchaseDefault.isVip {
+                            return
+                        }
                         publisher.enterPurchasePublisher.send(true)
                     }
                 
-//                SettingPurchaseTopView(eventPublisher: publisher, isViper: $viewModel.isViper)
-//                    .frame(height: 117*kDesignScale)
-//                    .onTapGesture {
-//                        if PurchaseManager.default.isVip {
-//                            return
-//                        }
-//                        publisher.enterPurchasePublisher.send(true)
-//                    }
-                
                 ForEach(viewModel.settingTypes, id:\.self) { type in
                     Spacer().frame(height: 16)
                     SettingListItemView(type: type)

+ 27 - 1
AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/TSThemeBrowseVC.swift

@@ -145,10 +145,36 @@ class TSThemeBrowseVC: TSBaseVC {
         kDelayMainShort {
             self.collectionView.scrollToItem(at: IndexPath(item: self.currentIndex, section: 0), at: .bottom, animated: false)
         }
-
+        
         browseViewModel.finallyIndex = currentIndex
+        browseViewModel.finallyIndexChange = {[weak self]  in
+            guard let self = self else { return }
+            setVipUI()
+        }
+        self.btnView.doneBtn.setImage(UIImage(named: "vip_icon_white"), for: .normal)
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+        setVipUI()
     }
     
+    @objc func vipInfoChanged() {
+        self.setVipUI()
+    }
+    
+    func setVipUI(){
+        kExecuteOnMainThread {
+            self.btnView.doneBtn.setImage(self.getNeedVip ? UIImage(named: "vip_icon_white"): nil, for: .normal)
+        }
+    }
+    
+    var getNeedVip:Bool{
+        if let currentModel = self.currentModel {
+            if currentModel.vip,kPurchaseDefault.isVip == false {
+                return true
+            }
+        }
+        return false
+    }
+
 }
 
 extension TSThemeBrowseVC {

+ 4 - 0
AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/VM/TSThemeBrowseVM.swift

@@ -10,12 +10,16 @@ class TSThemeBrowseVM {
     //vc 的 currentIndex 是根据滚动视图平凡变动的,finallyIndex只会在有新值时候产生变动
     var finallyIndex:Int = -1
 
+    
+    var finallyIndexChange:(()->Void)?
+    
     //return  发生了改变
     func setFinallyIndex(_ currentIndex:Int,ringtone:String?){
         if finallyIndex != currentIndex{
             finallyIndex = currentIndex
             audioPlayer?.stop()
             audioPlayer?.playRingtone(ringtone: ringtone)
+            finallyIndexChange?()
         }
     }
 }

+ 0 - 4
AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/View/TSTBBtnView.swift

@@ -98,10 +98,6 @@ class TSTBBtnView: TSBaseView {
         setUpNavi()
         setIcons()
         setDone()
-        
-        kDelayMainShort {
-            self.doneBtn.addCornerRadiusShadow(cornerRadius: 24, shadowColor: UIColor.themeColor.cgColor, shadowOffset: .zero, shadowRadius: 5, shadowOpacity: 0.5)
-        }
     }
         
         

+ 9 - 1
AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetItemView.swift

@@ -27,10 +27,11 @@ class TSThemeSetItemView: TSBaseView {
     
     let setBtn:UIButton = UIButton.createButton(title: "Set Now".localized,backgroundColor: .themeColor,font: .font(size: 16),titleColor: "#111111".uiColor,corner: 16)
     
+    let moreInfoBtn = kCreateMoreInfoBtn()
     
     override func creatUI() {
         
-//        setBtn.contentEdgeInsets = UIEdgeInsets(top:0, left: 16, bottom: 0, right: 16)
+        setBtn.imageEdgeInsets = UIEdgeInsets(top:0, left: -8, bottom: 0, right: 16)
         
         contentView.snp.updateConstraints { make in
             make.top.equalTo(16)
@@ -39,6 +40,7 @@ class TSThemeSetItemView: TSBaseView {
         contentView.addSubview(bgImageView)
         contentView.addSubview(numImageView)
         contentView.addSubview(titleLabel)
+        contentView.addSubview(moreInfoBtn)
         contentView.addSubview(diyView)
         contentView.addSubview(setBtn)
         
@@ -59,6 +61,12 @@ class TSThemeSetItemView: TSBaseView {
             make.top.equalTo(20)
         }
         
+        moreInfoBtn.snp.makeConstraints { make in
+            make.leading.equalTo(titleLabel.snp.trailing).offset(8)
+            make.width.height.equalTo(16)
+            make.centerY.equalTo(titleLabel)
+        }
+        
         diyView.snp.makeConstraints { make in
             make.leading.trailing.equalTo(0)
             make.top.equalTo(60)

+ 2 - 2
AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetRingToneView.swift

@@ -29,7 +29,7 @@ class TSThemeSetPosterView: TSThemeSetItemView {
     let netWorkImageView = UIImageView()
     override func creatUI() {
         super.creatUI()
-        titleLabel.text = "Poster".localized
+        titleLabel.text = "Contact Poster".localized
         numImageView.image = UIImage(named: "num_2")
         iconImageView.image = UIImage(named: "theme_icon_poster")
         setBtn.setTitle("Download".localized, for: .normal)
@@ -48,7 +48,7 @@ class TSThemeSetPhotorView: TSThemeSetItemView {
     let netWorkImageView = UIImageView()
     override func creatUI() {
         super.creatUI()
-        titleLabel.text = "Photo".localized
+        titleLabel.text = "Contact Photo".localized
         netWorkImageView.cornerRadius = 80
         numImageView.image = UIImage(named: "num_3")
         iconImageView.image = UIImage(named: "theme_icon_photo")

+ 49 - 0
AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetVC.swift

@@ -10,6 +10,14 @@ class TSThemeSetVC: TSBaseVC {
     
     var model:TSThemeModel
     var duration:Float
+    
+    var getNeedVip:Bool{
+//        return true
+        if model.vip,kPurchaseDefault.isVip == false {
+            return true
+        }
+        return false
+    }
 
     lazy var audioPlayer = TSBusinessAudioPlayer{ [weak self] state in
         guard let self = self else { return }
@@ -29,6 +37,7 @@ class TSThemeSetVC: TSBaseVC {
     
     lazy var ringView: TSThemeSetRingToneView = {
         let ringView = TSThemeSetRingToneView()
+        ringView.moreInfoBtn.isHidden = true
         ringView.setBtn.addTarget(self, action: #selector(clickSetRing), for: .touchUpInside)
         ringView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickPlay)))
         return ringView
@@ -37,12 +46,14 @@ class TSThemeSetVC: TSBaseVC {
     lazy var posterView: TSThemeSetPosterView = {
         let posterView = TSThemeSetPosterView()
         posterView.setBtn.addTarget(self, action: #selector(clickSetPoster), for: .touchUpInside)
+        posterView.moreInfoBtn.addTarget(self, action: #selector(clickSetMoreInfoPoster), for: .touchUpInside)
         return posterView
     }()
     
     lazy var photoView: TSThemeSetPhotorView = {
         let photoView = TSThemeSetPhotorView()
         photoView.setBtn.addTarget(self, action: #selector(clickPhoto), for: .touchUpInside)
+        posterView.moreInfoBtn.addTarget(self, action: #selector(clickSetMoreInfoPhoto), for: .touchUpInside)
         return photoView
     }()
     
@@ -93,8 +104,25 @@ class TSThemeSetVC: TSBaseVC {
         ringView.cellView.timeLab.text = duration.floatToMinuteSecond()
         posterView.netWorkImageView.setAsyncImage(urlString: model.poster,contentMode: .scaleAspectFill)
         photoView.netWorkImageView.setAsyncImage(urlString: model.photo,contentMode: .scaleAspectFill)
+        
+
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+        setVipUI()
     }
     
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.setVipUI()
+        }
+    }
+    
+    func setVipUI(){
+        self.ringView.setBtn.setImage(getNeedVip ? UIImage(named: "vip_icon_black"): nil, for: .normal)
+        self.posterView.setBtn.setImage(getNeedVip ? UIImage(named: "vip_icon_black"): nil, for: .normal)
+        self.photoView.setBtn.setImage(getNeedVip ? UIImage(named: "vip_icon_black"): nil, for: .normal)
+    }
+    
+
     func audioPlayerStateChange(state:TSBusinessAudioPlayer.PlayerState){
         switch state {
         case .loading(let progress):
@@ -135,12 +163,20 @@ extension TSThemeSetVC {
     }
     
     @objc func clickSetRing(){
+        
+        //判断 vip
+        if kJudgeVip(externalBool: getNeedVip, vc: self) { return }
+        
         ringTool.shareBand(with: model.ringtone, fileName: model.name) { success in
             
         }
     }
     
     @objc func clickSetPoster(){
+        
+        //判断 vip
+        if kJudgeVip(externalBool: getNeedVip, vc: self){ return }
+        
         if let image = posterView.netWorkImageView.image{
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
@@ -155,6 +191,11 @@ extension TSThemeSetVC {
     }
     
     @objc func clickPhoto(){
+        
+        //判断 vip
+        if kJudgeVip(externalBool: getNeedVip, vc: self)
+        { return }
+        
         guard let photo = avatarImage else { return }
         contactsTool.setContactsAvatar(avatarImage: photo) { data, error in
             if error == nil {
@@ -162,6 +203,14 @@ extension TSThemeSetVC {
             }
         }
     }
+    
+    @objc func clickSetMoreInfoPoster(){
+        kPresentModalVC(target: self, modelVC: TSTutorialPopupVC(),transitionStyle: .crossDissolve)
+    }
+    
+    @objc func clickSetMoreInfoPhoto(){
+        kPresentModalVC(target: self, modelVC: TSTutorialPopupVC(),transitionStyle: .crossDissolve)
+    }
 
 }
 

+ 12 - 14
AIRingtone/Business/TSThemeVC/TSThemeTutorialsVC/TSThemeTutorialsVC.swift

@@ -15,20 +15,6 @@ class TSThemeTutorialsVC: TSBaseVC {
     let titles:[String] = ["Ringtone".localized,"Poster".localized,"Photo".localized]
     let tutorialsImageNamed:[String] = ["tutorials_ringtone","tutorials_poster","tutorials_photo"]
 
-    lazy var pagingView: JXPagingView = {
-        //lazy var pagingView: JXPagingView = JXPagingListRefreshView(delegate: self) //整个刷新
-//        let pagingView = JXPagingListRefreshView(delegate: self) //list 刷新
-        let pagingView = JXPagingView(delegate: self)
-        pagingView.mainTableView.backgroundColor = .clear
-        pagingView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: k_ScreenHeight - k_Nav_Height)// - CGFloat(headerInSectionHeight))
-        pagingView.listContainerView.listCellBackgroundColor = .clear
-        //扣边返回处理,下面的代码要加上
-        pagingView.listContainerView.scrollView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
-        pagingView.mainTableView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
-        return pagingView
-    }()
-
-    
     lazy var segmentedDataSource: JXSegmentedTitleDataSource = {
         let dataSource = JXSegmentedTitleDataSource()
         dataSource.isTitleColorGradientEnabled = true
@@ -72,6 +58,18 @@ class TSThemeTutorialsVC: TSBaseVC {
         return segmentedView
     }()
  
+    lazy var pagingView: JXPagingView = {
+        let pagingView = JXPagingView(delegate: self)
+        pagingView.mainTableView.backgroundColor = .clear
+        pagingView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: k_ScreenHeight - k_Nav_Height)
+        pagingView.listContainerView.listCellBackgroundColor = .clear
+        //扣边返回处理,下面的代码要加上
+        pagingView.listContainerView.scrollView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
+        pagingView.mainTableView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
+        return pagingView
+    }()
+
+    
     override func createView() {
         addNormalNavBarView()
         setPageTitle("Tutorial".localized)

+ 180 - 0
AIRingtone/Business/TSTutorialsVC/TSTutorialPopupVC.swift

@@ -0,0 +1,180 @@
+//
+//  Untitled.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/13.
+//
+
+import JXSegmentedView
+import JXPagingView
+
+class TSTutorialPopupVC: TSBaseVC {
+    
+    var tableHeaderViewHeight: CGFloat = 0.0
+    var headerInSectionHeight: Int = 44
+    let titles:[String] = ["Contact Poster".localized,"Contact Photo".localized]
+    let popupContentViewW:CGFloat = k_ScreenWidth - 40.0
+    let popupContentViewH:CGFloat = k_ScreenHeight - 268.0
+    
+    lazy var popupContentView: UIView = {
+        let popupContentView = UIView()
+        popupContentView.backgroundColor = .cardColor
+        popupContentView.cornerRadius = 20.0
+        return popupContentView
+    }()
+    
+    lazy var segmentedDataSource: JXSegmentedTitleDataSource = {
+        let dataSource = JXSegmentedTitleDataSource()
+        dataSource.isTitleColorGradientEnabled = true
+        dataSource.itemSpacing = 16
+        dataSource.itemWidthIncrement = 32
+        
+        dataSource.isItemSpacingAverageEnabled = false
+        dataSource.titles = titles
+        dataSource.titleNormalColor = .assist
+        dataSource.titleSelectedColor = .white
+        dataSource.titleNormalFont = .font(size: 16.0)
+        return dataSource
+    }()
+    
+    //配置指示器
+    lazy var indicator: JXSegmentedIndicatorBackgroundView = {
+        let indicator = JXSegmentedIndicatorBackgroundView()
+        indicator.isIndicatorConvertToItemFrameEnabled = true
+        indicator.isIndicatorConvertToItemFrameEnabled = true
+        indicator.indicatorHeight = 2
+        indicator.indicatorPosition = .bottom
+        
+        let gradientView = JXSegmentedComponetGradientView()
+        gradientView.gradientLayer.endPoint = CGPoint(x: 0, y: 1)
+        gradientView.gradientLayer.endPoint = CGPoint(x: 1, y: 1)
+        gradientView.gradientLayer.colors = ["#E961F6".uiColor.cgColor, "#7E57F4".uiColor.cgColor]
+        gradientView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        gradientView.cornerRadius = 1
+        indicator.addSubview(gradientView)
+        indicator.addSubview(gradientView)
+        
+        return indicator
+    }()
+    
+
+    lazy var segmentedView: JXSegmentedView = {
+        let segmentedView = JXSegmentedView(frame: CGRect(x: 0, y: 0, width: popupContentViewW, height: CGFloat(headerInSectionHeight)))
+        segmentedView.indicators = [indicator]
+        segmentedView.dataSource = segmentedDataSource
+        segmentedView.backgroundColor = .clear
+        segmentedView.contentEdgeInsetLeft = 26
+        segmentedView.listContainer = pagingView.listContainerView
+        return segmentedView
+    }()
+    
+    lazy var pagingView: JXPagingView = {
+        let pagingView = JXPagingView(delegate: self)
+        pagingView.mainTableView.backgroundColor = .clear
+        pagingView.frame = CGRect(x: 0, y: 0, width:popupContentViewW, height:popupContentViewH)
+        pagingView.listContainerView.listCellBackgroundColor = .clear
+        //扣边返回处理,下面的代码要加上
+        pagingView.listContainerView.scrollView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
+        pagingView.mainTableView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
+        pagingView.pinSectionHeaderVerticalOffset = Int(TSTGPTitleView.viewH)
+        return pagingView
+    }()
+    
+    override func createView() {
+    
+        setNavBarViewHidden(true)
+        view.backgroundColor = .black.withAlphaComponent(0.7)
+        
+        
+        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickView)))
+        
+        contentView.addSubview(popupContentView)
+        popupContentView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.width.equalTo(popupContentViewW)
+            make.height.equalTo(popupContentViewH)
+        }
+        createSegmentedView()
+    }
+    
+    @objc func clickView() {
+        self.dismiss()
+    }
+}
+
+extension TSTutorialPopupVC {
+    
+    func createSegmentedView(){
+        popupContentView.addSubview(segmentedView)
+        popupContentView.addSubview(pagingView)
+    }
+}
+extension TSTutorialPopupVC: JXPagingViewDelegate {
+
+    func tableHeaderViewHeight(in pagingView: JXPagingView) -> Int {
+        return Int(tableHeaderViewHeight)
+    }
+
+    func tableHeaderView(in pagingView: JXPagingView) -> UIView {
+        return UIView()
+    }
+
+    func heightForPinSectionHeader(in pagingView: JXPagingView) -> Int {
+        return headerInSectionHeight
+    }
+
+    func viewForPinSectionHeader(in pagingView: JXPagingView) -> UIView {
+        return segmentedView
+    }
+
+    func numberOfLists(in pagingView: JXPagingView) -> Int {
+        return titles.count
+    }
+
+    func pagingView(_ pagingView: JXPagingView, initListAtIndex index: Int) -> JXPagingViewListViewDelegate {
+        return TSTutorialPopupView()
+    }
+}
+
+
+@objc public class TSTutorialPopupView: TSBaseView {
+    
+    var listViewDidScrollCallback: ((UIScrollView) -> ())?
+    lazy var stackView: TSCustomStackView = {
+        let stackView = TSCustomStackView()
+        stackView.scrollView.delegate = self
+        return stackView
+    }()
+    
+    public override func creatUI() {
+        self.backgroundColor = .random
+        
+        contentView.addSubview(stackView)
+        stackView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
+
+}
+
+extension TSTutorialPopupView: JXPagingViewListViewDelegate,UIScrollViewDelegate {
+    
+//    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
+//        self.listViewDidScrollCallback?(scrollView)
+//    }
+    
+    public func listScrollView() -> UIScrollView {
+        return UIScrollView()
+//        return stackView.scrollView
+    }
+    
+    public func listViewDidScrollCallback(callback: @escaping (UIScrollView) -> ()) {
+//        self.listViewDidScrollCallback = callback
+        callback(UIScrollView())
+    }
+    
+    public func listView() -> UIView {
+        return self
+    }
+    
+}

+ 26 - 0
AIRingtone/Business/VIewTool/TSButton.swift

@@ -0,0 +1,26 @@
+//
+//  TSButton.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/13.
+//
+
+
+class TSAppBtn: UIButton {
+ 
+}
+
+class TSNormalSubmitBtn: TSAppBtn {
+    override var isEnabled: Bool {
+       didSet {
+           // 根据 isEnabled 的值设置 alpha
+           self.alpha = isEnabled ? 1.0 : 0.6
+       }
+    }
+}
+
+
+class TSNormalCancelBtn: TSAppBtn {
+
+}
+

+ 28 - 4
AIRingtone/Business/VIewTool/TSViewTool.swift

@@ -23,15 +23,39 @@ func createBlurEffectView(style:UIBlurEffect.Style,backgroundColor:UIColor? = ni
     return blurEffectView
 }
 
-
 //常用提交按钮
 func kCreateNormalSubmitBtn(title:String, action: (() -> Void)? = nil) -> UIButton {
-    let btn = UIButton.createButton(title:title,backgroundColor: .black,font: UIFont.font(size: 16,weight: .regular),titleColor:.themeColor,corner: 24,action: action)
-    btn.layer.borderWidth = 1
-    btn.layer.borderColor = UIColor.themeColor.cgColor
+    
+    let btn = TSNormalSubmitBtn()
+    btn.setUpButton(title:title,font: UIFont.font(size: 16,weight: .regular),titleColor:.white,corner: 24,action: action)
+    btn.imageEdgeInsets = UIEdgeInsets(top:0, left: -8, bottom: 0, right: 16)
     btn.frame = CGRectMake(0, 0, 200, 48)
+    btn.addGradientBg(colors: ["#E961F6".uiColor.cgColor,"#7E57F4".uiColor.cgColor])
     return btn
 }
+
+func kAddNormalSubmitGradientBg(view:UIView){
+    kDelayMainShort {
+        view.addGradientBg(colors: ["#E961F6".uiColor.cgColor,"#7E57F4".uiColor.cgColor])
+    }
+}
+
+
+func kCreateMoreInfoBtn() -> UIButton{
+    let btn = TSUIExpandedTouchButton()
+    btn.setImage(UIImage(named: "more_info_white"), for: .normal)
+    return btn
+}
+
+
+////常用提交按钮
+//func kCreateNormalSubmitBtn(title:String, action: (() -> Void)? = nil) -> UIButton {
+//    let btn = UIButton.createButton(title:title,backgroundColor: .black,font: UIFont.font(size: 16,weight: .regular),titleColor:.themeColor,corner: 24,action: action)
+//    btn.layer.borderWidth = 1
+//    btn.layer.borderColor = UIColor.themeColor.cgColor
+//    btn.frame = CGRectMake(0, 0, 200, 48)
+//    return btn
+//}
 ////常用取消按钮
 //func kCreateNormalCancelBtn(title:String, action: (() -> Void)? = nil) -> UIButton {
 //    let btn = UIButton.createButton(title:title,backgroundColor: .fromHex("#FFFFFF", alpha: 0.4),font: UIFont.font(size: 14,weight: .medium),titleColor:.white,corner: 30,action: action)

+ 612 - 0
AIRingtone/Common/Purchase/TSPurchaseManager.swift

@@ -0,0 +1,612 @@
+//
+//  TSPurchaseManager.swift
+//  TSLiveWallpaper
+//
+//  Created by 100Years on 2025/1/13.
+//
+
+import Foundation
+import StoreKit
+
+public enum PremiumPeriod: String, CaseIterable {
+    case none           = ""
+    case week          = "Week"
+    case month          = "Monthly"
+    case year           = "Yearly"
+    case lifetime       = "Lifetime"
+}
+
+public enum VipFreeNumType: String, CaseIterable {
+    case ringtones           = "kRingtonesFreeNum"
+    case posetr              = "kPosetrFreeNum"
+    case photo               = "kPhotoFreeNum"
+}
+
+public struct PurchaseProduct {
+    public let productId: String
+    public let period: PremiumPeriod
+    
+    public init(productId: String, period: PremiumPeriod) {
+        self.productId = productId
+        self.period = period
+    }
+}
+
+public enum PremiumRequestState {
+    case none
+    
+    case loading
+    case loadSuccess
+    case loadFail
+    
+    case paying
+    case paySuccess
+    case payFail
+    
+    case restoreing
+    case restoreSuccess
+    case restoreFail
+    
+    case verifying
+    case verifySuccess
+    case verifyFail
+}
+
+
+public extension Notification.Name {
+    static let kPurchasePrepared = Self.init("kPurchaseProductPrepared")
+    static let kPurchaseDidChanged = Self.init("kPurchaseDidChanged")
+}
+
+private let kFreeNumKey = "kFreeNumKey"
+private let kPremiumExpiredInfoKey = "premiumExpiredInfoKey"
+
+
+typealias PurchaseStateChangeHandler = (_ manager: PurchaseManager, _ state: PremiumRequestState, _ object: Any?) -> Void
+
+let kPurchaseDefault = PurchaseManager.default
+public class PurchaseManager: NSObject {
+    @objc public static let `default` = PurchaseManager()
+
+    //苹果共享密钥
+    private let AppleSharedKey:String = "4ed5151881304025bfee0295ee459422"
+    
+    //商品信息
+    public lazy var purchaseProducts:[PurchaseProduct] = {
+        return [
+            PurchaseProduct(productId: "01", period:.month),
+//            PurchaseProduct(productId: "102", period:.year),
+//            PurchaseProduct(productId: "103", period:.week),
+            //PurchaseProduct(productId: "003", period: .lifetime),
+        ]
+    }()
+
+    struct Config {
+        static let verifyUrl = "https://buy.itunes.apple.com/verifyReceipt"
+        static let sandBoxUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
+    }
+
+    lazy var products: [SKProduct] = []
+
+    var onPurchaseStateChanged: PurchaseStateChangeHandler?
+
+    // 会员信息
+    var vipInformation: [String: Any] = [:]
+
+    // 免费使用会员的次数
+    var freeDict:[String:Int] = [:]
+        
+    //原始订单交易id dict
+    var originalTransactionIdentifierDict:[String:String] = [:]
+    
+    override init() {
+        super.init()
+
+        SKPaymentQueue.default().add(self)
+
+        if let info = UserDefaults.standard.object(forKey: kPremiumExpiredInfoKey) as? [String: Any] {
+            vipInformation = info
+        }
+        
+        initializeForFree()
+    }
+
+    public var expiredDate: Date? {
+        guard let time = vipInformation["expireTime"] as? String else {
+            return nil
+        }
+        return convertExpireDate(from: time)
+    }
+
+    public var expiredDateString: String {
+        if vipType == .lifetime{
+            return "Life Time"
+        } else {
+            if let expDate = expiredDate {
+                let format = DateFormatter()
+                format.locale = .current
+                format.dateFormat = "yyyy-MM-dd"
+                return format.string(from: expDate)
+            } else {
+                return "--"
+            }
+        }
+    }
+
+    private func convertExpireDate(from string: String) -> Date? {
+        if let ts = TimeInterval(string) {
+            let date = Date(timeIntervalSince1970: ts / 1000)
+            return date
+        }
+        return nil
+    }
+
+    @objc public var isVip: Bool {
+//        #if DEBUG
+//            return true
+//        #endif
+        guard let expiresDate = expiredDate else {
+            return false
+        }
+        let todayStart = Calendar.current.startOfDay(for: Date())
+        let todayStartTs = todayStart.timeIntervalSince1970
+        let expiresTs = expiresDate.timeIntervalSince1970
+
+        return expiresTs > todayStartTs
+    }
+
+    public var vipType: PremiumPeriod {
+        guard isVip, let type = vipInformation["type"] as? String else {
+            return .none
+        }
+        return PremiumPeriod(rawValue: type) ?? .none
+    }
+
+    /// 过期时间: 1683277585000 毫秒
+    func updateExpireTime(_ timeInterval: String,
+                          for productId: String) {
+        vipInformation.removeAll()
+        vipInformation["expireTime"] = timeInterval
+        vipInformation["productId"] = productId
+        vipInformation["type"] = period(for: productId).rawValue
+
+        UserDefaults.standard.set(vipInformation, forKey: kPremiumExpiredInfoKey)
+        UserDefaults.standard.synchronize()
+
+        NotificationCenter.default.post(name: .kPurchaseDidChanged, object: nil)
+    }
+
+    // 商品id对应的时间周期
+    func period(for productId: String) -> PremiumPeriod {
+        return purchaseProducts.first(where: { $0.productId == productId })?.period ?? .none
+    }
+
+    // 时间周期对应的商品id
+    func productId(for period: PremiumPeriod) -> String? {
+        return purchaseProducts.first(where: { $0.period == period })?.productId
+    }
+}
+
+// MARK: 商品信息
+
+extension PurchaseManager {
+    public func product(for period: PremiumPeriod) -> SKProduct? {
+        return products.first(where: { $0.productIdentifier == productId(for: period) })
+    }
+
+    // 商品价格
+    public func price(for period: PremiumPeriod) -> String? {
+        guard let product = product(for: period) else {
+            return nil
+        }
+        let formatter = NumberFormatter()
+        formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
+        formatter.numberStyle = .currency
+        formatter.locale = product.priceLocale
+        return formatter.string(from: product.price)
+    }
+
+    
+//    public func originalPrice(for period: PremiumPeriod) -> String? {
+//        guard let product = product(for: period) else {
+//            return nil
+//        }
+//        switch period {
+//        case .year, .lifetime:
+//            // 5折
+//            let price = product.price.doubleValue
+//            let calculatePrice = price * 2
+//            let originStr = String(format: "%.2f", calculatePrice)
+//            let originPrice = NSDecimalNumber(string: originStr, locale: product.priceLocale)
+//
+//            let formatter = NumberFormatter()
+//            formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
+//            formatter.numberStyle = .currency
+//            formatter.locale = product.priceLocale
+//            return formatter.string(from: originPrice)
+//        default:
+//            return nil
+//        }
+//    }
+}
+
+// MARK: 商品 & 订阅请求
+
+extension PurchaseManager {
+    ///请求商品
+    public func requestProducts() {
+        if !products.isEmpty {
+            purchase(self, didChaged: .loadSuccess, object: nil)
+        }
+
+        purchase(self, didChaged: .loading, object: nil)
+        let productIdentifiers = Set(purchaseProducts.map({ $0.productId }))
+        debugPrint("PurchaseManager requestProducts = \(productIdentifiers)")
+        let request = SKProductsRequest(productIdentifiers: productIdentifiers)
+        request.delegate = self
+        request.start()
+    }
+
+    public func restorePremium() {
+        purchase(self, didChaged: .restoreing, object: nil)
+        SKPaymentQueue.default().restoreCompletedTransactions()
+        debugPrint("PurchaseManager restoreCompletedTransactions")
+    }
+
+    /// 购买支付
+    public func pay(for period: PremiumPeriod) {
+        guard SKPaymentQueue.canMakePayments() else {
+            purchase(self, didChaged: .payFail, object: "Payment failed, please check your payment account")
+            return
+        }
+
+        guard SKPaymentQueue.default().transactions.count <= 0 else {
+            purchase(self, didChaged: .payFail, object: "You have outstanding orders that must be paid for before a new subscription can be placed.")
+            restorePremium()
+            return
+        }
+        if let product = product(for: period) {
+            purchase(self, didChaged: .paying, object: nil)
+            let payment = SKPayment(product: product)
+            SKPaymentQueue.default().add(payment)
+            debugPrint("PurchaseManager pay period = \(period)")
+        }else{
+            purchase(self, didChaged: .payFail, object: "Payment failed, no this item")
+        }
+    }
+}
+
+// MARK: 商品回调
+
+extension PurchaseManager: SKProductsRequestDelegate {
+    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
+        let products = response.products
+        self.products = products
+        purchase(self, didChaged: .loadSuccess, object: nil)
+        NotificationCenter.default.post(name: .kPurchasePrepared, object: nil)
+        debugPrint("PurchaseManager productsRequest didReceive = \(products)")
+    }
+
+    public func request(_ request: SKRequest, didFailWithError error: Error) {
+        debugPrint("PurchaseManager productsRequest error = \(error)")
+        purchase(self, didChaged: .loadFail, object: error.localizedDescription)
+    }
+}
+
+// MARK: 订阅回调
+
+extension PurchaseManager: SKPaymentTransactionObserver {
+    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
+        debugPrint("PurchaseManager paymentQueue transactions.count = \(transactions.count)")
+//        debugPrint("PurchaseManager paymentQueue transactions = \(transactions)")
+        
+        originalTransactionIdentifierDict.removeAll()
+        // 因为只有订阅类的购买项
+        for transaction in transactions {
+
+//            debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier original= \(transaction.original?.transactionIdentifier)")
+//            debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier = \(transaction.transactionIdentifier)")
+//            debugPrint("PurchaseManager paymentQueue transactions transactionIdentifier productIdentifier = \(transaction.payment.productIdentifier)")
+            
+            switch transaction.transactionState {
+            case .purchasing:
+                // Transaction is being added to the server queue.
+                purchase(self, didChaged: .paying, object: nil)
+
+            case .purchased:
+                SKPaymentQueue.default().finishTransaction(transaction)
+                //同样的原始订单,只处理一次.
+                guard judgeWhether(transaction: transaction) else {
+                    break
+                }
+                
+                // Transaction is in queue, user has been charged.  Client should complete the transaction.
+                #if DEBUG
+                    verifyPayResult(transaction: transaction, useSandBox: true)
+                #else
+                    verifyPayResult(transaction: transaction, useSandBox: false)
+                #endif
+                
+                
+            case .failed:
+                
+                SKPaymentQueue.default().finishTransaction(transaction)
+                // Transaction was cancelled or failed before being added to the server queue.
+                var message = "Payment Failed"
+                if let error = transaction.error as? SKError,
+                   error.code == SKError.paymentCancelled {
+                    message = "The subscription was canceled"
+                }
+                purchase(self, didChaged: .payFail, object: message)
+
+            case .restored:
+                SKPaymentQueue.default().finishTransaction(transaction)
+                //同样的原始订单,只处理一次.
+                guard judgeWhether(transaction: transaction) else {
+                    break
+                }
+                
+                // Transaction was restored from user's purchase history.  Client should complete the transaction.
+                if let original = transaction.original,
+                   original.transactionState == .purchased {
+                    #if DEBUG
+                        verifyPayResult(transaction: transaction, useSandBox: true)
+                    #else
+                        verifyPayResult(transaction: transaction, useSandBox: false)
+                    #endif
+                } else {
+                    purchase(self, didChaged: .restoreFail, object: "Failed to restore subscribe, please try again")
+                }
+                
+            case .deferred: // The transaction is in the queue, but its final status is pending external action.
+                break
+            @unknown default:
+                SKPaymentQueue.default().finishTransaction(transaction)
+            }
+        }
+    }
+
+    public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
+        purchase(self, didChaged: .restoreFail, object: nil)
+    }
+
+    public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
+        if let trans = queue.transactions.first(where: { $0.transactionState == .purchased }) {
+            verifyPayResult(transaction: trans, useSandBox: false)
+        } else if queue.transactions.isEmpty {
+            purchase(self, didChaged: .restoreFail, object: "You don't have an active subscription")
+        }
+    }
+    
+    func judgeWhether(transaction:SKPaymentTransaction) -> Bool {
+        let id = transaction.original?.transactionIdentifier
+        if let id = id {
+            if let value = originalTransactionIdentifierDict[id] {
+                return false
+            }
+            originalTransactionIdentifierDict[id] = "1"
+        }
+        return true
+    }
+}
+
+extension PurchaseManager {
+    func verifyPayResult(transaction: SKPaymentTransaction, useSandBox: Bool) {
+        purchase(self, didChaged: .verifying, object: nil)
+
+        guard let url = Bundle.main.appStoreReceiptURL,
+              let receiptData = try? Data(contentsOf: url) else {
+            purchase(self, didChaged: .verifyFail, object: "凭证文件为空")
+            return
+        }
+
+        let requestContents = [
+            "receipt-data": receiptData.base64EncodedString(),
+            "password": AppleSharedKey,
+        ]
+        guard let requestData = try? JSONSerialization.data(withJSONObject: requestContents) else {
+            purchase(self, didChaged: .verifyFail, object: "凭证文件为空")
+            return
+        }
+
+        let verifyUrlString = useSandBox ? Config.sandBoxUrl : Config.verifyUrl
+        
+        postRequest(urlString: verifyUrlString, httpBody: requestData) { [weak self] data, error in
+            guard let self = self else { return }
+            if let data = data,
+               let jsonResponse = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
+//                debugPrint("PurchaseManager verifyPayResult = \(jsonResponse)")
+                let status = jsonResponse["status"]
+                if let status = status as? String, status == "21007" {
+                    self.verifyPayResult(transaction: transaction, useSandBox: true)
+                } else if let status = status as? Int, status == 21007 {
+                    self.verifyPayResult(transaction: transaction, useSandBox: true)
+                } else if let status = status as? String, status == "0" {
+                    self.handlerPayResult(transaction: transaction, resp: jsonResponse)
+                } else if let status = status as? Int, status == 0 {
+                    self.handlerPayResult(transaction: transaction, resp: jsonResponse)
+                } else {
+                    self.purchase(self, didChaged: .verifyFail, object: "验证结果状态码错误:\(status.debugDescription)")
+                }
+            } else {
+                self.purchase(self, didChaged: .verifyFail, object: "验证结果为空")
+                debugPrint("PurchaseManager 验证结果为空")
+            }
+        }
+        
+        /*
+          21000 App Store无法读取你提供的JSON数据
+          21002 收据数据不符合格式
+          21003 收据无法被验证
+          21004 你提供的共享密钥和账户的共享密钥不一致
+          21005 收据服务器当前不可用
+          21006 收据是有效的,但订阅服务已经过期。当收到这个信息时,解码后的收据信息也包含在返回内容中
+          21007 收据信息是测试用(sandbox),但却被发送到产品环境中验证
+          21008 收据信息是产品环境中使用,但却被发送到测试环境中验证
+          */
+    }
+
+    func handlerPayResult(transaction: SKPaymentTransaction, resp: [String: Any]) {
+        var isLifetime = false
+        // 终生会员
+        if let receipt = resp["receipt"] as? [String: Any],
+           let in_app = receipt["in_app"] as? [[String: Any]] {
+            if let lifetimeProductId = purchaseProducts.first(where: { $0.period == .lifetime })?.productId,
+               let _ = in_app.filter({ ($0["product_id"] as? String) == lifetimeProductId }).first(where: { item in
+                   if let purchase_date = item["purchase_date"] as? String,
+                      !purchase_date.isEmpty {
+                       return true
+                   } else if let purchase_date_ms = item["purchase_date_ms"] as? String,
+                             !purchase_date_ms.isEmpty {
+                       return true
+                   }
+                   return false
+               }) {
+                updateExpireTime(lifetimeExpireTime, for: lifetimeProductId)
+                isLifetime = true
+            }
+        }
+
+        if !isLifetime {
+            let info = resp["latest_receipt_info"] as? [[String: Any]]
+            if let firstItem = info?.first,
+               let expires_date_ms = firstItem["expires_date_ms"] as? String,
+               let productId = firstItem["product_id"] as? String {
+                updateExpireTime(expires_date_ms, for: productId)
+            }
+        }
+
+        DispatchQueue.main.async {
+            if transaction.transactionState == .restored {
+                self.purchase(self, didChaged: .restoreSuccess, object: nil)
+            } else {
+                self.purchase(self, didChaged: .paySuccess, object: nil)
+            }
+        }
+    }
+    
+    // 终生会员过期时间:100年
+    var lifetimeExpireTime: String {
+        let date = Date().addingTimeInterval(100 * 365 * 24 * 60 * 60)
+        return "\(date.timeIntervalSince1970 * 1000)"
+    }
+
+    
+    /// 发送 POST 请求
+    /// - Parameters:
+    ///   - urlString: 请求的 URL 字符串
+    ///   - parameters: 请求的参数字典(将自动转换为 JSON)
+    ///   - timeout: 超时时间(默认 30 秒)
+    ///   - completion: 请求完成的回调,返回 `Data?` 和 `Error?`
+    func postRequest(
+        urlString: String,
+        httpBody: Data?,
+        timeout: TimeInterval = 90,
+        completion: @escaping (Data?, Error?) -> Void
+    ) {
+        // 确保 URL 有效
+        guard let url = URL(string: urlString) else {
+            completion(nil, NSError(domain: "Invalid URL", code: -1, userInfo: nil))
+            return
+        }
+        
+        dePrint("postRequest urlString=\(urlString)")
+        // 创建请求
+        var request = URLRequest(url: url)
+        request.httpMethod = "POST"
+        request.timeoutInterval = timeout
+        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
+        request.httpBody = httpBody
+  
+        // 创建数据任务
+        let task = URLSession.shared.dataTask(with: request) { data, response, error in
+            completion(data, error)
+        }
+        
+        // 启动任务
+        task.resume()
+    }
+}
+
+public extension PurchaseManager {
+    func canContinue(_ requireVip: Bool) -> Bool {
+        guard requireVip else {
+            return true
+        }
+        return isVip
+    }
+    
+    func purchase(_ manager: PurchaseManager, didChaged state: PremiumRequestState, object: Any?){
+        onPurchaseStateChanged?(manager,state,object)
+    }
+}
+
+
+/// 免费生成图片次数
+extension PurchaseManager {
+    /// 使用一次免费次数
+    func useOnceForFree(type:VipFreeNumType){
+        
+        if isVip {
+            return
+        }
+        
+        var freeNum = freeDict[type.rawValue] ?? 0
+        if freeNum > 0 {
+            freeNum-=1
+        }
+        
+        if freeNum < 0 {
+            freeNum = 0
+        }
+        
+        freeDict[type.rawValue] = freeNum
+        saveForFree()
+    }
+    
+    func freeNum(type:VipFreeNumType) -> Int{
+        let freeNum = freeDict[type.rawValue] ?? 0
+        return freeNum
+    }
+    
+    func saveForFree(){
+        UserDefaults.standard.set(freeDict, forKey: kFreeNumKey)
+        UserDefaults.standard.synchronize()
+    }
+    
+    func initializeForFree(){
+        if let dict = UserDefaults.standard.dictionary(forKey: kFreeNumKey) as? [String:Int]{
+            freeDict = dict
+        }else{
+            freeDict = [
+                VipFreeNumType.ringtones.rawValue:1,
+                VipFreeNumType.posetr.rawValue:1,
+                VipFreeNumType.photo.rawValue:1
+            ]
+            saveForFree()
+        }
+    }
+    
+    /// 免费次数是否可用
+    func freeNumAvailable(type:VipFreeNumType) -> Bool{
+        if isVip == true {
+            return true
+        }else{
+            if let freeNum = freeDict[type.rawValue],freeNum > 0 {
+                return true
+            }
+        }
+        return false
+    }
+}
+
+
+/*
+ 
+ 首先,创建SKProductsRequest对象并使用init(productIdentifiers:)初始化,传入要查询的产品标识符。
+ 然后,调用start()方法开始请求产品信息。
+ 当请求成功时,productsRequest(_:didReceive:)方法会被调用,在这里可以获取产品详细信息并展示给用户(如在界面上显示产品价格、名称等)。如果请求失败,productsRequest(_:didFailWithError:)方法会被调用来处理错误。
+ 当用户决定购买某个产品后,根据产品信息(SKProduct对象)创建SKPayment对象,然后使用SKPaymentQueue的add(_:)方法将支付请求添加到支付队列。
+ 同时,在应用启动等合适的时机,通过SKPaymentQueue的addTransactionObserver(_:)方法添加交易观察者。当支付状态发生变化时,paymentQueue(_:updatedTransactions:)方法会被调用,在这里可以根据交易状态(如购买成功、失败、恢复等)进行相应的处理。
+ 
+ */