Forráskód Böngészése

Merge branch 'dev-v2-次数购买' into dev-v2-diyVideo

# Conflicts:
#	AIEmoji.xcodeproj/project.pbxproj
#	AIEmoji/Common/Purchase/TSPurchaseManager.swift
100Years 1 hete
szülő
commit
5208626b57
52 módosított fájl, 2005 hozzáadás és 121 törlés
  1. 64 0
      AIEmoji.xcodeproj/project.pbxproj
  2. 22 0
      AIEmoji/Assets.xcassets/Common/ic_down.imageset/Contents.json
  3. BIN
      AIEmoji/Assets.xcassets/Common/ic_down.imageset/ic_down@2x.png
  4. BIN
      AIEmoji/Assets.xcassets/Common/ic_down.imageset/ic_down@3x.png
  5. 6 0
      AIEmoji/Assets.xcassets/Setting/Contents.json
  6. 22 0
      AIEmoji/Assets.xcassets/Setting/check.imageset/Contents.json
  7. BIN
      AIEmoji/Assets.xcassets/Setting/check.imageset/check@2x.png
  8. BIN
      AIEmoji/Assets.xcassets/Setting/check.imageset/check@3x.png
  9. 22 0
      AIEmoji/Assets.xcassets/Setting/hourglass.imageset/Contents.json
  10. BIN
      AIEmoji/Assets.xcassets/Setting/hourglass.imageset/hourglass@2x.png
  11. BIN
      AIEmoji/Assets.xcassets/Setting/hourglass.imageset/hourglass@3x.png
  12. 22 0
      AIEmoji/Assets.xcassets/Setting/language_System.imageset/Contents.json
  13. BIN
      AIEmoji/Assets.xcassets/Setting/language_System.imageset/language_System@2x.png
  14. BIN
      AIEmoji/Assets.xcassets/Setting/language_System.imageset/language_System@3x.png
  15. 22 0
      AIEmoji/Assets.xcassets/Setting/purchaseVideoTimesBtnbg.imageset/Contents.json
  16. BIN
      AIEmoji/Assets.xcassets/Setting/purchaseVideoTimesBtnbg.imageset/purchaseVideoTimesBtnbg@2x.png
  17. BIN
      AIEmoji/Assets.xcassets/Setting/purchaseVideoTimesBtnbg.imageset/purchaseVideoTimesBtnbg@3x.png
  18. 22 0
      AIEmoji/Assets.xcassets/Setting/purchaseVideoTimes_check.imageset/Contents.json
  19. BIN
      AIEmoji/Assets.xcassets/Setting/purchaseVideoTimes_check.imageset/purchaseVideoTimes_check@2x.png
  20. BIN
      AIEmoji/Assets.xcassets/Setting/purchaseVideoTimes_check.imageset/purchaseVideoTimes_check@3x.png
  21. 22 0
      AIEmoji/Assets.xcassets/Setting/sandGlass.imageset/Contents.json
  22. BIN
      AIEmoji/Assets.xcassets/Setting/sandGlass.imageset/sandGlass@2x.png
  23. BIN
      AIEmoji/Assets.xcassets/Setting/sandGlass.imageset/sandGlass@3x.png
  24. 22 0
      AIEmoji/Assets.xcassets/Setting/stopWatch.imageset/Contents.json
  25. BIN
      AIEmoji/Assets.xcassets/Setting/stopWatch.imageset/stopWatch@2x.png
  26. BIN
      AIEmoji/Assets.xcassets/Setting/stopWatch.imageset/stopWatch@3x.png
  27. 22 0
      AIEmoji/Assets.xcassets/Setting/unCheck.imageset/Contents.json
  28. BIN
      AIEmoji/Assets.xcassets/Setting/unCheck.imageset/unCheck@2x.png
  29. BIN
      AIEmoji/Assets.xcassets/Setting/unCheck.imageset/unCheck@3x.png
  30. 73 0
      AIEmoji/Business/Data/TSDBTimesManager.swift
  31. 341 0
      AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoBrowsePageVC/TSAIPhotoBrowsePageVC.swift
  32. 208 0
      AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/TSPurchaseVideoTimesVC.swift
  33. 162 0
      AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/View/TSPurchaseVideoTimesAlertView.swift
  34. 162 0
      AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/View/TSPurchaseVideoTimesView.swift
  35. 7 0
      AIEmoji/Business/TSSetingVC/SetingVC/TSSetingModel.swift
  36. 12 1
      AIEmoji/Business/TSSetingVC/SetingVC/TSSetingVC.swift
  37. 35 31
      AIEmoji/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift
  38. 7 2
      AIEmoji/Business/TSSetingVC/SetingVC/View/SettingPurchaseTopView.swift
  39. 124 28
      AIEmoji/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift
  40. 167 0
      AIEmoji/Business/TSSetingVC/TSChangeLanguageVC/TSChangeLanguageVC.swift
  41. 14 0
      AIEmoji/Business/VIewTool/TSGeneratorloadingView/TSGeneratorErrorView.swift
  42. 2 0
      AIEmoji/Business/VIewTool/TSGeneratorloadingView/TSGeneratorloadingView.swift
  43. 22 1
      AIEmoji/Business2/DisCover/TSAIGenerateVC/TSAIGenerateVC.swift
  44. 17 13
      AIEmoji/Business2/DisCover/TSGenerateHistoryVC/TSGenerateHistoryVC.swift
  45. 4 4
      AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift
  46. 48 7
      AIEmoji/Common/Purchase/TSPurchaseManager+Enmu.swift
  47. 9 10
      AIEmoji/Common/Purchase/TSPurchaseManager+Free.swift
  48. 64 15
      AIEmoji/Common/Purchase/TSPurchaseManager+Limit.swift
  49. 83 0
      AIEmoji/Common/Purchase/TSPurchaseManager+PurchasedNum.swift
  50. 28 8
      AIEmoji/Common/Purchase/TSPurchaseManager.swift
  51. 147 0
      AIEmoji/Common/Tool/LanguageManager.swift
  52. 1 1
      AIEmoji/Common/View/TSPhotoPickerManager/TSPhotoPickerManager.swift

+ 64 - 0
AIEmoji.xcodeproj/project.pbxproj

@@ -197,6 +197,13 @@
 		A87587162D81734300286A66 /* text_to_photo_style.json in Resources */ = {isa = PBXBuildFile; fileRef = A87587152D81733C00286A66 /* text_to_photo_style.json */; };
 		A87587182D81814500286A66 /* TSAIThinkingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A87587172D81812C00286A66 /* TSAIThinkingView.swift */; };
 		A880ECF22E420FDC00222C47 /* AppDelegate+KF.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECF12E420FD600222C47 /* AppDelegate+KF.swift */; };
+		A880ECF02E41E61600222C47 /* TSPurchaseManager+PurchasedNum.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECEF2E41E61600222C47 /* TSPurchaseManager+PurchasedNum.swift */; };
+		A880ECF42E42F28600222C47 /* TSDBTimesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECF32E42F28100222C47 /* TSDBTimesManager.swift */; };
+		A880ECF72E43434A00222C47 /* TSChangeLanguageVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECF52E43434A00222C47 /* TSChangeLanguageVC.swift */; };
+		A880ECF92E43437C00222C47 /* LanguageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECF82E43437C00222C47 /* LanguageManager.swift */; };
+		A880ECFC2E43494400222C47 /* TSPurchaseVideoTimesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECFB2E43494300222C47 /* TSPurchaseVideoTimesVC.swift */; };
+		A880ECFE2E434A1900222C47 /* TSPurchaseVideoTimesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECFD2E434A1700222C47 /* TSPurchaseVideoTimesView.swift */; };
+		A880ED002E434AB100222C47 /* TSPurchaseVideoTimesAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A880ECFF2E434AA800222C47 /* TSPurchaseVideoTimesAlertView.swift */; };
 		A88508882DBCD944000FBCEC /* MemoryLeakChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88508872DBCD940000FBCEC /* MemoryLeakChecker.swift */; };
 		A885088D2DBCE7BE000FBCEC /* TSPTPHistoryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A885088C2DBCE7BC000FBCEC /* TSPTPHistoryVC.swift */; };
 		A88508B62DBF142B000FBCEC /* TSGennertatorSelectStyleVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88508B52DBF142A000FBCEC /* TSGennertatorSelectStyleVC.swift */; };
@@ -263,6 +270,7 @@
 		A8D638472DB21FAD00A96C0E /* TSBusinessFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638452DB21FAD00A96C0E /* TSBusinessFileManager.swift */; };
 		A8D638482DB21FAD00A96C0E /* TSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638462DB21FAD00A96C0E /* TSDownloadManager.swift */; };
 		A8D6384A2DB252F100A96C0E /* TSAIListHistoryBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638492DB252F000A96C0E /* TSAIListHistoryBaseCell.swift */; };
+		A8DFCDAF2E445C4E005A6D44 /* TSAIPhotoBrowsePageVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8DFCDAE2E445C4D005A6D44 /* TSAIPhotoBrowsePageVC.swift */; };
 		A8EB1A642E30EA24001F58D7 /* KeychainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8EB1A632E30EA22001F58D7 /* KeychainManager.swift */; };
 		A8EB1A662E30FA78001F58D7 /* TSAppOtherAlertVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8EB1A652E30FA71001F58D7 /* TSAppOtherAlertVC.swift */; };
 		A8EB1AA02E320438001F58D7 /* Pixel World.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = A8EB1A982E320438001F58D7 /* Pixel World.mp4 */; };
@@ -601,6 +609,13 @@
 		A87587152D81733C00286A66 /* text_to_photo_style.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = text_to_photo_style.json; sourceTree = "<group>"; };
 		A87587172D81812C00286A66 /* TSAIThinkingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIThinkingView.swift; sourceTree = "<group>"; };
 		A880ECF12E420FD600222C47 /* AppDelegate+KF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+KF.swift"; sourceTree = "<group>"; };
+		A880ECEF2E41E61600222C47 /* TSPurchaseManager+PurchasedNum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSPurchaseManager+PurchasedNum.swift"; sourceTree = "<group>"; };
+		A880ECF32E42F28100222C47 /* TSDBTimesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDBTimesManager.swift; sourceTree = "<group>"; };
+		A880ECF52E43434A00222C47 /* TSChangeLanguageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSChangeLanguageVC.swift; sourceTree = "<group>"; };
+		A880ECF82E43437C00222C47 /* LanguageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageManager.swift; sourceTree = "<group>"; };
+		A880ECFB2E43494300222C47 /* TSPurchaseVideoTimesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVideoTimesVC.swift; sourceTree = "<group>"; };
+		A880ECFD2E434A1700222C47 /* TSPurchaseVideoTimesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVideoTimesView.swift; sourceTree = "<group>"; };
+		A880ECFF2E434AA800222C47 /* TSPurchaseVideoTimesAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVideoTimesAlertView.swift; sourceTree = "<group>"; };
 		A88508872DBCD940000FBCEC /* MemoryLeakChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryLeakChecker.swift; sourceTree = "<group>"; };
 		A885088C2DBCE7BC000FBCEC /* TSPTPHistoryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPTPHistoryVC.swift; sourceTree = "<group>"; };
 		A88508B52DBF142A000FBCEC /* TSGennertatorSelectStyleVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGennertatorSelectStyleVC.swift; sourceTree = "<group>"; };
@@ -668,6 +683,7 @@
 		A8D638452DB21FAD00A96C0E /* TSBusinessFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBusinessFileManager.swift; sourceTree = "<group>"; };
 		A8D638462DB21FAD00A96C0E /* TSDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDownloadManager.swift; sourceTree = "<group>"; };
 		A8D638492DB252F000A96C0E /* TSAIListHistoryBaseCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListHistoryBaseCell.swift; sourceTree = "<group>"; };
+		A8DFCDAE2E445C4D005A6D44 /* TSAIPhotoBrowsePageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIPhotoBrowsePageVC.swift; sourceTree = "<group>"; };
 		A8EB1A632E30EA22001F58D7 /* KeychainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainManager.swift; sourceTree = "<group>"; };
 		A8EB1A652E30FA71001F58D7 /* TSAppOtherAlertVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAppOtherAlertVC.swift; sourceTree = "<group>"; };
 		A8EB1A852E320438001F58D7 /* Animal Diving Show.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Animal Diving Show.mp4"; sourceTree = "<group>"; };
@@ -1044,6 +1060,7 @@
 		A80E73DD2D533E5800C64288 /* TSPurchaseMembershipVC */ = {
 			isa = PBXGroup;
 			children = (
+				A880ECFA2E43492F00222C47 /* TSPurchaseVideoTimesVC */,
 				A8EB38442E14080D002F90E9 /* TSPurchasePromotionalVC */,
 				A8EB383F2E13BB07002F90E9 /* View */,
 				A80E73D82D533E5800C64288 /* TSPurchaseVC.swift */,
@@ -1059,6 +1076,7 @@
 				A8EE84362E33183100CA04F0 /* TSPurchaseManager+Free.swift */,
 				A8EE84382E3318AC00CA04F0 /* TSPurchaseManager+Limit.swift */,
 				A8F413502DA7B711001E715A /* TSPurchaseManager+DataReport.swift */,
+				A880ECEF2E41E61600222C47 /* TSPurchaseManager+PurchasedNum.swift */,
 			);
 			path = Purchase;
 			sourceTree = "<group>";
@@ -1348,6 +1366,7 @@
 		A80EDDDC2D6EB17D003CD332 /* TSPTPGeneratorVC */ = {
 			isa = PBXGroup;
 			children = (
+				A8DFCDAD2E445C49005A6D44 /* TSAIPhotoBrowsePageVC */,
 				A804B9902DC1CCE600C494C7 /* TSAbnormalPopUpAlertVC */,
 				A88508B42DBF1408000FBCEC /* TSGennertatorSelectStyleVC */,
 				A885088B2DBCE7B8000FBCEC /* TSPTPHistoryVC */,
@@ -1777,6 +1796,7 @@
 		A87587112D81702700286A66 /* Data */ = {
 			isa = PBXGroup;
 			children = (
+				A880ECF32E42F28100222C47 /* TSDBTimesManager.swift */,
 				A8BE26E72DBC6A9700A1DD18 /* TSDBHistoryManager.swift */,
 				A82D609A2DB9D83500596190 /* TSBaseHistoryManager.swift */,
 				A87587102D81702700286A66 /* TSUserDefaultData.swift */,
@@ -1792,6 +1812,32 @@
 			path = TSChatMessageUIModel;
 			sourceTree = "<group>";
 		};
+		A880ECF62E43434A00222C47 /* TSChangeLanguageVC */ = {
+			isa = PBXGroup;
+			children = (
+				A880ECF52E43434A00222C47 /* TSChangeLanguageVC.swift */,
+			);
+			path = TSChangeLanguageVC;
+			sourceTree = "<group>";
+		};
+		A880ECFA2E43492F00222C47 /* TSPurchaseVideoTimesVC */ = {
+			isa = PBXGroup;
+			children = (
+				A880ED032E434B3700222C47 /* View */,
+				A880ECFB2E43494300222C47 /* TSPurchaseVideoTimesVC.swift */,
+			);
+			path = TSPurchaseVideoTimesVC;
+			sourceTree = "<group>";
+		};
+		A880ED032E434B3700222C47 /* View */ = {
+			isa = PBXGroup;
+			children = (
+				A880ECFF2E434AA800222C47 /* TSPurchaseVideoTimesAlertView.swift */,
+				A880ECFD2E434A1700222C47 /* TSPurchaseVideoTimesView.swift */,
+			);
+			path = View;
+			sourceTree = "<group>";
+		};
 		A885088B2DBCE7B8000FBCEC /* TSPTPHistoryVC */ = {
 			isa = PBXGroup;
 			children = (
@@ -2100,6 +2146,14 @@
 			path = TSAIListVideoPlayerVC;
 			sourceTree = "<group>";
 		};
+		A8DFCDAD2E445C49005A6D44 /* TSAIPhotoBrowsePageVC */ = {
+			isa = PBXGroup;
+			children = (
+				A8DFCDAE2E445C4D005A6D44 /* TSAIPhotoBrowsePageVC.swift */,
+			);
+			path = TSAIPhotoBrowsePageVC;
+			sourceTree = "<group>";
+		};
 		A8EB383F2E13BB07002F90E9 /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -2253,6 +2307,7 @@
 		A8F774CE2D38EA8C00AA6E93 /* Tool */ = {
 			isa = PBXGroup;
 			children = (
+				A880ECF82E43437C00222C47 /* LanguageManager.swift */,
 				A8EB1A632E30EA22001F58D7 /* KeychainManager.swift */,
 				A8708A482E0C091800601686 /* TSAppUpdateManager.swift */,
 				A88508872DBCD940000FBCEC /* MemoryLeakChecker.swift */,
@@ -2342,6 +2397,7 @@
 		A8F775152D38EB5D00AA6E93 /* TSSetingVC */ = {
 			isa = PBXGroup;
 			children = (
+				A880ECF62E43434A00222C47 /* TSChangeLanguageVC */,
 				A8F4134D2DA75E92001E715A /* TSAboutDataVC.swift */,
 				A8F775482D3935D600AA6E93 /* TSBusinessWebVC */,
 				A8F775412D39344A00AA6E93 /* SetingVC */,
@@ -2875,6 +2931,7 @@
 				A80E72792D42285500C64288 /* TSBootPageVC.swift in Sources */,
 				60EE9A852E38A7C200565900 /* TSDiyVideoPromptElementView.swift in Sources */,
 				A80E726A2D409E5400C64288 /* TSDiyTLYFlowersView.swift in Sources */,
+				A880ECFC2E43494400222C47 /* TSPurchaseVideoTimesVC.swift in Sources */,
 				A8F7764E2D3E00A800AA6E93 /* TSEmojisColViewModel.swift in Sources */,
 				A89EA6C82D6359ED000EB181 /* TSChatViewController+VipView.swift in Sources */,
 				60C805B32E3B1CCB0051164F /* TSDiyStyleElementView.swift in Sources */,
@@ -2925,6 +2982,7 @@
 				A875870F2D81689A00286A66 /* TSPTPEnterView.swift in Sources */,
 				A8708A382E0B8ABA00601686 /* TSAIGenerateVC.swift in Sources */,
 				A80EDD522D6C3F82003CD332 /* MarkdownCode+AppKit.swift in Sources */,
+				A880ECFE2E434A1900222C47 /* TSPurchaseVideoTimesView.swift in Sources */,
 				A82D60832DB87D1A00596190 /* TSAIExpandChangeView.swift in Sources */,
 				A8990EE52DEE8C2300DD55FE /* TSAISmallUploadView.swift in Sources */,
 				A80EDD532D6C3F82003CD332 /* MarkdownHeader.swift in Sources */,
@@ -2963,6 +3021,7 @@
 				A80EDD602D6C3F82003CD332 /* MarkdownElement.swift in Sources */,
 				A80EDD612D6C3F82003CD332 /* MarkdownLink+AppKit.swift in Sources */,
 				A80EDD622D6C3F82003CD332 /* MarkdownLink.swift in Sources */,
+				A8DFCDAF2E445C4E005A6D44 /* TSAIPhotoBrowsePageVC.swift in Sources */,
 				A8BA76722DA65A95000B6707 /* TSAIUploadPhotoBaseVC.swift in Sources */,
 				60EE9A782E386EB200565900 /* TSAIModelElementView.swift in Sources */,
 				A80EDD632D6C3F82003CD332 /* MarkdownAutomaticLink.swift in Sources */,
@@ -3011,6 +3070,7 @@
 				A80EDDE42D6EB8FA003CD332 /* TSPTPSelectStyleCell.swift in Sources */,
 				A8F775172D38EB7400AA6E93 /* TSTabBarController.swift in Sources */,
 				A8708A3C2E0B933F00601686 /* TSAIGenerateBaseVC.swift in Sources */,
+				A880ECF42E42F28600222C47 /* TSDBTimesManager.swift in Sources */,
 				A8708A182E095A2300601686 /* TSDiscoverBaseCell.swift in Sources */,
 				A80E73E12D533E5800C64288 /* TSPurchaseVC.swift in Sources */,
 				A8708A0C2E08F5C600601686 /* TSGennerateCellView.swift in Sources */,
@@ -3034,6 +3094,7 @@
 				A8708A2F2E0AA03B00601686 /* TSUploadImageView.swift in Sources */,
 				A80E72222D3F3A9200C64288 /* DiyStickerElement.swift in Sources */,
 				A85E47C32D6964A50018D62D /* TSMSGAIDefaultHeaderView.swift in Sources */,
+				A880ECF92E43437C00222C47 /* LanguageManager.swift in Sources */,
 				A80EDD032D6C282B003CD332 /* TSMarkDownTool.swift in Sources */,
 				A8EB383C2E13AE6F002F90E9 /* TSPurchasePromotionalVC+View.swift in Sources */,
 				A8EE843B2E33190900CA04F0 /* TSPurchaseManager+Enmu.swift in Sources */,
@@ -3061,6 +3122,7 @@
 				A82542D42E279C0200F54FE5 /* TSDiscoverViewModel.swift in Sources */,
 				A85E47962D672ADA0018D62D /* TSTextPicGennerateVC.swift in Sources */,
 				A82D60A02DBA1B0500596190 /* TSImageGenerateView.swift in Sources */,
+				A880ED002E434AB100222C47 /* TSPurchaseVideoTimesAlertView.swift in Sources */,
 				A80E72562D3F98D700C64288 /* TSDiyKeyboardViewVC.swift in Sources */,
 				A8F775032D38EA8C00AA6E93 /* GlobalImports.swift in Sources */,
 				A89EA67A2D59D25F000EB181 /* TSAIChatVM.swift in Sources */,
@@ -3071,6 +3133,7 @@
 				A8EB38412E13BB0E002F90E9 /* PurchaseView.swift in Sources */,
 				A8BA76752DA67E66000B6707 /* TSAIListHistoryBaseVC.swift in Sources */,
 				A8F776322D3A771400AA6E93 /* TSGenmojiCollectionViewModel.swift in Sources */,
+				A880ECF02E41E61600222C47 /* TSPurchaseManager+PurchasedNum.swift in Sources */,
 				A80E721E2D3F3A7500C64288 /* DiyElement.swift in Sources */,
 				A8708A132E0958F200601686 /* TSDiscoverVC.swift in Sources */,
 				A8EB38462E140829002F90E9 /* TSPurchasePromotionalCountDownTime.swift in Sources */,
@@ -3137,6 +3200,7 @@
 				A8D638482DB21FAD00A96C0E /* TSDownloadManager.swift in Sources */,
 				A8FB02B72D3E3A3D0031A396 /* TSEmojisChildViewModel.swift in Sources */,
 				A8EB1A642E30EA24001F58D7 /* KeychainManager.swift in Sources */,
+				A880ECF72E43434A00222C47 /* TSChangeLanguageVC.swift in Sources */,
 				A8708A1D2E095A8100601686 /* TSDiscoverStyleMoreCell.swift in Sources */,
 				A8F7754B2D39376800AA6E93 /* TSSettingListView.swift in Sources */,
 				A8F7748B2D38E8B700AA6E93 /* AppDelegate.swift in Sources */,

+ 22 - 0
AIEmoji/Assets.xcassets/Common/ic_down.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Common/ic_down.imageset/ic_down@2x.png


BIN
AIEmoji/Assets.xcassets/Common/ic_down.imageset/ic_down@3x.png


+ 6 - 0
AIEmoji/Assets.xcassets/Setting/Contents.json

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

+ 22 - 0
AIEmoji/Assets.xcassets/Setting/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
AIEmoji/Assets.xcassets/Setting/check.imageset/check@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/check.imageset/check@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/hourglass.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/hourglass.imageset/hourglass@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/hourglass.imageset/hourglass@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/language_System.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/language_System.imageset/language_System@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/language_System.imageset/language_System@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/purchaseVideoTimesBtnbg.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/purchaseVideoTimesBtnbg.imageset/purchaseVideoTimesBtnbg@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/purchaseVideoTimesBtnbg.imageset/purchaseVideoTimesBtnbg@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/purchaseVideoTimes_check.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/purchaseVideoTimes_check.imageset/purchaseVideoTimes_check@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/purchaseVideoTimes_check.imageset/purchaseVideoTimes_check@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/sandGlass.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/sandGlass.imageset/sandGlass@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/sandGlass.imageset/sandGlass@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/stopWatch.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/stopWatch.imageset/stopWatch@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/stopWatch.imageset/stopWatch@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Setting/unCheck.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/Setting/unCheck.imageset/unCheck@2x.png


BIN
AIEmoji/Assets.xcassets/Setting/unCheck.imageset/unCheck@3x.png


+ 73 - 0
AIEmoji/Business/Data/TSDBTimesManager.swift

@@ -0,0 +1,73 @@
+//
+//  Untitled.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/5.
+//
+
+
+import RealmSwift
+//MARK: TSDBAIChatList - 用于存储会话及消息列表
+class TSDBTimesManager: Object {
+    @Persisted(primaryKey: true) var primaryKey: String = ""
+    
+    @Persisted var times: Int = 0 //可用次数
+    @Persisted var type: Int = 0 //0视频 1xx 2yy
+    @Persisted var jsonString = ""
+
+    lazy var jsonDict: [String: Any] = {
+        guard let data = jsonString.data(using: .utf8),
+              let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
+            return [:]
+        }
+        return dict
+    }()
+    
+    func addNewRecords(key:String,dict:[String:Any],num:Int){
+        if jsonDict[key] == nil {
+            jsonDict[key] = dict
+            if let data = try? JSONSerialization.data(withJSONObject: jsonDict),
+               let str = String(data: data, encoding: .utf8) {
+                TSRMShared.writeThread {
+                    jsonString = str
+                    times = times + num
+                    dePrint("发放视频可用次数的key=\(key)")
+                    dePrint("视频可用次数TSDBTimesManager.times=\(times)")
+                }
+            }
+        }
+    }
+    
+    
+    func reduceTimes(num:Int = 1){
+        TSRMShared.writeThread {
+            times = max(0, times - num)
+            dePrint("视频可用次数TSDBTimesManager.times=\(times)")
+        }
+    }
+    
+    
+    func addTimes(num:Int = 1){
+        TSRMShared.writeThread {
+            times = times + num
+            dePrint("视频可用次数TSDBTimesManager.times=\(times)")
+        }
+    }
+}
+
+extension TSDBTimesManager{
+    static var purchaseVideo: TSDBTimesManager = {
+        let primaryKey = "TSDBTimesManager_videoPurchaseNum"
+        let historys = TSRMShared.realm.objects(TSDBTimesManager.self).filter(NSPredicate(format: "primaryKey == %@",primaryKey))
+        if let history = historys.first {
+            return history
+        }else {
+            let dbHistory = TSDBTimesManager()
+            dbHistory.primaryKey = primaryKey
+            TSRMShared.update(dbHistory)
+            return dbHistory
+        }
+    }()
+}
+
+

+ 341 - 0
AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoBrowsePageVC/TSAIPhotoBrowsePageVC.swift

@@ -0,0 +1,341 @@
+//
+//  TSAIPhotoBrowsePageVC.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/6.
+//
+import RealmSwift
+private let bottomViewH = 60+k_Height_safeAreaInsetsBottom()
+class TSAIPhotoBrowsePageVC: TSBaseVC {
+
+    var browserPageVC:TSAIPhotoBrowsePageViewVC
+    public init(dataItems: [Any], startIndex: Int = 0) {
+        browserPageVC = TSAIPhotoBrowsePageViewVC(dataItems: dataItems, startIndex: startIndex)
+        super.init()
+    }
+    
+    @MainActor required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    var deleteComplete:((TSActionInfoModel)->Void)?
+
+    var currentIndex:Int{
+        return browserPageVC.currentIndex
+    }
+    
+    var currentModel:TSActionInfoModel?{
+        if let model = browserPageVC.dataItems.safeObj(At: currentIndex) as? TSActionInfoModel{
+            return model
+        }
+        return nil
+    }
+    
+
+
+    lazy var bottomView: UIView = {
+        let bottom = 60+k_Height_safeAreaInsetsBottom()
+        let bottomView = UIView(frame: CGRectMake(0, k_ScreenHeight-bottomViewH, k_ScreenWidth, bottomViewH))
+        let colorView = UIView.creatColor(color: "#222222".uiColor)
+        colorView.frame = bottomView.bounds
+        colorView.cornersRound(radius: 20, corner: [.topLeft,.topRight])
+        bottomView.addSubview(colorView)
+
+        return bottomView
+    }()
+    
+    //左边分享按钮
+    lazy var shareBtn: TSVerticalButton = {
+        let shareBtn = TSVerticalButton()
+        shareBtn.setUpButton(title: "Share".localized,
+                                  image: UIImage(named: "share"),
+                                  font: .font(size: 11),
+                                  titleColor: .white.withAlphaComponent(0.8)){ [weak self]  in
+            guard let self = self else { return }
+            clickShare()
+        }
+        shareBtn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
+        return shareBtn
+    }()
+    
+    //保存按钮
+    lazy var bigSaveBtn: UIButton = {
+        let bigSaveBtn = kCreateNormalSubmitBtn(title: "Save".localized) { [weak self]  in
+            guard let self = self else { return }
+            clickSubmitBtn()
+        }
+        bigSaveBtn.contentEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
+        bigSaveBtn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
+        bigSaveBtn.frame = CGRectMake(0, 0, 252, 44)
+        bigSaveBtn.cornerRadius = 22.0
+        kSetBtnVipIcon(btn: bigSaveBtn, show: true)
+        return bigSaveBtn
+    }()
+    
+    lazy var xBtn: UIButton = {
+        let xBtn = UIButton.createButton(image: UIImage(named: "close_gray")) { [weak self]  in
+            guard let self = self else { return }
+            clickXBtn()
+        }
+        return xBtn
+    }()
+    
+    
+    lazy var deleteBtn: UIButton = {
+        let deleteBtn = UIButton.createButton(image: UIImage(named: "delete_bg_gray")) { [weak self]  in
+            guard let self = self else { return }
+            showCustomAlert(message: "Are you sure to delete".localized, deleteHandler:  { [weak self]  in
+                guard let self = self else { return }
+                if let currentModel = currentModel {
+                    deleteComplete?(currentModel)
+                    browserPageVC.removePage(at: currentIndex, animated: true)
+                    if browserPageVC.dataItems.count <= 0 {
+                        self.clickXBtn()
+                    }
+                }
+            })
+        }
+        return deleteBtn
+    }()
+    
+    
+    override func createView() {
+        super.createView()
+        
+        setNavBarViewHidden(true)
+        
+
+        contentView.addSubview(browserPageVC.view)
+        browserPageVC.view.snp.makeConstraints { make in
+            make.leading.trailing.top.equalTo(0)
+            make.bottom.equalTo(-bottomViewH)
+        }
+        self.addChild(browserPageVC)
+        
+        //关闭按钮
+        contentView.addSubview(xBtn)
+        xBtn.snp.makeConstraints { make in
+            make.top.equalTo(k_Height_StatusBar + 4)
+            make.leading.equalTo(16)
+            make.width.equalTo(36)
+            make.height.equalTo(36)
+        }
+        
+        contentView.addSubview(deleteBtn)
+        deleteBtn.snp.makeConstraints { make in
+            make.top.equalTo(k_Height_StatusBar + 4)
+            make.trailing.equalTo(-16)
+            make.width.equalTo(36)
+            make.height.equalTo(36)
+        }
+        
+        let bottomBtnTop:CGFloat = 8.0
+        contentView.addSubview(bottomView)
+        bottomView.addSubview(bigSaveBtn)
+        bigSaveBtn.snp.makeConstraints { make in
+            make.top.equalTo(bottomBtnTop)
+            make.trailing.equalTo(-16)
+            make.width.equalTo(252*kDesignScale)
+            make.height.equalTo(44)
+        }
+        
+        bottomView.addSubview(shareBtn)
+        shareBtn.snp.makeConstraints { make in
+            make.top.equalTo(bottomBtnTop)
+            make.leading.equalTo(16)
+            make.width.equalTo(shareBtn.intrinsicContentSize.width)
+            make.height.equalTo(44)
+        }
+        
+    }
+    
+    override func dealThings() {
+        addPullDownClosePage()
+    }
+    @objc func clickSubmitBtn(){
+  
+        guard let currentModel = currentModel else { return }
+        if currentModel.modelType != .example{
+            if kJudgeVip(externalBool: true,vc: self) {
+                return
+            }
+        }
+
+        let urlString = currentModel.response.resultUrl
+        if currentModel.isVideo {
+            TSDownloadManager.getDownLoadVideo(urlString: urlString) { url, _ in
+                if let url = url {
+                    PhotoManagerShared.saveVideoToAlbum(videoURL: url) { success, error in
+                        if success {
+                            kSaveSuccesswShared.show(atView: self.view)
+                        }else{
+                            debugPrint(error)
+                        }
+                    }
+                }
+            }
+        }else{
+            TSImageStoreTool.downloadImageWithProgress(urlString: urlString) { image in
+                if let image = image{
+                    PhotoManagerShared.saveImageToAlbum(image) { success, error in
+                        if success {
+                            kSaveSuccesswShared.show(atView: self.view)
+                        }else{
+                            debugPrint(error)
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    @objc func clickXBtn(){
+        pop()
+    }
+    
+    func clickShare() {
+        guard let currentModel = currentModel else { return }
+        
+        if currentModel.modelType != .example{
+            if kJudgeVip(externalBool: true,vc: self) { return }
+        }
+        
+        let urlString = currentModel.response.resultUrl
+        if currentModel.isVideo {
+            TSDownloadManager.getDownLoadVideo(urlString: urlString) { url, _ in
+                if let url = url {
+                    kShareContent(target: self, anyData: url)
+                }
+            }
+        }else{
+            TSImageStoreTool.downloadImageWithProgress(urlString: urlString) { image in
+                if let image = image{
+                    kShareContent(target: self, anyData: image)
+                }
+            }
+        }
+    }
+    
+    func reloadUI() {
+        guard let currentModel = currentModel else { return }
+        kSetBtnVipIcon(btn: bigSaveBtn, show: currentModel.modelType == .example)
+    }
+}
+
+class TSAIPhotoBrowsePageViewVC: TSBasePageVC {
+    override func viewController(at index: Int) -> UIViewController? {
+          guard index >= 0 && index < dataItems.count else { return nil }
+          if let model = dataItems[index] as? TSActionInfoModel  {
+              return TSAIPhotoDetailsBrowserBaseVC.createBrowserVC(with: model,frame: self.view.frame)
+          }
+          return nil
+      }
+    
+    override func indexOfViewController(_ viewController: UIViewController) -> Int? {
+          guard let vc = viewController as? TSAIPhotoDetailsBrowserBaseVC,
+                let index = dataItems.firstIndex(where: { ($0 as? TSActionInfoModel) == vc.model }) else {
+              return nil
+          }
+          return index
+      }
+    
+    override func dealThings() {
+ 
+    }
+}
+
+
+class TSAIPhotoDetailsBrowserBaseVC: TSBaseVC {
+    
+    var model:TSActionInfoModel
+    init(model: TSActionInfoModel) {
+        self.model = model
+        super.init()
+    }
+    
+    @MainActor required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    override func createView() {
+        setNavBarViewHidden(true)
+    }
+    
+    // 工厂方法
+    static func createBrowserVC(with model: TSActionInfoModel,frame:CGRect) -> TSAIPhotoDetailsBrowserBaseVC {
+        if model.isVideo {
+            return TSAIPhotoDetailsBrowserVideoVC(model: model)
+        }else{
+            return TSAIPhotoDetailsBrowserImageVC(model: model)
+        }
+    }
+    
+}
+
+class TSAIPhotoDetailsBrowserImageVC: TSAIPhotoDetailsBrowserBaseVC {
+    lazy var netWorkImageView: UIImageView = UIImageView.createImageView()
+    lazy var vipImageView: UIImageView = {
+        let vipImageView = UIImageView.createImageView(imageName:"vip_side_icon")
+        vipImageView.contentMode = .scaleToFill
+        vipImageView.isHidden = true
+        return vipImageView
+    }()
+    
+    override func createView() {
+        super.createView()
+        contentView.addSubview(netWorkImageView)
+        netWorkImageView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        netWorkImageView.addSubview(vipImageView)
+        vipImageView.snp.makeConstraints { make in
+            make.width.height.equalTo(40)
+            make.top.equalTo(-5)
+            make.trailing.equalTo(5)
+        }
+    }
+    
+    override func dealThings() {
+        if model.modelType == .example {
+            netWorkImageView.image = UIImage(named:model.response.resultUrl)
+        }else{
+            netWorkImageView.setAsyncImage(urlString: model.response.resultUrl,placeholder: kPlaceholderImage)
+        }
+        vipImageView.isHidden = !model.response.vip
+    }
+}
+
+class TSAIPhotoDetailsBrowserVideoVC: TSAIPhotoDetailsBrowserBaseVC {
+    
+    var videoPlayerVC: TSAIListVideoPlayerVC?
+    
+    override func createView() {
+        super.createView()
+    }
+    
+    
+    override func dealThings() {
+        self.videoPlayerVC?.view.removeFromSuperview()
+        self.contentView.removeAllSubViews()
+        self.videoPlayerVC = nil
+        self.videoPlayerVC = TSAIListVideoPlayerVC(videoURL: self.model.videoURL)
+        self.view.addSubview(self.videoPlayerVC!.view)
+        self.videoPlayerVC!.setControlsBottom(bottem: -20)
+        self.videoPlayerVC!.view.snp.remakeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        self.videoPlayerVC?.runloppPlay()
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        self.videoPlayerVC?.runloppPlay()
+    }
+    
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        self.videoPlayerVC?.playPause()
+    }
+
+}

+ 208 - 0
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/TSPurchaseVideoTimesVC.swift

@@ -0,0 +1,208 @@
+//
+//  TSPurchaseVideoTimesVC.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/6.
+//
+
+import Combine
+import SwiftUI
+import SwiftUIX
+
+class TSPurchaseVideoTimesVM : ObservableObject{
+    
+    @Published var selectedType: PremiumPeriod = .purchase(.videoNum2)
+    
+    /// 订阅publisher
+    let buyPublisher  = PassthroughSubject<Bool,Never>()
+    
+    /// 订阅publisher
+    let closePagePublisher  = PassthroughSubject<Bool,Never>()
+}
+
+
+class TSPurchaseVideoTimesVC: TSBaseVC {
+
+    var isShowAlertModel:Bool//是否以弹窗的样式展现
+    init(isShowAlertModel: Bool) {
+        self.isShowAlertModel = isShowAlertModel
+        super.init()
+    }
+    
+    @MainActor required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    var cancellabel: [AnyCancellable] = []
+    var viewModel: TSPurchaseVideoTimesVM = .init()
+    var buyPeriod:PremiumPeriod = .year
+    var isHandlePurchaseStateChanged = false //是否处理购买状态变化
+    
+    
+    lazy var fullVC: UIHostingController<TSPurchaseVideoTimesView> = {
+        let vc = UIHostingController(rootView: TSPurchaseVideoTimesView(viewModel: viewModel))
+        vc.view.backgroundColor = .clear
+        return vc
+    }()
+    
+    lazy var alertVC: UIHostingController<TSPurchaseVideoTimesAlertView> = {
+        let vc = UIHostingController(rootView: TSPurchaseVideoTimesAlertView(viewModel: viewModel))
+        vc.view.backgroundColor = .clear
+        return vc
+    }()
+    
+    
+    
+    override func createView() {
+        if isShowAlertModel {
+            setUpAlerView()
+        }else{
+            setUpFullView()
+        }
+    }
+    
+    override func dealThings() {
+        addNotifaction()
+        onPurchaseStateChanged()
+        NotificationCenter.default.addObserver(forName: .kPurchasePrepared, object: nil, queue: OperationQueue.main) { [weak self] _ in
+            guard let self = self else { return }
+            viewModel.selectedType = viewModel.selectedType
+        }
+    }
+    
+    func addNotifaction() {
+        viewModel.buyPublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
+            guard let self = self else { return }
+            isHandlePurchaseStateChanged = true
+            PurchaseManager.default.pay(for: self.viewModel.selectedType)
+        }.store(in: &cancellabel)
+        
+        viewModel.closePagePublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
+            guard let self = self else { return }
+            closePage()
+        }.store(in: &cancellabel)
+    }
+    
+}
+
+extension TSPurchaseVideoTimesVC {
+    
+    func setUpFullView(){
+        addNormalNavBarView()
+        setPageTitle("")
+        
+        contentView.snp.updateConstraints { make in
+            make.top.equalTo(0)
+        }
+      
+        let bgColorView = UIView(frame: UIScreen.main.bounds)
+        bgColorView.addGradientBg(colors: ["#13052C".uiCGColor,"#160B2C".uiCGColor],startPoint: CGPoint(x: 0.5, y: 0),endPoint: CGPoint(x: 0.5, y: 1.0))
+        contentView.addSubview(bgColorView)
+        
+        
+        contentView.addSubview(fullVC.view)
+        fullVC.view.snp.makeConstraints { make in
+            make.leading.trailing.bottom.top.equalToSuperview()
+        }
+    }
+    
+}
+extension TSPurchaseVideoTimesVC {
+    
+    func setUpAlerView(){
+        setNavBarViewHidden(true)
+        self.view.backgroundColor = .black.withAlphaComponent(0.5)
+        
+        contentView.addSubview(alertVC.view)
+        alertVC.view.snp.makeConstraints { make in
+            make.leading.trailing.bottom.top.equalToSuperview()
+        }
+    }
+}
+
+
+extension TSPurchaseVideoTimesVC {
+    func onPurchaseStateChanged(){
+        PurchaseManager.default.onPurchaseStateChanged = { [weak self] manager,state,object in
+            guard let self = self else { return }
+        
+            if isHandlePurchaseStateChanged == false {
+                debugPrint("purchaseManager.onPurchaseStateChanged 不处理")
+                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 = "Failed to get the price, will automatically retry in 5 seconds".localized
+                    TSToastShared.showToast(text: message)
+                    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
+                        PurchaseManager.default.requestProducts()
+                    }
+                case .paying:
+                    TSToastShared.showLoading(text: "Purchasing now".localized,containerView: self.view)
+                case .paySuccess:
+                    TSToastShared.hideLoading()
+                    let loadingText = "购买成功".localized
+                    debugPrint(loadingText)
+                    TSToastShared.showToast(text:loadingText)
+                    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 = "购买成功".localized
+                    debugPrint(loadingText)
+                    TSToastShared.showToast(text:loadingText)
+                    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) ?? "Failed to validate receipt".localized
+                    TSToastShared.showToast(text:message)
+
+                #endif
+                }
+            }
+            debugPrint("PurchaseManager onPurchaseStateChanged=\(String(describing: state))")
+        }
+    }
+    
+    @objc func closePage(){
+        TSToastShared.hideLoading()
+        self.dismiss(animated: true)
+        NotificationCenter.default.post(name: .kCloseTSPurchaseVC, object: nil)
+    }
+}
+
+

+ 162 - 0
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/View/TSPurchaseVideoTimesAlertView.swift

@@ -0,0 +1,162 @@
+//
+//  TSPurchaseVideoTimesAlertView.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/6.
+//
+
+import SwiftUI
+import SwiftUIX
+
+struct TSPurchaseVideoTimesAlertView :View {
+    @ObservedObject var viewModel: TSPurchaseVideoTimesVM
+    @State var isExpand: Bool = false
+    var body: some View {
+        VStack {
+            let limit = "You've hit your limit. Get more effect credits below.".localized
+            
+            Spacer()
+            ZStack(alignment: .topTrailing) {
+                VStack {
+                    Spacer().frame(height: 24)
+                    Text("Limit Reached".localized)
+                        .font(.font(size: 22,weight: .medium))
+                        .foregroundColor(.white)
+                        .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
+                    
+                    Spacer().frame(height: 16)
+                    Text(limit)
+                        .foregroundColor(.white.opacity(0.7))
+                        .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
+                    
+                    Spacer().frame(height: 15)
+                    VStack(alignment: .leading, spacing: 20, content: {
+                        TSPurchaseVideoTimesCellView(type: .purchase(.videoNum1), selectedType: $viewModel.selectedType)
+                            .onTapGesture {
+                                viewModel.selectedType = .purchase(.videoNum1)
+                            }
+                        ZStack(alignment: .topTrailing) {
+                            TSPurchaseVideoTimesCellView(type: .purchase(.videoNum2), selectedType: $viewModel.selectedType)
+                                .onTapGesture {
+                                    viewModel.selectedType = .purchase(.videoNum2)
+                                }
+                            TSPurchaseVideoTimesRecView()
+                                .offset(x:-30,y:-14)
+                        }
+                        TSPurchaseVideoTimesCellView(type: .purchase(.videoNum3), selectedType: $viewModel.selectedType)
+                            .onTapGesture {
+                                viewModel.selectedType = .purchase(.videoNum3)
+                            }
+                    }).multilineTextAlignment(.center).font(.font(size: 16,weight:.regular)).foregroundColor(.white)
+                    
+                    Spacer().frame(height: 44)
+                    
+                    
+                    Button {
+                        viewModel.buyPublisher.send(true)
+                    } label: {
+                        ZStack {
+                            Image(.purchaseVideoTimesBtnbg)
+                            Text("Purchase".localized)
+                                .font(.font(size: 16,weight: .medium))
+                                .foregroundColor(.white)
+                        }.frame(maxWidth: .infinity ,minHeight: 48.0,maxHeight: 48.0)
+                    }
+                    
+                    
+                    VStack{
+                        Spacer().frame(height: 15)
+                        Button {
+                            withAnimation(.easeInOut(duration: 0.3)) {
+                                isExpand.toggle()
+                            }
+                        } label: {
+                            HStack(spacing: 4) {
+                                Text("View Credit Rules".localized)
+                                    .font(.font(size: 12,weight: .regular))
+                                    .foregroundColor(.white.opacity(0.7))
+                                Image(isExpand ? .chatUpArrow : .chatDownArrow)
+                            }
+                        }
+                    }
+                    
+                    if isExpand {
+                        
+                        Spacer().frame(height: 10)
+                        
+                        VStack(alignment: .leading, spacing: 8, content: {
+                            TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "Only VIP can purchase video effect credits".localized)
+                            TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "Credits can be applied to all video generations".localized)
+                            TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "Credits never expire and can be purchased repeatedly".localized)
+                            TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "All unused credits will be lost if the app is uninstalled".localized)
+                        })
+                        .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
+                        
+                        Spacer().frame(height: 24 + k_Height_safeAreaInsetsBottom())
+                    }else{
+                        Spacer().frame(height: k_Height_safeAreaInsetsBottom())
+                    }
+
+                }
+                .padding(.horizontal,16)
+                .background(
+                    Color.hex("#261840").cornerRadius([.topLeading,.topTrailing], 20)
+                )
+                
+                Button {
+                    viewModel.closePagePublisher.send(true)
+                } label: {
+                    Image(.closeClear).resizable().frame(width: 24, height: 24)
+                }.offset(x:-8,y:8)
+            }
+        }
+        .ignoresSafeArea()
+    }
+}
+
+
+struct TSPurchaseVideoTimesAler1tView :View {
+    @State var isExpand: Bool = false
+    var body: some View {
+        VStack {
+
+            Spacer()
+            
+            VStack {
+                Spacer().frame(height: 24)
+                Text("Limit Reached".localized)
+                
+                Spacer().frame(height: 24)
+                
+                VStack{
+                    Spacer().frame(height: 15)
+                    Button {
+                        isExpand = !isExpand
+                    } label: {
+                        HStack(spacing: 4) {
+                            Text("View Credit Rules".localized)
+                                .font(.font(size: 12,weight: .regular))
+                                .foregroundColor(.white.opacity(0.7))
+                            Image(isExpand ? .chatUpArrow : .chatDownArrow)
+                        }
+                    }
+                    Spacer().frame(height: k_Height_safeAreaInsetsBottom())
+                }
+                
+                if isExpand {
+                    VStack(alignment: .leading, spacing: 8, content: {
+                        Text("View Credit Rules1".localized)
+                        Text("View Credit Rules2".localized)
+                        Text("View Credit Rules3".localized)
+                    })
+                    .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
+                    
+                    Spacer().frame(height: 24 + k_Height_safeAreaInsetsBottom())
+                }
+
+            }
+            .padding(.horizontal,16)
+        }
+        .ignoresSafeArea()
+    }
+}

+ 162 - 0
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVideoTimesVC/View/TSPurchaseVideoTimesView.swift

@@ -0,0 +1,162 @@
+//
+//  TSPurchaseVideoTimesView.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/6.
+//
+
+
+import SwiftUI
+import SwiftUIX
+
+struct TSPurchaseVideoTimesView :View {
+    @ObservedObject var viewModel: TSPurchaseVideoTimesVM
+ 
+    var body: some View {
+        VStack {
+            
+            Spacer()
+            
+            ZStack(alignment: .topTrailing) {
+                VStack {
+                    Spacer().frame(height: 40)
+                    
+                    Text("Video Effect Credits Package".localized)
+                        .font(.font(name:.PoppinsBoldItalic,size: 22))
+                        .gradientForeground(colors: [Color.hex("#8C5FFF"),Color.hex("#F680F8")], startPoint:.leading, endPoint:.trailing)
+                        .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
+                    
+                    
+                    VStack(alignment: .leading, spacing: 8, content: {
+                        TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "Only VIP can purchase video effect credits".localized)
+                        TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "Credits can be applied to all video generations".localized)
+                        TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "Credits never expire and can be purchased repeatedly".localized)
+                        TSPurchaseVideoTextLineView(image: .purchaseVideoTimesCheck, text: "All unused credits will be lost if the app is uninstalled".localized)
+                        
+                    })
+                    .padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10))
+                    
+                    Spacer().frame(height: 43)
+                    
+                    VStack(alignment: .leading, spacing: 20, content: {
+                        TSPurchaseVideoTimesCellView(type: .purchase(.videoNum1), selectedType: $viewModel.selectedType)
+                            .onTapGesture {
+                                viewModel.selectedType = .purchase(.videoNum1)
+                            }
+                        ZStack(alignment: .topTrailing) {
+                            TSPurchaseVideoTimesCellView(type: .purchase(.videoNum2), selectedType: $viewModel.selectedType)
+                                .onTapGesture {
+                                    viewModel.selectedType = .purchase(.videoNum2)
+                                }
+                            TSPurchaseVideoTimesRecView()
+                                .offset(x:-12,y:-9)
+                        }
+                        TSPurchaseVideoTimesCellView(type: .purchase(.videoNum3), selectedType: $viewModel.selectedType)
+                            .onTapGesture {
+                                viewModel.selectedType = .purchase(.videoNum3)
+                            }
+                    }).multilineTextAlignment(.center).font(.font(size: 16,weight:.regular)).foregroundColor(.white)
+                    
+                    Spacer().frame(height: 44)
+                    
+                    
+                    Button {
+                        viewModel.buyPublisher.send(true)
+                    } label: {
+                        ZStack {
+                            Image(.purchaseVideoTimesBtnbg)
+                            Text("Purchase".localized)
+                                .font(.font(size: 16,weight: .medium))
+                                .foregroundColor(.white)
+                        }.frame(maxWidth: .infinity ,minHeight: 48.0,maxHeight: 48.0)
+                    }
+                    
+                    Spacer().frame(height: 24 + k_Height_safeAreaInsetsBottom())
+                    
+                    Text("").frame(height: 1.0)
+                }
+                .padding(.horizontal,16)
+                .background(
+                    Color.hex("#261840").cornerRadius([.topLeading,.topTrailing], 20)
+                )
+
+                Image(.sandGlass)
+                .frame(width: 158, height: 158)
+                .offset(x:0,y:-107)
+            }
+        }
+        .ignoresSafeArea()
+    }
+}
+                       
+
+struct TSPurchaseVideoTextLineView: View {
+   var image:ImageResource
+   var text:String
+   var body: some View {
+       HStack(alignment: .top, spacing: 4) {
+           Image(image).resizable().frame(width: 24, height: 24)
+           Text(text).lineLimit(2).multilineTextAlignment(.leading).font(.font(size: 16,weight:.regular)).foregroundColor(.white)
+       }.frame(height: 40)
+   }
+}
+struct TSPurchaseVideoTimes1View :View {
+
+    var body: some View {
+        VStack {
+            Spacer()
+            ZStack{
+                VStack {
+                    Spacer().frame(height: 40)
+                    Text("Video Effect Credits Package".localized)
+                     Spacer().frame(height: 40)
+                }
+                .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
+                 .background(
+                    Color.white.cornerRadius([.topLeading,.topTrailing], 20)
+                )
+
+                Color.red.frame(width: 100, height: 100)
+            }
+        }
+    }
+}
+
+
+struct TSPurchaseVideoTimesCellView: View {
+    var type: PremiumPeriod
+    @Binding var selectedType: PremiumPeriod
+
+    var body: some View {
+        ZStack {
+            Color.white.opacity(0.1)
+            
+            HStack(spacing:8) {
+                Image(.stopWatch).resizable().frame(width: 28, height: 28)
+                Text(String(format: "%d% Times".localized, type.purchaseNum ?? 0))
+                Spacer()
+                Text(PurchaseManager.default.price(for: type) ?? "--")
+            }
+            .font(.font(size: 18,weight: .medium)).foregroundColor(UIColor.white.color)
+            .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
+        }
+        .frame(height: 60) // 设置高度
+        .cornerRadius(16.0) // 圆角
+        .overlay(
+            RoundedRectangle(cornerRadius: 16)
+                .stroke(Color.hex("#9257FF"), lineWidth: type == selectedType ? 1.5 : 0) // 边框
+        )
+    }
+}
+
+
+struct TSPurchaseVideoTimesRecView: View {
+    var body: some View {
+
+        Text(String(format: "%d%% Picked".localized, 90)).lineLimit(2).multilineTextAlignment(.leading).font(.font(size: 11,weight:.medium)).foregroundColor(Color.hex("#111111"))
+        .frame(height: 18) // 设置高度
+        .padding(EdgeInsets(top: 0, leading: 6, bottom: 0, trailing: 6))
+        .background(Color.hex("#FECB34"))
+        .cornerRadius(9)
+    }
+}

+ 7 - 0
AIEmoji/Business/TSSetingVC/SetingVC/TSSetingModel.swift

@@ -27,8 +27,15 @@ enum SettingType: String, CaseIterable {
     case privacy = "Privacy Policy"
     case agreement = "Terms of Service"
 //    case about = "About us"
+    
+
+    case videoTimes = "Video Effect Times"
+    case changeLanguage = "Change Language"
+    case videoPackage = "Video Effect Times Package"
 #if DEBUG
     case deleteAllData = "删除钥匙串和次数限制"
     case lookAllData = "查看目前的次数"
+    case videoTimesAdd = "视频可次数+1"
+    case videoTimesReduce = "视频可次数-1"
 #endif
 }

+ 12 - 1
AIEmoji/Business/TSSetingVC/SetingVC/TSSetingVC.swift

@@ -85,12 +85,21 @@ class TSSetingVC: TSBaseVC {
 //                break
             case .rateus:
                 viewModel.rateAction()
+            case .videoTimes:
+                dePrint("点击了视频次数")
+            case .changeLanguage:
+                viewModel.changeLanguageVC(parent: self)
+            case .videoPackage:
+                viewModel.purchaseVideoTimesVC(parent: self)
 #if DEBUG
             case .deleteAllData:
                 viewModel.deleteAllData(parent: self)
-                
             case .lookAllData:
                 viewModel.lookAllData(parent: self)
+            case .videoTimesAdd:
+                viewModel.videoTimesAdd(num: 1)
+            case .videoTimesReduce:
+                viewModel.videoTimesReduce(num: -1)
 #endif
             }
             
@@ -99,11 +108,13 @@ class TSSetingVC: TSBaseVC {
         refreshView()
         NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
         NotificationCenter.default.addObserver(self, selector: #selector(refreshView), name: .kRefreshSettingView, object: nil)
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseVideoNumChanged, object: nil)
     }
     
     @objc func vipInfoChanged() {
         kExecuteOnMainThread {
             self.viewModel.vipType = PurchaseManager.default.vipType
+            self.viewModel.videoTimes = kPurchaseDefault.videoAvailableNum
         }
     }
     

+ 35 - 31
AIEmoji/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift

@@ -12,7 +12,11 @@ class TSSetingViewModel: ObservableObject {
     @Published var settingTypes: [SettingType] = SettingType.allCases
     @Published var vipType: PremiumPeriod = PurchaseManager.default.vipType
     @Published var isHaveNewVersion: Bool = false
-
+    @Published var videoTimes: Int = 0
+    
+    init() {
+        videoTimes = kPurchaseDefault.videoAvailableNum
+    }
 
     // todo.kailen-privacy
     func showPrivacy(parent: UIViewController) {
@@ -79,23 +83,6 @@ class TSSetingViewModel: ObservableObject {
             TSToastShared.hideLoading()
             kSaveSuccesswShared.show(atView: parent.view,text: "Removed Successfully".localized,showViewBtn: false)
         }
-    
-#if DEBUG
-//        KeychainManager.clearAll()
-//        UIAlertView(title: "", message: "清除了钥匙串", delegate: nil, cancelButtonTitle: "OK").show()
-//        kSaveSuccesswShared.show(atView: parent.view)
-//        TSAbnormalPopUpAlertVC.showRobotWarning()
-//        guard let window = WindowHelper.getKeyWindow() else {
-//            debugPrint("getKeyWindow nil")
-//            return
-//        }
-//        let topY = k_Nav_Height+10
-//        debugPrint("topY=\(topY)")
-//        kSaveSuccesswShared.show(atView: window,text: "Successfully generated".localized,deadline: 5.0,bottom: kSaveSuccesswShared.getBottom(topY: topY)) {
-//        }
-//    
-//        TSRMShared.textPtpDBHistory()
-#endif
     }
     
     func AboutData(parent: UIViewController) {
@@ -105,7 +92,16 @@ class TSSetingViewModel: ObservableObject {
     }
     
     
-
+    func changeLanguageVC(parent: UIViewController) {
+        kPushVC(target: parent, modelVC: TSChangeLanguageVC())
+    }
+    
+    func purchaseVideoTimesVC(parent: UIViewController) {
+        kPresentModalVC(target: parent, modelVC: TSPurchaseVideoTimesVC(isShowAlertModel: false))
+    }
+    
+    
+#if DEBUG
     
     // todo.kailen-privacy
     func deleteAllData(parent: UIViewController) {
@@ -115,10 +111,8 @@ class TSSetingViewModel: ObservableObject {
         UserDefaults.standard.set(nil, forKey: kFreeNumKey)
         UserDefaults.standard.synchronize()
         TSToastShared.showToast(text: "清除了钥匙串")
-        
     }
     
-    
     func lookAllData(parent: UIViewController) {
         var string = ""
         if var saveDict = UserDefaults.standard.dictionary(forKey: kFreeNumKey) as? [String: Int]{
@@ -138,19 +132,29 @@ class TSSetingViewModel: ObservableObject {
         dePrint("所有的会员次数=\(string)")
     }
     
-}
-
-extension UIImage {
-    // 压缩图片到指定尺寸
-    func compressImageSize(to size: CGSize) -> UIImage {
-        let targetSize = size
-        if self.size.width > targetSize.width || self.size.height > targetSize.height {
-            return kf.resize(to: targetSize)
-        }
-        return self
+    func videoTimesAdd(num:Int) {
+        TSDBTimesManager.purchaseVideo.addTimes()
+        NotificationCenter.default.post(name: .kPurchaseVideoNumChanged, object: nil)
+    }
+    
+    func videoTimesReduce(num:Int) {
+        TSDBTimesManager.purchaseVideo.reduceTimes()
+        NotificationCenter.default.post(name: .kPurchaseVideoNumChanged, object: nil)
     }
+    
+#endif
 }
 
+
+
+
+
+
+
+
+
+
+
 //首次点击保存弹出系统评分弹窗
 func kFirstSaveRateAction(){
     if UserDefaults.standard.string(forKey: "iskFirstSaveRateAction") == nil {

+ 7 - 2
AIEmoji/Business/TSSetingVC/SetingVC/View/SettingPurchaseTopView.swift

@@ -19,8 +19,13 @@ struct SettingPurchaseTopView: View {
             Image(.settingVipBj).resizable().cornerRadius(16.0)
             if vipType == .year {
                 VStack(alignment: .center) {
-                    customGradientText(text: "\(kAppName) pro", font: .font(name: .PoppinsBoldItalic,size: 36))
-                    Spacer().frame(height: 20)//16
+                    customGradientText(text: "\(kAppName) pro", font: .font(name: .PoppinsBoldItalic,size: 32))
+                    Spacer().frame(height: 10)
+                    Text(String(format: "Remaining Times: %d".localized, kPurchaseDefault.videoAvailableNum))
+                        .foregroundColor(UIColor.themeColor.color)
+                        .font(.font(size: 16,weight: .regular))
+                        .frame(height: 16)
+                    Spacer().frame(height: 16)
                     Text(timeString)
                         .foregroundColor(.white.opacity(0.6))
                         .font(.font(size: 14,weight: .medium))

+ 124 - 28
AIEmoji/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift

@@ -11,23 +11,51 @@ struct TSSettingListView: View {
     @ObservedObject var viewModel: TSSetingViewModel
     var publisher: ListEventPublisher
     var body: some View {
+        let spaceHeight = 16.0
         CocoaScrollView {
             VStack(spacing: 0) {
-                Spacer().frame(height: 16)
+                Spacer().frame(height: spaceHeight)
         
                 SettingPurchaseTopView(eventPublisher: publisher, vipType: $viewModel.vipType)
                     .onTapGesture {
                         publisher.enterPurchasePublisher.send(true)
                     }
                 
-                ForEach(viewModel.settingTypes, id:\.self) { type in
-                    Spacer().frame(height: 16)
-                    SettingListItemView(type: type,isHaveNewVersion: $viewModel.isHaveNewVersion)
-                    .onTapGesture {
-                        publisher.settingPublisher.send(type)
-                    }
+                if kPurchaseDefault.isVip {
+                    Spacer().frame(height: spaceHeight)
+                    SettingListVideoItemView(type: .videoPackage, publisher: publisher)
                 }
+            
+                Spacer().frame(height: spaceHeight)
+                VStack(spacing:0) {
+                    SettingListItemView(type: .videoTimes, publisher: publisher, rightView: SettingListNumItemView(num: $viewModel.videoTimes), rightArrow: false)
+                    SettingListItemView(type: .changeLanguage, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .update, publisher: publisher, rightView: SettingListAppUpdateItemView(isHaveNewVersion: $viewModel.isHaveNewVersion), rightArrow: true)
+                }.cornerRadius(16)
+                
+                Spacer().frame(height: spaceHeight)
+                VStack(spacing:0) {
+                    SettingListItemView(type: .rateus, publisher: publisher, rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .shareus, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                }.cornerRadius(16)
                 
+                Spacer().frame(height: spaceHeight)
+                VStack(spacing:0) {
+                    SettingListItemView(type: .AboutData, publisher: publisher, rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .removeCloudData, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .privacy, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .agreement, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                }.cornerRadius(16)
+                
+#if DEBUG
+                Spacer().frame(height: spaceHeight)
+                VStack(spacing:0) {
+                    SettingListItemView(type: .deleteAllData, publisher: publisher, rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .lookAllData, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+//                    SettingListItemView(type: .videoTimesAdd, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                    SettingListItemView(type: .videoTimesReduce, publisher: publisher,rightView: EmptyView(), rightArrow: true)
+                }.cornerRadius(16)
+#endif
                 Spacer()
 
             }.padding(.horizontal)
@@ -38,38 +66,106 @@ struct TSSettingListView: View {
     }
 }
 
-
-
-
-struct SettingListItemView: View {
+struct SettingListItemView<RightView:View>: View {
     var type : SettingType
-    @Binding var isHaveNewVersion: Bool
+    var publisher: ListEventPublisher
+    var rightView:RightView?
+    var rightArrow: Bool
+
     var body: some View {
-        
         ZStack {
             Color.white.opacity(0.1)
             HStack {
                 Text(type.rawValue.localized).font(.font(size: 16.0)).foregroundColor(.white)
                 Spacer()
-//                if type != .about {
-//                    Image(.whiteRightArrow)
-//                }else{
-//                    Text(appVersion()).foregroundColor(.hex("#FFFFFF").opacity(0.4)).font(.font(size: 16))
-//                }
-                
-                if type == .update {
-                    if isHaveNewVersion {
-                        Color.hex("#FECB34").frame(width: 4, height: 4).cornerRadius(2)
-                        Spacer().frame(width: 4)
-                    }
-                    Text(appVersion()).foregroundColor(.hex("#FFFFFF").opacity(0.4)).font(.font(size: 16))
+
+                if let view = rightView{
+                    view
                 }
-            
-                Image(.whiteRightArrow)
+   
+                if rightArrow {
+                    Image(.whiteRightArrow)
+                }
+                
             }.padding(.horizontal)
         }
         .frame(height: 64)
+        .onTapGesture {
+            publisher.settingPublisher.send(type)
+        }
+    }
+}
+
+struct SettingListAppUpdateItemView: View {
+    @Binding var isHaveNewVersion: Bool
+    var body: some View {
+        if isHaveNewVersion {
+            Color.hex("#FECB34").frame(width: 4, height: 4).cornerRadius(2)
+            Spacer().frame(width: 4)
+        }
+        Text(appVersion()).foregroundColor(.hex("#FFFFFF").opacity(0.4)).font(.font(size: 16))
+    }
+}
+
+struct SettingListNumItemView: View {
+    @Binding var num: Int
+    var body: some View {
+        Text("\(num)").foregroundColor(UIColor.themeColor.color).font(.font(size: 16))
+    }
+}
+
+
+
+struct SettingListVideoItemView: View {
+    
+    var type : SettingType
+    var publisher: ListEventPublisher
+    
+    var body: some View {
+        ZStack{
+            LinearGradient(gradient: Gradient(colors: [Color.hex("#583986"),Color.hex("#333C8C")]), startPoint: .leading, endPoint: .trailing)
+            HStack(spacing:8){
+                Image(.hourglass)
+                Text("Video Effect Times Package").font(.font(size: 14.0)).foregroundColor(.white)
+                Spacer()
+                Image(.whiteRightArrow)
+            }
+            .padding(.horizontal)
+        }
+        .frame(height: 48)
         .cornerRadius(16)
-//        .cornerRadius(.allCorners, 16)
+        .onTapGesture {
+            publisher.settingPublisher.send(type)
+        }
     }
 }
+
+
+//
+//struct SettingListItemView: View {
+//    var type : SettingType
+//    var publisher: ListEventPublisher
+//    @Binding var isHaveNewVersion: Bool
+//    
+//    var body: some View {
+//        ZStack {
+//            Color.white.opacity(0.1)
+//            HStack {
+//                Text(type.rawValue.localized).font(.font(size: 16.0)).foregroundColor(.white)
+//                Spacer()
+//                if type == .update {
+//                    if isHaveNewVersion {
+//                        Color.hex("#FECB34").frame(width: 4, height: 4).cornerRadius(2)
+//                        Spacer().frame(width: 4)
+//                    }
+//                    Text(appVersion()).foregroundColor(.hex("#FFFFFF").opacity(0.4)).font(.font(size: 16))
+//                }
+//            
+//                Image(.whiteRightArrow)
+//            }.padding(.horizontal)
+//        }
+//        .frame(height: 64)
+////        .cornerRadius(16)
+////        .cornerRadius(.allCorners, 16)
+//    }
+//}

+ 167 - 0
AIEmoji/Business/TSSetingVC/TSChangeLanguageVC/TSChangeLanguageVC.swift

@@ -0,0 +1,167 @@
+//
+//  TSChangeLanguageVC.swift
+//  TSLiveWallpaper
+//
+//  Created by 100Years on 2025/7/10.
+//
+
+class TSChangeLanguageVC: TSBaseVC {
+    
+    lazy var simpleTableView: TSBaseTableView = {
+        let simpleTableView = TSBaseTableView()
+        simpleTableView.register(TSChangeLanguageCell.self, forCellReuseIdentifier: "TSChangeLanguageCell")
+        simpleTableView.delegate = self
+        simpleTableView.dataSource = self
+        return simpleTableView
+    }()
+    
+    var selectedIndex:Int = 0
+    lazy var dataArray:[LanguageManager.AppLanguage] = LanguageManager.AppLanguage.allCases
+
+    
+    override func createData() {
+        if LanguageManager.shared.followSystem {
+            selectedIndex = 0
+        }else{
+            let currentLanguage = LanguageManager.shared.currentLanguage
+            for (i,language) in dataArray.enumerated() {
+                if language == currentLanguage {
+                    selectedIndex = i
+                    break
+                }
+            }
+        }
+    }
+    
+    override func createView() {
+        setPageTitle("Change Language".localized)
+        
+//        _ = setNavigationItem("Save".localized, imageName: "", direction: .right, action: #selector(clickSave))
+        
+        contentView.addSubview(simpleTableView)
+        simpleTableView.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.trailing.equalTo(-16)
+            make.top.equalTo(16)
+            make.bottom.equalTo(0)
+        }
+    }
+ 
+    @objc func clickSave() {
+        LanguageManager.shared.currentLanguage = dataArray.safeObj(At: selectedIndex) ?? .en
+        TSRTLManage.setUpInit()
+        AppDelegate.shared?.goToTab()
+    }
+}
+
+extension TSChangeLanguageVC : UITableViewDataSource, UITableViewDelegate {
+    
+    public func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+    
+    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return dataArray.count
+    }
+    
+    public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        return 64
+    }
+    
+    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if let cell = tableView.dequeueReusableCell(withIdentifier: "TSChangeLanguageCell") as? TSChangeLanguageCell{
+            if let language = dataArray.safeObj(At: indexPath.row){
+                if let image = UIImage(named: language.flag) {
+                    cell.leftImageView.image = UIImage(named: language.flag)
+                }else{
+                    cell.leftLab.text = language.flag
+                }
+                cell.textLab.text = language.displayName
+                cell.rightImageView.image = selectedIndex ==  indexPath.row ? .check : .unCheck
+            }
+            return cell
+        }
+        
+        return UITableViewCell()
+    }
+    
+    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        selectedIndex = indexPath.row
+        simpleTableView.reloadData()
+        clickSave()
+    }
+}
+
+class TSChangeLanguageCell: UITableViewCell {
+    
+    open lazy var bgContentView:UIView = {
+        let view = UIView()
+        view.backgroundColor = .white.withAlphaComponent(0.1)
+        return view
+    }()
+    
+    public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        self.selectionStyle = .none
+        self.backgroundColor = .clear
+        self.addSubview(bgContentView)
+        bgContentView.snp.makeConstraints { make in
+            make.top.leading.trailing.bottom.equalTo(0)
+        }
+        creatUI()
+    }
+    
+    required public init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    lazy var leftImageView: UIImageView = {
+        let leftImageView = UIImageView.createImageView()
+        return leftImageView
+    }()
+    
+    lazy var leftLab: UILabel = {
+        return UILabel.createLabel(font: .font(size: 16),textColor: .white,textAlignment: .center)
+    }()
+    
+    lazy var textLab: UILabel = {
+        return UILabel.createLabel(font: .font(size: 16),textColor: .white)
+    }()
+
+    lazy var rightImageView: UIImageView = {
+        return UIImageView.createImageView()
+    }()
+    
+
+    func creatUI() {
+        bgContentView.addSubview(leftLab)
+        bgContentView.addSubview(leftImageView)
+        bgContentView.addSubview(textLab)
+        bgContentView.addSubview(rightImageView)
+
+        leftImageView.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.centerY.equalToSuperview()
+            make.width.height.equalTo(20)
+        }
+        
+        leftLab.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.centerY.equalToSuperview()
+            make.width.height.equalTo(20)
+        }
+    
+        textLab.snp.makeConstraints { make in
+            make.leading.equalTo(leftImageView.snp.trailing).offset(12)
+            make.centerY.equalToSuperview()
+        }
+        
+        rightImageView.snp.makeConstraints { make in
+            make.trailing.equalTo(-16)
+            make.centerY.equalToSuperview()
+            make.width.height.equalTo(16)
+        }
+        
+    }
+
+}

+ 14 - 0
AIEmoji/Business/VIewTool/TSGeneratorloadingView/TSGeneratorErrorView.swift

@@ -17,6 +17,8 @@ class TSGeneratorErrorView: TSBaseView {
                 netWorkErrorView()
             case .generateTooMuch:
                 generateTooMuchView()
+            case .generateToMax:
+                generateToMaxView()
             default:
                 generalErrorView()
             }
@@ -130,4 +132,16 @@ extension TSGeneratorErrorView {
 
         textLabel.text = "Your photo may contain nudity, gore or violence that does not comply with the health policy, please replace the photo and try again.".localized
     }
+    
+    
+    func generateToMaxView() {
+        submitBtn.setTitle("Try Again".localized, for: .normal)
+        errorImageView.image = UIImage(named: "yellow_warning")
+        
+        errorImageView.snp.updateConstraints { make in
+            make.width.height.equalTo(56)
+        }
+        textLabel.text = "You have used all your video effect generations".localized
+    }
+    
 }

+ 2 - 0
AIEmoji/Business/VIewTool/TSGeneratorloadingView/TSGeneratorloadingView.swift

@@ -20,6 +20,7 @@ class TSGeneratorView: TSBaseView {
         case sensitiveError   //敏感类的错误
         case netWorkError //网络错误
         case generateTooMuch //生成次数过多
+        case generateToMax //生成次数用完了 vip的次数
     }
     
     var style:Style = .generalError
@@ -178,6 +179,7 @@ extension TSGeneratorView{
         showError(text: text.isEmpty ? kGenerateFailed : text)
         isRotating = false
         setBackgroundGenerateBtnHidden(true)
+
     }
     
     func updateShowSuccess(){

+ 22 - 1
AIEmoji/Business2/DisCover/TSAIGenerateVC/TSAIGenerateVC.swift

@@ -54,6 +54,8 @@ class TSAIGenerateVC: TSAIGenerateBaseVC {
                 clickTryAgainBtn()
             case .sensitiveError:
                 pickSinglePhoto()
+            case .generateToMax:
+                clickTryAgainBtn()
             default:
                 self.dismiss(animated: false, completion: nil)
                 break
@@ -342,7 +344,12 @@ extension TSAIGenerateVC {
         progressState = state
         switch state {
         case let .failed(errorStr, code):
-            showError(text: errorStr, code: code)
+            if handleGenerateToMax(code: code,completion: { [weak self]  in
+                guard let self = self else { return }
+                self.showError(text: errorStr, code: code)
+            }){}else {
+                showError(text: errorStr, code: code)
+            }
         case .success:
             if let model = model {
                 showSuccess(model: model)
@@ -389,3 +396,17 @@ extension TSAIGenerateVC {
         kFirstSaveRateAction()
     }
 }
+
+extension TSAIGenerateVC {
+    
+    func handleGenerateToMax(code:Int,completion: (() -> Void)? = nil) -> Bool {
+        //如果是vip次数超限,则主动触发,让 vc去谈起购买次数的弹窗
+        let style = TSNetWorkCode.getGeneratorStyle(code: code)
+        if style == .generateToMax{
+            kPresentModalVC(target: self, modelVC: TSPurchaseVideoTimesVC(isShowAlertModel: true),transitionStyle:.crossDissolve,completion: completion)
+            return true
+        }
+        
+        return false
+    }
+}

+ 17 - 13
AIEmoji/Business2/DisCover/TSGenerateHistoryVC/TSGenerateHistoryVC.swift

@@ -9,7 +9,7 @@ import RealmSwift
 
 class TSGenerateHistoryVC: TSBaseVC {
 
-    var listModelArray = List<TSDBActionInfoModel>()//惰性加载特性
+    var listModelArray:[TSActionInfoModel] = []
     //###################################### 导航栏 view ######################################
     lazy var vipBtn: UIButton = {
        let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self]  in
@@ -185,8 +185,11 @@ class TSGenerateHistoryVC: TSBaseVC {
     }
     
     @objc func updateDataView(){
-        listModelArray = dbHistory.listModels//惰性加载特性
-        updateView()
+        dbHistory.getModelList(completion: { [weak self] dataArray in
+            guard let self = self else { return }
+            listModelArray = dataArray
+            updateView()
+        })
     }
     
 
@@ -226,8 +229,8 @@ extension TSGenerateHistoryVC: UICollectionViewDataSource ,UICollectionViewDeleg
     public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
         
         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath)
-        if let cell = cell as? TSGenerateHistoryCell ,let dbModel = listModelArray[safe:indexPath.row]{
-            cell.dataModel = dbModel.getModel()
+        if let cell = cell as? TSGenerateHistoryCell ,let dbModel = listModelArray.safeObj(At:indexPath.row){
+            cell.dataModel = dbModel
             cell.buttonTapped = { [weak self] cmd in
                 self?.handelCellCmd(cmd: cmd,indexPath: indexPath)
             }
@@ -237,14 +240,15 @@ extension TSGenerateHistoryVC: UICollectionViewDataSource ,UICollectionViewDeleg
     }
 
     public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
-        guard let selectedModel = listModelArray[safe:indexPath.row] else { return }
-        let filteredResults = listModelArray.filter("status == %@", "success")//直接过滤List(返回Results类型)
-        let filteredList = List<TSDBActionInfoModel>()
-        filteredList.append(objectsIn: filteredResults)
+        guard let selectedModel = listModelArray.safeObj(At:indexPath.row) else { return }
+        let filteredResults = listModelArray.filter({$0.status == "success"})
+        let startIndex = filteredResults.firstIndex(of: selectedModel) ?? 0
+        
+//        let browseVC = TSAIPhotoBrowseVC()
+//        browseVC.dataModelArray = filteredList
+//        browseVC.currentIndex = startIndex
         
-        let browseVC = TSAIPhotoBrowseVC()
-        browseVC.dataModelArray = filteredList
-        browseVC.currentIndex = filteredList.firstIndex(of: selectedModel) ?? 0
+        let browseVC = TSAIPhotoBrowsePageVC(dataItems: filteredResults, startIndex: startIndex)
         browseVC.deleteComplete = { [weak self]  deleteModel in
             guard let self = self else { return }
             dbHistory.deleteListModel(uuid: deleteModel.uuid)
@@ -270,7 +274,7 @@ extension TSGenerateHistoryVC: UICollectionViewDataSource ,UICollectionViewDeleg
 
 extension TSGenerateHistoryVC{
     func handelCellCmd(cmd:String,indexPath: IndexPath){
-        if let currentActionInfoModel = listModelArray[safe:indexPath.row]{
+        if let currentActionInfoModel = listModelArray.safeObj(At:indexPath.row){
             if cmd == "delete_task_expired" {
                 dbHistory.deleteListModel(id: currentActionInfoModel.id)
                 updateDataView()

+ 4 - 4
AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift

@@ -170,7 +170,7 @@ enum TSNetWorkCode : Int {
         case .generateTooMuch:
             return TSGeneratorView.Style.generateTooMuch
         case .generateToMax:
-            return TSGeneratorView.Style.generateTooMuch
+            return TSGeneratorView.Style.generateToMax
         default:
             return TSGeneratorView.Style.generalError
         }
@@ -371,14 +371,14 @@ extension TSNetworkManager {
         progressHandler: @escaping (Float) -> Void, // 上传进度回调
         completion: @escaping (Any?, Error?) -> Void)
     -> Request?{
-        var vipFreeNumType:VipFreeNumType = vipFreeNumType == .videoV2 ? .videoV2 : .aiGenerate
-        ///需要校验且需要判断是否超过最大次数
+        let vipFreeNumType:VipFreeNumType = vipFreeNumType == .videoV2 ? .videoV2 : .aiGenerate
+        ///需要校验,是否超出了 vip 生成的次数,且需要判断是否超过最大次数
         if vipFreeNumType == .videoV2, PurchaseManager.default.isExceedsVipGeneratedNum(vipFreeNumType: .videoV2) {
             completion(nil,NSError(domain: "", code: TSNetWorkCode.generateToMax.rawValue))
             return nil
         }
         
-        ///需要校验。且需要判断是否超过最大次数
+        ///需要校验。且需要判断是否超过每天生成的最大次数
         if PurchaseManager.default.isExceedsDayGeneratedNum(vipFreeNumType: vipFreeNumType) {
             completion(nil,NSError(domain: "", code: TSNetWorkCode.generateTooMuch.rawValue))
             return nil

+ 48 - 7
AIEmoji/Common/Purchase/TSPurchaseManager+Enmu.swift

@@ -14,12 +14,19 @@ public enum PremiumPeriod{
     case year
     case lifetime
     case week(WeekType)
-
+    case purchase(PurchaseType)
+    
     public enum WeekType:String, CaseIterable{
         case week = "week"
         case week1 = "week1"
         case weekPromotional1 = "weekPromotional1"
     }
+    
+    public enum PurchaseType:String, CaseIterable{
+        case videoNum1 = "videoNum1"
+        case videoNum2 = "videoNum2"
+        case videoNum3 = "videoNum3"
+    }
 }
 
 extension PremiumPeriod:CaseIterable ,RawRepresentable {
@@ -32,6 +39,8 @@ extension PremiumPeriod:CaseIterable ,RawRepresentable {
     
     public var rawValue: String{
         switch self {
+        case .purchase(let type):
+            type.rawValue
         case .week(let type):
             type.rawValue
         case .month:
@@ -55,7 +64,9 @@ extension PremiumPeriod:CaseIterable ,RawRepresentable {
          case "Lifetime":
              self = .lifetime
          default:
-             if let weekType = WeekType(rawValue: rawValue){
+             if let purchaseType = PurchaseType(rawValue: rawValue){
+                 self = .purchase(purchaseType)
+             }else if let weekType = WeekType(rawValue: rawValue){
                  self = .week(weekType)
              }else{
                  self = .none
@@ -71,10 +82,16 @@ extension PremiumPeriod:CaseIterable ,RawRepresentable {
             return false
         }
     }
+    
+    var isPurchaseType:Bool {
+        if case .purchase(_) = self {
+            return true
+        }
+        return true
+    }
 }
 
 extension PremiumPeriod {
-
     /// 对应vip类型,可以每天限额次数
     var dayGeneratedNumber: Int {
         return 50//50 3
@@ -83,16 +100,40 @@ extension PremiumPeriod {
     var dayGeneratedVideoNumber: Int {
         return 20//20 3
     }
-    /// 对应vip类型,可以一共限额视频次数
+    /// 对应vip类型,赠送的视频生成次数次数
     var creatVideoMaxNum:Int {
         switch self  {
         case .year:
-            return 200//200 2
+            return  2//200 2
         default:
-            return 20//20 1
+            if self == .none {
+                return 0
+            }else{
+                return 1//20 1
+            }
         }
     }
-    
+}
+
+extension PremiumPeriod {
+    /// 对应vip类型,返回的购买成功后,返回的数额
+    var purchaseNum: Int? {
+        switch self {
+        case .purchase(.videoNum1):
+            return 10
+        case .purchase(.videoNum2):
+            return 20
+        case .purchase(.videoNum3):
+            return 50
+        default:
+            return nil
+        }
+    }
+}
+
+
+
+extension PremiumPeriod {
     var saveString: String {
         switch self {
         case .none:

+ 9 - 10
AIEmoji/Common/Purchase/TSPurchaseManager+Free.swift

@@ -11,14 +11,12 @@ extension PurchaseManager {
     
     /// 使用一次免费次数
     func useOnceForFree(type: VipFreeNumType) {
-        if isVip {
-            return
+        if isVip || videoAvailableNum > 0 {
+            saveForDayGeneratedNum(type: type)
+            saveForVipGeneratedNum(type: type)
+        }else{
+            saveForFreeNum(type: type) //非会员的免费次数
         }
-        
-        /// 总使用次数
-        saveForVipFreeNum(type: type)
-        saveForDayGeneratedNum(type: type)
-        saveForVipGeneratedNum(type: type)
     }
     
     func saveForUserKeychain(data:[String:Any],key:String) {
@@ -65,7 +63,7 @@ extension PurchaseManager {
 
     }
 
-    func saveForVipFreeNum(type: VipFreeNumType){
+    func saveForFreeNum(type: VipFreeNumType){
         var freeDict = getFreeDict()
         var freeNum = freeDict[type.rawValue] ?? 0
         if freeNum > 0 {
@@ -105,10 +103,11 @@ extension PurchaseManager {
 
     /// 免费次数是否可用
     func freeNumAvailable(type: VipFreeNumType) -> Bool {
-        if isVip == true {
+//        if isVip == true {
+        if isVip == true ||  (type == .videoV2 && videoAvailableNum > 0) {
             return true
         } else {
-            if let freeNum = getFreeDict()[type.rawValue], freeNum > 0 {
+            if freeNum(type: type) > 0 {
                 return true
             }
         }

+ 64 - 15
AIEmoji/Common/Purchase/TSPurchaseManager+Limit.swift

@@ -65,28 +65,31 @@ extension PurchaseManager {
     //是否超出Vip期间使用的次数
     public func isExceedsVipGeneratedNum(vipFreeNumType:VipFreeNumType) -> Bool {
         if isVip {
-            let dayNum = loadVipGeneratedNum(type: vipFreeNumType)
-            if vipFreeNumType == .videoV2 {
-                return dayNum >= vipType.creatVideoMaxNum
-            }
-            return false
+            return videoAvailableNum <= 0 //次数不够的,返回 true
         }
         return false
     }
 
     func saveForVipGeneratedNum(type: VipFreeNumType) {
         let type = getDayGeneratedNum(type: type)
+        var num = loadVipGeneratedNum(type: type)
         
-        var dayNum = loadVipGeneratedNum(type: type)
-        dayNum = dayNum + 1
-        // 保存新的记录
-        var dict: [String: [String: Any]] = [:]
-        if var saveDict = UserDefaults.standard.dictionary(forKey: kVipGeneratedNumKey) as? [String: [String: Any]]{
-            dict = saveDict
+        //购买视频次数相关
+        //检查vip自带的视频生成次数,是否用完了,如果用完了,就扣购买的次数
+        if vipType.creatVideoMaxNum - num <= 0{
+            reduceVideoPurchasedNum(type: type)
+        }else{
+            //增加视频生成次数记录
+            num = num + 1
+            var dict: [String: [String: Any]] = [:]
+            if let saveDict = UserDefaults.standard.dictionary(forKey: kVipGeneratedNumKey) as? [String: [String: Any]]{
+                dict = saveDict
+            }
+            dict[type.rawValue] = ["times": num,"expireTime":expireTime]
+            saveForUserKeychain(data: dict,key: kVipGeneratedNumKey)
         }
-        dict[type.rawValue] = ["times": dayNum,"expireTime":expireTime]
-        
-        saveForUserKeychain(data: dict,key: kVipGeneratedNumKey)
+   
+        NotificationCenter.default.post(name: .kPurchaseVideoNumChanged, object: nil)
     }
 
     func loadVipGeneratedNum(type: VipFreeNumType)-> Int {
@@ -99,8 +102,54 @@ extension PurchaseManager {
             return 0
         }
         dePrint("从钥匙串 loadVipGeneratedNum=\(dict)")
-        // 有记录,设置已经使用次数
+        // 有记录,读取已经使用次数
         return typeDict.safeInt(forKey: "times")
     }
     
 }
+
+
+//extension PurchaseManager {
+//    
+//    //是否超出Vip期间使用的次数
+//    public func isExceedsVipGeneratedNum(vipFreeNumType:VipFreeNumType) -> Bool {
+//        if isVip {
+//            let dayNum = loadVipGeneratedNum(type: vipFreeNumType)
+//            if vipFreeNumType == .videoV2 {
+//                return dayNum >= vipType.creatVideoMaxNum
+//            }
+//            return false
+//        }
+//        return false
+//    }
+//
+//    func saveForVipGeneratedNum(type: VipFreeNumType) {
+//        let type = getDayGeneratedNum(type: type)
+//        
+//        var dayNum = loadVipGeneratedNum(type: type)
+//        dayNum = dayNum + 1
+//        // 保存新的记录
+//        var dict: [String: [String: Any]] = [:]
+//        if var saveDict = UserDefaults.standard.dictionary(forKey: kVipGeneratedNumKey) as? [String: [String: Any]]{
+//            dict = saveDict
+//        }
+//        dict[type.rawValue] = ["times": dayNum,"expireTime":expireTime]
+//        
+//        saveForUserKeychain(data: dict,key: kVipGeneratedNumKey)
+//    }
+//
+//    func loadVipGeneratedNum(type: VipFreeNumType)-> Int {
+//        let type = getDayGeneratedNum(type: type)
+//        // 当天没记录,设置默认次数
+//        guard let dict = UserDefaults.standard.dictionary(forKey: kVipGeneratedNumKey),
+//              let typeDict = dict.safeDictionary(forKey: type.rawValue) as? [String: Any],
+//              typeDict.safeString(forKey: "expireTime") == expireTime
+//        else {
+//            return 0
+//        }
+//        dePrint("从钥匙串 loadVipGeneratedNum=\(dict)")
+//        // 有记录,设置已经使用次数
+//        return typeDict.safeInt(forKey: "times")
+//    }
+//    
+//}

+ 83 - 0
AIEmoji/Common/Purchase/TSPurchaseManager+PurchasedNum.swift

@@ -0,0 +1,83 @@
+//
+//  TSPurchaseManager+PurchasedNum.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/8/1.
+//
+
+
+public extension Notification.Name {
+    static let kPurchaseVideoNumChanged = Self("kPurchaseVideoNumChanged") //购买视频的生成次数发生了变化
+}
+//购买视频次数相关
+extension PurchaseManager {
+    func analyzeDataReceiptForPurchase(resp: [String: Any]) {
+        if let receipt = resp["receipt"] as? [String: Any],
+           let in_app = receipt["in_app"] as? [[String: Any]]
+        {
+            //从交易成功的本地数据,拿到已购买成功的消耗品数据
+            let purchasedArray = in_app.filter{ dict in
+                guard let productId = dict["product_id"] as? String else { return false }
+                return purchaseNumProducts.contains(productId)
+            }
+
+            for dict in purchasedArray {
+                if let transaction_id = dict["transaction_id"] as? String,
+                   let product_id = dict["product_id"] as? String,
+                   let premiumPeriod = premiumPeriod(productId: product_id),
+                    let purchaseNum = premiumPeriod.purchaseNum
+                {
+                    //满足条件后,会增加生成次数的
+                    TSDBTimesManager.purchaseVideo.addNewRecords(key: transaction_id, dict: dict, num: purchaseNum)
+                }
+            }
+            
+            NotificationCenter.default.post(name: .kPurchaseVideoNumChanged, object: nil)
+        }
+    }
+    
+    //消耗购买的视频次数
+    func reduceVideoPurchasedNum(type: VipFreeNumType) {
+        if type == .videoV2 {
+            TSDBTimesManager.purchaseVideo.reduceTimes()
+        }
+    }
+    
+    //视频生成可用的次数
+    var videoAvailableNum:Int {
+        let vipFreeNumType:VipFreeNumType = .videoV2
+        var vipNum = vipType.creatVideoMaxNum - loadVipGeneratedNum(type:vipFreeNumType) //会员自身带的额度
+        vipNum = max(0, vipNum)
+        let purchaseNum = TSDBTimesManager.purchaseVideo.times //用户后面购买的次数
+        let total = vipNum + purchaseNum
+        dePrint("视频可用次数total=\(total),vipNum=\(vipNum),purchaseNum=\(purchaseNum)")
+        return total
+    }
+}
+
+
+
+
+//消耗型商品的唯一标识选择
+//✅ ​**推荐方案:transaction_id**
+//​原因​:
+//每次购买(即使是同一商品)都会生成全新的 `transaction_id。
+//苹果保证其全局唯一性(跨用户、跨设备、跨时间)。
+//直接对应单次交易,完美匹配消耗型商品的“一次性”特性。
+//⚠️ ​**不要使用 original_transaction_id**
+//这是为订阅设计的字段,对消耗型商品无意义(每次购买都会生成相同的 original_transaction_id 和 transaction_id)。
+//    "in_app" =     (
+//                {
+//            "in_app_ownership_type" = PURCHASED;
+//            "is_trial_period" = false;
+//            "original_purchase_date" = "2025-08-01 07:06:23 Etc/GMT";
+//            "original_purchase_date_ms" = 1754031983000;
+//            "original_purchase_date_pst" = "2025-08-01 00:06:23 America/Los_Angeles";
+//            "original_transaction_id" = 2000000974433576;
+//            "product_id" = 201;
+//            "purchase_date" = "2025-08-01 07:06:23 Etc/GMT";
+//            "purchase_date_ms" = 1754031983000;
+//            "purchase_date_pst" = "2025-08-01 00:06:23 America/Los_Angeles";
+//            quantity = 1;
+//            "transaction_id" = 2000000974433576;
+//        },

+ 28 - 8
AIEmoji/Common/Purchase/TSPurchaseManager.swift

@@ -31,10 +31,19 @@ public class PurchaseManager: NSObject {
             PurchaseProduct(productId: "101", period: .month),//增加月付费
             PurchaseProduct(productId: "102", period: .year),
             PurchaseProduct(productId: "103", period: .week(.week)),
-            PurchaseProduct(productId: "113", period: .week(.weekPromotional1))
+            PurchaseProduct(productId: "113", period: .week(.weekPromotional1)),
+            
+            PurchaseProduct(productId: "201", period: .purchase(.videoNum1)),
+            PurchaseProduct(productId: "202", period: .purchase(.videoNum1)),
+            PurchaseProduct(productId: "203", period: .purchase(.videoNum1)),
         ]
     }()
 
+
+    lazy var purchaseNumProducts: [String] = {
+        return ["201","202","203"]
+    }()
+    
     struct Config {
         static let verifyUrl = "https://buy.itunes.apple.com/verifyReceipt"
         static let sandBoxUrl = "https://sandbox.itunes.apple.com/verifyReceipt"
@@ -112,9 +121,9 @@ public class PurchaseManager: NSObject {
     }
 
     @objc public var isVip: Bool {
-#if DEBUG
-        return true
-#endif
+//#if DEBUG
+//        return vipType != .none
+//#endif
         guard let expiresDate = expiredDate else {
             return false
         }
@@ -154,6 +163,10 @@ public class PurchaseManager: NSObject {
         return purchaseProducts.first(where: { $0.productId == productId })?.period ?? .none
     }
 
+    func premiumPeriod(productId: String) -> PremiumPeriod? {
+        return purchaseProducts.first(where: { $0.productId == productId })?.period
+    }
+    
     // 时间周期对应的商品id
     func productId(for period: PremiumPeriod) -> String? {
         return purchaseProducts.first(where: { $0.period == period })?.productId
@@ -486,7 +499,7 @@ extension PurchaseManager {
             guard let self = self else { return }
             if let data = data,
                let jsonResponse = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
-//                debugPrint("PurchaseManager verifyPayResult = \(jsonResponse)")
+                debugPrint("PurchaseManager verifyPayResult = \(jsonResponse)")
                 let status = jsonResponse["status"]
                 if let status = status as? String, status == "21007" {
                     self.verifyPayResult(transaction: transaction, useSandBox: true)
@@ -518,6 +531,11 @@ extension PurchaseManager {
     }
 
     func handlerPayResult(transaction: SKPaymentTransaction, resp: [String: Any]) {
+        
+        //购买视频次数相关
+        //分析处理购买消耗品的逻辑处理
+        analyzeDataReceiptForPurchase(resp: resp)
+        
         var isLifetime = false
         // 终生会员
         if let receipt = resp["receipt"] as? [String: Any],
@@ -568,11 +586,11 @@ extension PurchaseManager {
         guard !resp.isEmpty else {
             return false
         }
-        
+
         //取当前已生效的订单号
         guard let info = resp["latest_receipt_info"] as? [[String: Any]]
         else { return false}
-        
+
         /*
         pending_renewal_info={
             "auto_renew_product_id" = 102;
@@ -632,7 +650,9 @@ extension PurchaseManager {
 
         // 创建数据任务
         let task = URLSession.shared.dataTask(with: request) { data, _, error in
-            completion(data, error)
+            DispatchQueue.main.async{
+                completion(data, error)
+            }
         }
 
         // 启动任务

+ 147 - 0
AIEmoji/Common/Tool/LanguageManager.swift

@@ -0,0 +1,147 @@
+//
+//  LanguageManager.swift
+//  TSLiveWallpaper
+//
+//  Created by 100Years on 2025/7/10.
+//
+
+
+// 主工程中
+class MainLocalizationProvider: LocalizationProvider {
+    func localizedString(for key: String) -> String {
+        // 主工程自定义逻辑
+        return LanguageManager.shared.localizedString(for: key)
+    }
+
+    func isArabic() -> Bool {
+        return LanguageManager.shared.isArabic
+    }
+}
+
+class LanguageManager {
+    static let shared = LanguageManager()
+    
+    private let userDefaultsKey = "SelectedAppLanguage"
+    private var bundle: Bundle?
+    
+    // 支持的语言列表
+    enum AppLanguage: String, CaseIterable {
+        case system = "system" // 跟随系统
+        case en = "en"    // 英语
+        case zhHans = "zh-Hans" // 中文简体
+        case ko = "ko"    // 韩语
+        case es = "es"    // 西班牙
+        case pt = "pt-PT"    // 葡萄牙
+        case ja = "ja"    // 日语
+        case de = "de"    // 德
+        case zhHant = "zh-Hant" // 中文简体
+
+        var displayName: String {
+            switch self {
+            case .system: return "Follow System".localized
+            case .en: return "English"
+            case .zhHans: return "简体中文"
+            case .ja: return "日本語"
+            case .zhHant: return "繁體中文"
+            case .es: return "Español"
+            case .ko: return "한국어"
+            case .de: return "Deutsch"
+            case .pt: return "Português"
+            }
+        }
+        
+        var flag: String {
+            switch self {
+            case .system: return "language_System"
+            case .en: return "🇺🇸"
+            case .ja: return "🇯🇵"
+            case .zhHans: return "🇨🇳"
+            case .zhHant: return "🇭🇰"
+            case .es: return "🇪🇸"
+            case .ko: return "🇰🇷"
+            case .de: return "🇩🇪"
+            case .pt: return "🇧🇷"
+            }
+        }
+
+    }
+    
+    var isArabic:Bool {
+        return false
+    }
+    
+    // 当前语言
+    var currentLanguage: AppLanguage {
+        get {
+            // 先从用户选择中读取
+            if let savedLanguage = UserDefaults.standard.string(forKey: userDefaultsKey),
+               let language = AppLanguage(rawValue: savedLanguage) {
+                return language
+            }
+            
+            // 没有用户选择,则根据系统语言自动选择
+            return preferredLanguage(for: Locale.preferredLanguages.first)
+        }
+        set {
+            UserDefaults.standard.set(newValue.rawValue, forKey: userDefaultsKey)
+            setLanguage(newValue)
+        }
+    }
+    
+    var followSystem:Bool{
+        return UserDefaults.standard.string(forKey: userDefaultsKey) == nil ? true : false
+    }
+    
+    // 初始化
+    private init() {
+        // 初始化时设置当前语言
+        setLanguage(currentLanguage)
+        LocalizationManager.sharedProvider = MainLocalizationProvider()
+    }
+    
+    // 根据系统语言选择最合适的应用语言
+    private func preferredLanguage(for systemLanguage: String?) -> AppLanguage {
+        guard let systemLanguage = systemLanguage else { return .en }
+        
+        // 处理中文情况
+        if systemLanguage.hasPrefix("zh-") {
+            if systemLanguage.contains("Hans") { // 简体中文
+                return .zhHant // 简体中文使用繁体中文显示
+            }
+            return .zhHant // 其他中文变体使用繁体中文
+        }
+        
+        // 检查是否支持系统语言
+        for language in AppLanguage.allCases {
+            if systemLanguage.hasPrefix(language.rawValue) {
+                return language
+            }
+        }
+        
+        // 默认英语
+        return .en
+    }
+    
+    // 设置语言并加载对应的语言包
+    private func setLanguage(_ language: AppLanguage) {
+        guard let path = Bundle.main.path(forResource: language.rawValue, ofType: "lproj") else {
+            bundle = nil
+            return
+        }
+        bundle = Bundle(path: path)
+        
+        // 发送语言变更通知
+        NotificationCenter.default.post(name: .languageChanged, object: nil)
+    }
+    
+    // 本地化字符串方法
+    func localizedString(for key: String, value: String? = nil) -> String {
+        let value = value ?? key
+        return bundle?.localizedString(forKey: key, value: value, table: nil) ?? Bundle.main.localizedString(forKey: key, value: value, table: nil)
+    }
+}
+
+// 语言变更通知
+extension Notification.Name {
+    static let languageChanged = Notification.Name("LanguageChangedNotification")
+}

+ 1 - 1
AIEmoji/Common/View/TSPhotoPickerManager/TSPhotoPickerManager.swift

@@ -45,7 +45,7 @@ class TSPhotoPickerManager: NSObject {
             completion(true)
         case .notDetermined:
             PHPhotoLibrary.requestAuthorization { newStatus in
-                DispatchQueue.main.async {
+                DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
                     completion(newStatus == .authorized)
                 }
             }