Kaynağa Gözat

1.1(1)新增订阅功能

100Years 2 ay önce
ebeveyn
işleme
9d72cf7f9e
63 değiştirilmiş dosya ile 1497 ekleme ve 111 silme
  1. 32 4
      AIEmoji.xcodeproj/project.pbxproj
  2. 1 1
      AIEmoji.xcodeproj/xcuserdata/100years.xcuserdatad/xcschemes/xcschememanagement.plist
  3. 6 0
      AIEmoji/Assets.xcassets/VIP/Contents.json
  4. 22 0
      AIEmoji/Assets.xcassets/VIP/check.imageset/Contents.json
  5. BIN
      AIEmoji/Assets.xcassets/VIP/check.imageset/check@2x.png
  6. BIN
      AIEmoji/Assets.xcassets/VIP/check.imageset/check@3x.png
  7. 22 0
      AIEmoji/Assets.xcassets/VIP/close_gray.imageset/Contents.json
  8. BIN
      AIEmoji/Assets.xcassets/VIP/close_gray.imageset/close_gray@2x.png
  9. BIN
      AIEmoji/Assets.xcassets/VIP/close_gray.imageset/close_gray@3x.png
  10. 22 0
      AIEmoji/Assets.xcassets/VIP/nav_vip.imageset/Contents.json
  11. BIN
      AIEmoji/Assets.xcassets/VIP/nav_vip.imageset/nav_vip@2x.png
  12. BIN
      AIEmoji/Assets.xcassets/VIP/nav_vip.imageset/nav_vip@3x.png
  13. 22 0
      AIEmoji/Assets.xcassets/VIP/purchase_bj.imageset/Contents.json
  14. BIN
      AIEmoji/Assets.xcassets/VIP/purchase_bj.imageset/purchase_bj@2x.png
  15. BIN
      AIEmoji/Assets.xcassets/VIP/purchase_bj.imageset/purchase_bj@3x.png
  16. 22 0
      AIEmoji/Assets.xcassets/VIP/radioboxSelected.imageset/Contents.json
  17. BIN
      AIEmoji/Assets.xcassets/VIP/radioboxSelected.imageset/radioboxSelected@2x.png
  18. BIN
      AIEmoji/Assets.xcassets/VIP/radioboxSelected.imageset/radioboxSelected@3x.png
  19. 22 0
      AIEmoji/Assets.xcassets/VIP/setting_noVip.imageset/Contents.json
  20. BIN
      AIEmoji/Assets.xcassets/VIP/setting_noVip.imageset/setting_noVip@2x.png
  21. BIN
      AIEmoji/Assets.xcassets/VIP/setting_noVip.imageset/setting_noVip@3x.png
  22. 22 0
      AIEmoji/Assets.xcassets/VIP/setting_vip.imageset/Contents.json
  23. BIN
      AIEmoji/Assets.xcassets/VIP/setting_vip.imageset/setting_vip@2x.png
  24. BIN
      AIEmoji/Assets.xcassets/VIP/setting_vip.imageset/setting_vip@3x.png
  25. 22 0
      AIEmoji/Assets.xcassets/VIP/vip_big_icon.imageset/Contents.json
  26. BIN
      AIEmoji/Assets.xcassets/VIP/vip_big_icon.imageset/vip_big_icon@2x.png
  27. BIN
      AIEmoji/Assets.xcassets/VIP/vip_big_icon.imageset/vip_big_icon@3x.png
  28. 22 0
      AIEmoji/Assets.xcassets/VIP/vip_icon.imageset/Contents.json
  29. BIN
      AIEmoji/Assets.xcassets/VIP/vip_icon.imageset/vip_icon@2x.png
  30. BIN
      AIEmoji/Assets.xcassets/VIP/vip_icon.imageset/vip_icon@3x.png
  31. 22 0
      AIEmoji/Assets.xcassets/VIP/vip_side_icon.imageset/Contents.json
  32. BIN
      AIEmoji/Assets.xcassets/VIP/vip_side_icon.imageset/vip_big_icon@2x.png
  33. BIN
      AIEmoji/Assets.xcassets/VIP/vip_side_icon.imageset/vip_big_icon@3x.png
  34. 34 0
      AIEmoji/Business/General/TSSmallIconBrowseVC/TSSmallIconBrowseVC.swift
  35. 14 0
      AIEmoji/Business/General/TSSmallIconBrowseVC/View/TSSmallIconBrowseCell.swift
  36. 3 3
      AIEmoji/Business/LaunchVC/TSLaunchVC.swift
  37. 1 0
      AIEmoji/Business/TSEmojisVC/TSEmojisChildVC/TSEmojisChildVC.swift
  38. 14 0
      AIEmoji/Business/TSEmojisVC/TSEmojisChildVC/VIew/TSEmojisCoLItemCell.swift
  39. 30 0
      AIEmoji/Business/TSEmojisVC/TSEmojisVC/TSEmojisVC.swift
  40. 2 0
      AIEmoji/Business/TSGenmojiVC/TSGenmojiGennerateVC/TSGenmojiGennerateVC.swift
  41. 2 0
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/Model/TSGenmojiModel.swift
  42. 39 1
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/TSGenmojiVC.swift
  43. 13 0
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/View/TSGenmojiGennerateCell.swift
  44. 335 0
      AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift
  45. 1 1
      AIEmoji/Business/TSSetingVC/SetingVC/TSSetingModel.swift
  46. 12 2
      AIEmoji/Business/TSSetingVC/SetingVC/TSSetingVC.swift
  47. 9 0
      AIEmoji/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift
  48. 53 0
      AIEmoji/Business/TSSetingVC/SetingVC/View/SettingPurchaseTopView.swift
  49. 9 16
      AIEmoji/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift
  50. 1 1
      AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift
  51. 585 0
      AIEmoji/Common/Purchase/TSPurchaseManager.swift
  52. 0 1
      AIEmoji/Common/Tool/TSNetworkTool.swift
  53. 6 6
      AIEmoji/Res/Beauty👸.json
  54. 5 5
      AIEmoji/Res/Cat🐱.json
  55. 13 13
      AIEmoji/Res/Crystal🔮.json
  56. 5 5
      AIEmoji/Res/Dog🐕.json
  57. 7 7
      AIEmoji/Res/Drink🥤.json
  58. 5 5
      AIEmoji/Res/Fish🐠.json
  59. 7 7
      AIEmoji/Res/Flower💐.json
  60. 9 9
      AIEmoji/Res/Food🍔.json
  61. 8 8
      AIEmoji/Res/Fruit🍊.json
  62. 5 5
      AIEmoji/Res/Pink🩷.json
  63. 11 11
      AIEmoji/Res/Universe🌍.json

+ 32 - 4
AIEmoji.xcodeproj/project.pbxproj

@@ -37,6 +37,9 @@
 		A80E72742D40FEA600C64288 /* UIApplication+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E72732D40FEA000C64288 /* UIApplication+Ex.swift */; };
 		A80E72772D41EFF900C64288 /* TSEmojisTutorialsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E72762D41EFF700C64288 /* TSEmojisTutorialsVC.swift */; };
 		A80E72792D42285500C64288 /* TSBootPageVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E72782D42285500C64288 /* TSBootPageVC.swift */; };
+		A80E73E12D533E5800C64288 /* TSPurchaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E73D82D533E5800C64288 /* TSPurchaseVC.swift */; };
+		A80E73E42D533EB000C64288 /* TSPurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E73E22D533EB000C64288 /* TSPurchaseManager.swift */; };
+		A80E73E62D5348D000C64288 /* SettingPurchaseTopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80E73E52D5348CF00C64288 /* SettingPurchaseTopView.swift */; };
 		A8EEADD42D3E6C660032C5A0 /* Flower💐.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD32D3E6C610032C5A0 /* Flower💐.json */; };
 		A8EEADD62D3E6CD80032C5A0 /* Fish🐠.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */; };
 		A8EEADD82D3E74D20032C5A0 /* Pink🩷.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */; };
@@ -164,6 +167,9 @@
 		A80E72732D40FEA000C64288 /* UIApplication+Ex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Ex.swift"; sourceTree = "<group>"; };
 		A80E72762D41EFF700C64288 /* TSEmojisTutorialsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSEmojisTutorialsVC.swift; sourceTree = "<group>"; };
 		A80E72782D42285500C64288 /* TSBootPageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBootPageVC.swift; sourceTree = "<group>"; };
+		A80E73D82D533E5800C64288 /* TSPurchaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseVC.swift; sourceTree = "<group>"; };
+		A80E73E22D533EB000C64288 /* TSPurchaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPurchaseManager.swift; sourceTree = "<group>"; };
+		A80E73E52D5348CF00C64288 /* SettingPurchaseTopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingPurchaseTopView.swift; sourceTree = "<group>"; };
 		A8EEADD32D3E6C610032C5A0 /* Flower💐.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Flower💐.json"; sourceTree = "<group>"; };
 		A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Fish🐠.json"; sourceTree = "<group>"; };
 		A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Pink🩷.json"; sourceTree = "<group>"; };
@@ -420,6 +426,22 @@
 			path = TSEmojisTutorialsVC;
 			sourceTree = "<group>";
 		};
+		A80E73DD2D533E5800C64288 /* TSPurchaseMembershipVC */ = {
+			isa = PBXGroup;
+			children = (
+				A80E73D82D533E5800C64288 /* TSPurchaseVC.swift */,
+			);
+			path = TSPurchaseMembershipVC;
+			sourceTree = "<group>";
+		};
+		A80E73E32D533EB000C64288 /* Purchase */ = {
+			isa = PBXGroup;
+			children = (
+				A80E73E22D533EB000C64288 /* TSPurchaseManager.swift */,
+			);
+			path = Purchase;
+			sourceTree = "<group>";
+		};
 		A8F774602D38E8B000AA6E93 = {
 			isa = PBXGroup;
 			children = (
@@ -456,6 +478,7 @@
 		A8F774922D38EA8C00AA6E93 /* Business */ = {
 			isa = PBXGroup;
 			children = (
+				A80E73DD2D533E5800C64288 /* TSPurchaseMembershipVC */,
 				A80E72702D40F85800C64288 /* LaunchVC */,
 				A8F775142D38EB5300AA6E93 /* TSWallpaperVC */,
 				A8F776402D3B75EA00AA6E93 /* General */,
@@ -599,6 +622,7 @@
 		A8F774D82D38EA8C00AA6E93 /* Common */ = {
 			isa = PBXGroup;
 			children = (
+				A80E73E32D533EB000C64288 /* Purchase */,
 				A8F7749B2D38EA8C00AA6E93 /* BaseClass */,
 				A8F774B32D38EA8C00AA6E93 /* Ex */,
 				A8F774B52D38EA8C00AA6E93 /* GlobalImports */,
@@ -708,6 +732,7 @@
 		A8F775462D39348500AA6E93 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				A80E73E52D5348CF00C64288 /* SettingPurchaseTopView.swift */,
 				A8F7754A2D39376700AA6E93 /* TSSettingListView.swift */,
 			);
 			path = View;
@@ -1038,6 +1063,7 @@
 				A8F774E02D38EA8C00AA6E93 /* TSCommonTool.swift in Sources */,
 				A8F774E12D38EA8C00AA6E93 /* TSFileManagerTool.swift in Sources */,
 				A8F774E22D38EA8C00AA6E93 /* UICollectionView+Ex.swift in Sources */,
+				A80E73E42D533EB000C64288 /* TSPurchaseManager.swift in Sources */,
 				A8F774E32D38EA8C00AA6E93 /* TSBasicItemModel.swift in Sources */,
 				A8F7762F2D3A765400AA6E93 /* TSGenmojiViewModel.swift in Sources */,
 				A8F7751B2D38EC9800AA6E93 /* TSGenmojiVC.swift in Sources */,
@@ -1072,6 +1098,7 @@
 				A8F775172D38EB7400AA6E93 /* TSTabBarController.swift in Sources */,
 				A8F774F62D38EA8C00AA6E93 /* UIImageView+Ex.swift in Sources */,
 				A8F776272D3A6EC200AA6E93 /* TSGenmojiTextView.swift in Sources */,
+				A80E73E12D533E5800C64288 /* TSPurchaseVC.swift in Sources */,
 				A8F774F82D38EA8C00AA6E93 /* TSSimpleTableView.swift in Sources */,
 				A8F776352D3A7C2B00AA6E93 /* TSGenmojiColSectionView.swift in Sources */,
 				A8F774F92D38EA8C00AA6E93 /* UIColor+Ex.swift in Sources */,
@@ -1109,6 +1136,7 @@
 				A8F775322D38FA5E00AA6E93 /* UITextView+Ex.swift in Sources */,
 				A8F7753F2D39340E00AA6E93 /* TSSetingVC.swift in Sources */,
 				A8F7762B2D3A70B200AA6E93 /* PaddedLabel.swift in Sources */,
+				A80E73E62D5348D000C64288 /* SettingPurchaseTopView.swift in Sources */,
 				A80E72382D3F473B00C64288 /* DiyPaperProtocol.swift in Sources */,
 				A8F775382D390C3C00AA6E93 /* TSNetworkManager.swift in Sources */,
 				A8F7750B2D38EA8C00AA6E93 /* TSBaseTabViewCell.swift in Sources */,
@@ -1141,7 +1169,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 13;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -1157,7 +1185,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0;
+				MARKETING_VERSION = 1.1;
 				PRODUCT_BUNDLE_IDENTIFIER = com.girl.music.wallpaper;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -1180,7 +1208,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 13;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -1196,7 +1224,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 1.0;
+				MARKETING_VERSION = 1.1;
 				PRODUCT_BUNDLE_IDENTIFIER = com.girl.music.wallpaper;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

+ 1 - 1
AIEmoji.xcodeproj/xcuserdata/100years.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -7,7 +7,7 @@
 		<key>AIEmoji.xcscheme_^#shared#^_</key>
 		<dict>
 			<key>orderHint</key>
-			<integer>33</integer>
+			<integer>0</integer>
 		</dict>
 	</dict>
 	<key>SuppressBuildableAutocreation</key>

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

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

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

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

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


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


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

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

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


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


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

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

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


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


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

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

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


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


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

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

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


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


+ 22 - 0
AIEmoji/Assets.xcassets/VIP/setting_noVip.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/VIP/setting_noVip.imageset/setting_noVip@2x.png


BIN
AIEmoji/Assets.xcassets/VIP/setting_noVip.imageset/setting_noVip@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/VIP/setting_vip.imageset/Contents.json

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

BIN
AIEmoji/Assets.xcassets/VIP/setting_vip.imageset/setting_vip@2x.png


BIN
AIEmoji/Assets.xcassets/VIP/setting_vip.imageset/setting_vip@3x.png


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

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

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


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


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

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

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


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


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

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

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


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


+ 34 - 0
AIEmoji/Business/General/TSSmallIconBrowseVC/TSSmallIconBrowseVC.swift

@@ -16,6 +16,14 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
         image = image?.pngImage
         return image
     }
+    var currentModel:TSGenmojiModel?{
+        if let model = dataModelArray.safeObj(At: currentIndex){
+            return model
+        }
+        return nil
+    }
+    
+    
     var currentIndex:Int = 0 {
         didSet{
             reloadUI()
@@ -86,6 +94,11 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
     
     
     @objc override func clickCancelBtn(){
+        
+        if JudgeVip(){
+            return
+        }
+  
         if let image = currentImage{
             UIDevice.copyImage(image: image)
             kSavePhotoSuccesswShared.show(atView: self.view,text: "Copy Successfully".localized,showViewBtn:false)
@@ -96,6 +109,11 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
     }
     
     @objc override func clickConfirmBtn(){
+        
+        if JudgeVip(){
+            return
+        }
+        
         if let image = currentImage{
             PhotoManagerShared.saveImageToAlbum(image) { success, error in
                 if success {
@@ -109,6 +127,21 @@ class TSSmallIconBrowseVC: TSBottomAlertVC {
             kShowToastDataMissing()
         }
     }
+    
+    
+    func JudgeVip() -> Bool {
+        //判断 vip
+        if currentModel?.response.vip
+            == true,
+           PurchaseManager.default.isVip == false
+        {
+            TSPurchaseVC.show(target: self) {[weak self]  in
+                guard let self = self else { return }
+            }
+            return true
+        }
+        return false
+    }
 }
 
 
@@ -157,6 +190,7 @@ extension TSSmallIconBrowseVC:UICollectionViewDataSource,UICollectionViewDelegat
         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! TSSmallIconBrowseCell
         if let model = dataModelArray.safeObj(At: indexPath.item){
             cell.netWorkImageView.setAsyncImage(urlString: model.response.resultUrl,placeholder: kPlaceholderImage)
+            cell.vipImageView.isHidden = !model.response.vip
         }
         return cell
     }

+ 14 - 0
AIEmoji/Business/General/TSSmallIconBrowseVC/View/TSSmallIconBrowseCell.swift

@@ -11,6 +11,13 @@ class TSSmallIconBrowseCell : TSBaseCollectionCell{
         let netWorkImageView = UIImageView.createImageView(imageName: "",corner: 24.0)
         return netWorkImageView
     }()
+    
+    
+    lazy var vipImageView: UIImageView = {
+        let vipImageView = UIImageView.createImageView(imageName:"vip_side_icon")
+        vipImageView.contentMode = .scaleToFill
+        return vipImageView
+    }()
 
     override func creatUI() {
         bgContentView.addSubview(netWorkImageView)
@@ -20,5 +27,12 @@ class TSSmallIconBrowseCell : TSBaseCollectionCell{
             make.height.equalTo(200)
         }
         
+        netWorkImageView.addSubview(vipImageView)
+        vipImageView.snp.makeConstraints { make in
+            make.width.height.equalTo(40)
+            make.top.equalTo(-5)
+            make.trailing.equalTo(5)
+        }
+        
     }
 }

+ 3 - 3
AIEmoji/Business/LaunchVC/TSLaunchVC.swift

@@ -34,12 +34,12 @@ class TSLaunchVC: UIViewController {
     }
 
     func addNotifiy() {
-        TSNetworkShard.startListenNetStatus { status, manager in
+        TSNetworkTool.shared.startListenNetStatus { status, manager in
             switch status {
             case .reachable:
-//                PurchaseManager.default.requestProducts()
+                PurchaseManager.default.requestProducts()
 //                AppDelegate.requestAdTrack()
-//                manager?.stopListening()
+                manager?.stopListening()
 //                self.initAdMob()
                 break
             default:

+ 1 - 0
AIEmoji/Business/TSEmojisVC/TSEmojisChildVC/TSEmojisChildVC.swift

@@ -45,6 +45,7 @@ class TSEmojisChildVC: TSBaseVC {
                 for itemModel in sections.items {
                     let model = TSGenmojiModel()
                     model.response.resultUrl = itemModel.dataModel.imageUrl
+                    model.response.vip = itemModel.dataModel.vip
                     dataModelArray.append(model)
                 }
                 

+ 14 - 0
AIEmoji/Business/TSEmojisVC/TSEmojisChildVC/VIew/TSEmojisCoLItemCell.swift

@@ -14,6 +14,12 @@ class TSEmojisCoLItemCell: TSBaseCollectionCell {
         return showImageView
     }()
     
+    lazy var vipImageView: UIImageView = {
+        let vipImageView = UIImageView.createImageView(imageName:"vip_side_icon")
+        vipImageView.contentMode = .scaleToFill
+        return vipImageView
+    }()
+    
     override func creatUI() {
         bgContentView.cornerRadius = 18
         bgContentView.addSubview(showImageView)
@@ -22,12 +28,20 @@ class TSEmojisCoLItemCell: TSBaseCollectionCell {
             make.leading.equalTo(0)
             make.trailing.bottom.equalTo(0)
         }
+        
+        bgContentView.addSubview(vipImageView)
+        vipImageView.snp.makeConstraints { make in
+            make.width.height.equalTo(40)
+            make.top.equalTo(-8)
+            make.trailing.equalTo(8)
+        }
     }
     
     override func renderView(with object: Any?, component: CollectionViewComponent, attributes: [String : Any]?) {
         super.renderView(with: object, component: component, attributes: attributes)
         if let itemModel = object as? TSEmojisColItemModel{
             showImageView.setAsyncImage(urlString: itemModel.dataModel.imageUrl,placeholder: kPlaceholderImage)
+            vipImageView.isHidden = !itemModel.dataModel.vip
         }
     }
     

+ 30 - 0
AIEmoji/Business/TSEmojisVC/TSEmojisVC/TSEmojisVC.swift

@@ -13,6 +13,17 @@ class TSEmojisVC: TSBaseVC {
     var tableHeaderViewHeight: Int = 150
     var headerInSectionHeight: Int = 50
     
+    
+    lazy var vipBtn: UIButton = {
+        let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self]  in
+            guard let self = self else { return }
+            TSPurchaseVC.show(target: self) {
+      
+            }
+        }
+        return vipBtn
+    }()
+    
     lazy var navBarView: TSBaseNavContentBarView = {
         let navBarView = TSBaseNavContentBarView()
         
@@ -36,6 +47,13 @@ class TSEmojisVC: TSBaseVC {
             make.width.height.equalTo(24)
         }
         
+        navBarView.barView.addSubview(vipBtn)
+        vipBtn.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.trailing.equalTo(-62)
+            make.width.height.equalTo(24)
+        }
+
         return navBarView
     }()
     
@@ -129,6 +147,18 @@ class TSEmojisVC: TSBaseVC {
         vc.hidesBottomBarWhenPushed = true
         navigationController?.pushViewController(vc, animated: true)
     }
+    
+    
+    override func dealThings() {
+        vipBtn.isHidden = PurchaseManager.default.isVip
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+    }
+    
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.vipBtn.isHidden = PurchaseManager.default.isVip
+        }
+    }
 }
 
 

+ 2 - 0
AIEmoji/Business/TSGenmojiVC/TSGenmojiGennerateVC/TSGenmojiGennerateVC.swift

@@ -169,5 +169,7 @@ extension TSGenmojiGennerateVC {
             cancelBtn.isEnabled = true
             confirmBtn.isEnabled = true
         }
+        
+        kPurchaseDefault.useOnceForFree()
     }
 }

+ 2 - 0
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/Model/TSGenmojiModel.swift

@@ -48,8 +48,10 @@ class TSGenmojiModel: TSBaseModel {
 
 class TSGenmojiResponseModel : TSBaseModel {
     var resultUrl:String = ""
+    var vip:Bool = false
     override func mapping(map: ObjectMapper.Map) {
         resultUrl           <- map["resultUrl"]
+        vip           <- map["vip"]
     }
 }
 

+ 39 - 1
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/TSGenmojiVC.swift

@@ -9,6 +9,16 @@ class TSGenmojiVC: TSBaseVC {
 
     var viewModel:TSGenmojiViewModel = TSGenmojiViewModel()
     
+    lazy var vipBtn: UIButton = {
+        let vipBtn = UIButton.createButton(image: UIImage(named: "nav_vip")) { [weak self]  in
+            guard let self = self else { return }
+            TSPurchaseVC.show(target: self) {
+      
+            }
+        }
+        return vipBtn
+    }()
+    
     lazy var navBarView: TSBaseNavContentBarView = {
         let navBarView = TSBaseNavContentBarView()
         
@@ -32,6 +42,13 @@ class TSGenmojiVC: TSBaseVC {
             make.width.height.equalTo(24)
         }
         
+        navBarView.barView.addSubview(vipBtn)
+        vipBtn.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.trailing.equalTo(-62)
+            make.width.height.equalTo(24)
+        }
+        
         return navBarView
     }()
     
@@ -99,14 +116,35 @@ class TSGenmojiVC: TSBaseVC {
         collectionComponent.clear()
         collectionComponent.reloadView(with:viewModel.colDataArray)
     }
+    
+    override func dealThings() {
+        vipBtn.isHidden = PurchaseManager.default.isVip
+        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
+    }
+    
+    @objc func vipInfoChanged() {
+        kExecuteOnMainThread {
+            self.vipBtn.isHidden = PurchaseManager.default.isVip
+            self.collectionComponent.reloadData()
+        }
+    }
 }
 
 
 extension TSGenmojiVC {
     func generateImage(text:String) {
+        
+        //判断 vip
+        if kPurchaseDefault.freeNumAvailable() == false{
+            TSPurchaseVC.show(target: self) {[weak self] in
+                guard let self = self else { return }
+                collectionComponent.reloadData()
+            }
+            return
+        }
+        
         let gennerateVC = TSGenmojiGennerateVC(aiText: text) {[weak self] model in
             guard let self = self else { return }
-            
             if viewModel.saveModel(model:model) {
                 collectionComponent.clear()
                 collectionComponent.reloadView(with:viewModel.colDataArray)

+ 13 - 0
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/View/TSGenmojiGennerateCell.swift

@@ -62,6 +62,19 @@ class TSGenmojiGennerateCell : TSBaseCollectionCell{
             make.height.equalTo(48)
         }
     }
+    
+    
+    func getVipText()->String{
+        if kPurchaseDefault.isVip {
+            return "Generate"
+        }
+        return "Generate (\(kPurchaseDefault.freeNum))"
+    }
+    
+    override func renderView(with object: Any?, component: CollectionViewComponent, attributes: [String : Any]?) {
+        super.renderView(with: object, component: component, attributes: attributes)
+        submitBtn.setTitle(getVipText(), for: .normal)
+    }
 }
 
 extension TSGenmojiGennerateCell: UITextViewDelegate{

+ 335 - 0
AIEmoji/Business/TSPurchaseMembershipVC/TSPurchaseVC.swift

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

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

@@ -6,7 +6,7 @@
 //
 
 enum SettingType: String, CaseIterable {
-    case tutorialsBanner = "tutorialsBanner"
+    case howToUse = "How to use?"
     case shareus = "Share us"
     case rateus = "Rate us"
     case agreement = "Terms of Service"

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

@@ -12,6 +12,8 @@ import SwiftUI
 class ListEventPublisher {
     /// 设置
     let settingPublisher = PassthroughSubject<SettingType, Never>()
+    /// 进入订阅详情页
+    let enterPurchasePublisher = PassthroughSubject<Bool, Never>()
 }
 
 class TSSetingVC: TSBaseVC {
@@ -36,12 +38,21 @@ class TSSetingVC: TSBaseVC {
     }
     
     override func dealThings() {
+        
+        publisher.enterPurchasePublisher.receive(on: DispatchQueue.main).sink { [weak self] _ in
+            guard let self = self else { return }
+            viewModel.pushVipPurchase(parent: self)
+        }.store(in: &cancellable)
+        
+
         publisher.settingPublisher.receive(on: DispatchQueue.main).sink{ [weak self] type in
             guard let self = self else {
                 return
             }
             
             switch type {
+            case .howToUse:
+                viewModel.pushTutorials(parent: self)
             case .shareus:
                 viewModel.shareApp(parent: self)
             case .agreement:
@@ -52,9 +63,8 @@ class TSSetingVC: TSBaseVC {
                 break
             case .rateus:
                 viewModel.rateAction()
-            case .tutorialsBanner:
-                viewModel.pushTutorials(parent: self)
             }
+            
         }.store(in: &cancellable)
     }
     

+ 9 - 0
AIEmoji/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift

@@ -10,6 +10,7 @@ import StoreKit
 class TSSetingViewModel: ObservableObject {
     
     @Published var settingTypes: [SettingType] = SettingType.allCases
+    @Published var isViper: Bool = PurchaseManager.default.isVip
     
     // todo.kailen-privacy
     func showPrivacy(parent: UIViewController) {
@@ -81,4 +82,12 @@ class TSSetingViewModel: ObservableObject {
         vc.hidesBottomBarWhenPushed = true
         parent.navigationController?.pushViewController(vc, animated: true)
     }
+    
+    // todo.kailen-logo
+    func pushVipPurchase(parent: UIViewController) {
+        TSPurchaseVC.show(target: parent) {[weak self]  in
+            guard let self = self else { return }
+            isViper = PurchaseManager.default.isVip
+        }
+    }
 }

+ 53 - 0
AIEmoji/Business/TSSetingVC/SetingVC/View/SettingPurchaseTopView.swift

@@ -0,0 +1,53 @@
+//
+//  SettingPurchaseTopView.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/2/4.
+//
+import SwiftUI
+struct SettingPurchaseTopView: View {
+    var eventPublisher: ListEventPublisher
+    @Binding var isViper: Bool
+    var body: some View {
+        ZStack {
+            Image(PurchaseManager.default.isVip ? .settingVip : .settingNoVip).resizable().frame(width: 343*kDesignScale, height: 117*kDesignScale)
+
+            if isViper {
+                VStack {
+                    Spacer()
+        
+                    Text("Due Date:".localized + " \(PurchaseManager.default.expiredDateString)")
+                        .foregroundColor(.white.opacity(0.6))
+                        .font(.font(size: 14,weight: .medium))
+                        .frame(height: 14)
+
+                    Spacer().frame(height: 13)
+                }
+            }else {
+                
+                HStack {
+                    Spacer()
+                    
+                    Text("Upgrade")
+                        .font(.font(size: 14,weight: .medium))
+                        .padding(EdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 12))
+                        .frame(height: 34)
+                        .foregroundColor("#010101".color.color)
+                        .background(UIColor.themeColor.color)
+                        .cornerRadius(14.0)
+                    
+                    .frame(height: 26) // 设置高度
+                    .cornerRadius(14.0) // 圆角
+                    .onTapGesture {
+                        if PurchaseManager.default.isVip {
+                            return
+                        }
+                        eventPublisher.enterPurchasePublisher.send(true)
+                    }
+
+                    Spacer().frame(width: 24)
+                }
+            }
+        }
+    }
+}

+ 9 - 16
AIEmoji/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift

@@ -8,7 +8,6 @@
 import SwiftUI
 import SwiftUIX
 
-
 struct TSSettingListView: View {
     @ObservedObject var viewModel: TSSetingViewModel
     var publisher: ListEventPublisher
@@ -18,23 +17,16 @@ struct TSSettingListView: View {
             VStack(spacing: 0) {
                 Spacer().height(16)
                 
-                if viewModel.settingTypes.contains(.tutorialsBanner) {
-                    Image(.tutorialsBanner).resizable().frame(width: k_ScreenWidth-32,height: 117*kDesignScale)
+                SettingPurchaseTopView(eventPublisher: publisher, isViper: $viewModel.isViper)
+                    .height(117*kDesignScale)
                     .onTapGesture {
-                        publisher.settingPublisher.send(.tutorialsBanner)
+                        if PurchaseManager.default.isVip {
+                            return
+                        }
+                        publisher.enterPurchasePublisher.send(true)
                     }
-                }
-                
-//                CocoaList(viewModel.settingTypes.filter({$0 != .tutorialsBanner}), id: \.self) { type in
-//                    Spacer().height(16)
-//                    SettingListItemView(type: type)
-//                    .onTapGesture {
-//                        publisher.settingPublisher.send(type)
-//                    }
-//                }.listStyle(.grouped)
                 
-                
-                ForEach(viewModel.settingTypes.filter({$0 != .tutorialsBanner}), id:\.self) { type in
+                ForEach(viewModel.settingTypes, id:\.self) { type in
                     Spacer().height(16)
                     SettingListItemView(type: type)
                     .onTapGesture {
@@ -44,7 +36,8 @@ struct TSSettingListView: View {
                 
                 Spacer()
 
-            }.padding(.horizontal)
+            }
+            .padding(.horizontal)
             
         }
     }

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

@@ -52,7 +52,7 @@ extension TSNetworkManager {
             completion(result)
         }
     }
-    
+
 }
 
 

+ 585 - 0
AIEmoji/Common/Purchase/TSPurchaseManager.swift

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

+ 0 - 1
AIEmoji/Common/Tool/TSNetworkTool.swift

@@ -7,7 +7,6 @@
 
 import Alamofire
 import Network
-let TSNetworkShard = TSNetworkTool.shared
 
 class TSNetworkTool {
     static let shared = TSNetworkTool()

+ 6 - 6
AIEmoji/Res/Beauty👸.json

@@ -22,7 +22,7 @@
     {
         "name" : "aHd2YWRwbndk.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/02fc0960-2550-4276-85ed-33680a5ac2b3",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "Photo of Victoria secret model posing with sunglasses emoji AI E (3).webp",
@@ -57,7 +57,7 @@
     {
         "name" : "Photo of Victoria secret model posing with sunglasses emoji AI E (6).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/a6caa9ff-e14f-4787-ab3a-ee0dcd9a47d7",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "Y2EwMnRpNTgw.webp",
@@ -157,7 +157,7 @@
     {
         "name" : "NnFhdzd1eWlt.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/b2639384-4126-4c54-a39c-5897897ee377",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "MzMyOWxuMWZ6.webp",
@@ -267,7 +267,7 @@
     {
         "name" : "Ballerina.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/9d4ac6d7-9856-4f4e-b4d7-69c0bc805ac7",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "ODF1enEyNWNn.webp",
@@ -352,7 +352,7 @@
     {
         "name" : "AI Emoji Generator (13).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/c68ae16d-16db-4f52-b5ed-adb2405a8284",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "aGlnZzFuNjN3.webp",
@@ -427,7 +427,7 @@
     {
         "name" : "AI Emoji Generator (17)-1.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/cae878f1-fa68-4e4a-9f1c-d011fe8edab0",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "MzRzNGE3aGJk.webp",

+ 5 - 5
AIEmoji/Res/Cat🐱.json

@@ -27,7 +27,7 @@
     {
         "name" : "AI Emoji Generator (31).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/eadd3d60-7237-429b-87bf-49f27c591f87",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (18).webp",
@@ -152,7 +152,7 @@
     {
         "name" : "AI Emoji Generator (75).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/57bc0f58-3029-42b0-9438-d8c6774e1f9a",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (74).webp",
@@ -332,7 +332,7 @@
     {
         "name" : "ajMwM2llbWxs.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/74e1ff31-633c-42fe-98c1-65987ed763de",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "MWlxMjRmc3kx.webp",
@@ -527,7 +527,7 @@
     {
         "name" : "AI Emoji Generator (40).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/27bf16f9-0969-4302-9086-6bc909c52657",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (139).webp",
@@ -792,7 +792,7 @@
     {
         "name" : "aDMxNGp2Ynll.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/bc2befd1-fddc-4c6f-a770-45b9b4802fcc",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "NnpjdGVuMTMy.webp",

+ 13 - 13
AIEmoji/Res/Crystal🔮.json

@@ -7,12 +7,12 @@
     {
         "name" : "ZXMzcXVtaWcz.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/97eebfcd-dc3c-473f-a801-37834a0a8f4c",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "OTNoNXl4aXIz.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/a98cf7f8-9500-479a-8acb-ae43757c4893",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "ZWlzNnVnZWV6.webp",
@@ -27,7 +27,7 @@
     {
         "name" : "YWxpcXE1bmRr.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/64d80182-8b45-4ed5-ad25-ae97c3977cda",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (51).webp",
@@ -97,7 +97,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (76).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/4afa93c7-5992-494f-a603-4016dd4585f8",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (72).webp",
@@ -167,7 +167,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (87).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/9afbccb1-2fdf-4a51-ac45-c0a3d5e54248",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (95).webp",
@@ -352,7 +352,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (122).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/d838e67c-1349-4645-9234-38a5631c82e7",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (123).webp",
@@ -572,7 +572,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (13).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/013174a9-82ff-4a7c-8355-6b2e16bf60b4",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (12).webp",
@@ -607,7 +607,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (5).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/0433314e-370a-4223-b420-7372c4808aa4",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (4).webp",
@@ -652,7 +652,7 @@
     {
         "name" : "NXh0aWhtbDZz.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/43838ba9-d870-4f40-8289-cb5774b5fb31",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "NWUyamZteDQ3.webp",
@@ -742,7 +742,7 @@
     {
         "name" : "M3NtMHNkZmxx.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/3f4382c6-c78f-47b0-9459-d89c9debcff5",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "eTVoNzBqejNj.webp",
@@ -842,7 +842,7 @@
     {
         "name" : "dDdsNWFuenN6.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/034edb52-6cf5-4c1e-8dbc-0bee53a7006e",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "cWpocTJkNncx.webp",
@@ -877,7 +877,7 @@
     {
         "name" : "bTJyYWJuNndr.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/22035ee3-cd1b-4e84-aba0-134d9e316457",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "bnYxMjV3MHph.webp",
@@ -937,7 +937,7 @@
     {
         "name" : "aHk5bDRpYjNq.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/37b2184f-4cca-4173-bef2-c6ca9cc9f6c9",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "aHJwdHRmaGJx.webp",

+ 5 - 5
AIEmoji/Res/Dog🐕.json

@@ -12,7 +12,7 @@
     {
         "name" : "AI Emoji Generator (2)-2.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/eae20952-6cd9-453a-bdbe-4fa29677eed5",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "dm1qOHUxMWhq.webp",
@@ -67,7 +67,7 @@
     {
         "name" : "ejl5ZDZzcnNm.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/f8be4cf6-d71e-486b-9bd4-e663d9c7730e",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (7)-2.webp",
@@ -167,7 +167,7 @@
     {
         "name" : "AI Emoji Generator (18).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/fbc4a70c-a0ee-4b4c-960c-f8c4969402ae",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (20).webp",
@@ -367,7 +367,7 @@
     {
         "name" : "cWtud2NvZjhw.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/37c8bed2-e301-4b99-9028-bf38f0dd25b9",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (41).webp",
@@ -457,7 +457,7 @@
     {
         "name" : "AI Emoji Generator (48).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/5c4533ce-fe22-4be7-9cf2-9fa541c6b577",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (52).webp",

+ 7 - 7
AIEmoji/Res/Drink🥤.json

@@ -7,7 +7,7 @@
     {
         "name" : "cjRjZWZjYjRw.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/ee3840aa-b611-454c-ae4c-cd7188cf0485",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator-3.webp",
@@ -22,7 +22,7 @@
     {
         "name" : "bzNieWtxaDY1.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/4bd2a51f-e18b-4a73-a30d-20577e3cefa5",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "OTVqbmt3cGNu.webp",
@@ -107,7 +107,7 @@
     {
         "name" : "AI Emoji Generator (32).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/6edb40dc-5d6e-48df-b655-ad91f9b7d396",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "MWp1eWlsYWps.webp",
@@ -247,7 +247,7 @@
     {
         "name" : "MDBkMGUyOXNn.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/84766b2a-5967-4e85-a5e3-f27750ff0bf2",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (49).webp",
@@ -327,7 +327,7 @@
     {
         "name" : "bDQyYmNzeDM0.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/f7f65e02-47a6-4189-a00c-e30ebb305d70",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "dXhyampnb3Jz.webp",
@@ -437,7 +437,7 @@
     {
         "name" : "AI Emoji Generator (18).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/3885422c-b437-45e9-b428-b939599a1767",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (17).webp",
@@ -532,7 +532,7 @@
     {
         "name" : "AI Emoji Generator (3)-2.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/c77c57c2-3c95-44fb-b9ba-2c1b2d9f2f89",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (3)-1.webp",

+ 5 - 5
AIEmoji/Res/Fish🐠.json

@@ -7,7 +7,7 @@
     {
         "name" : "Iridescent beta fish emoji.png",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/edecc584-0567-4406-aa6e-a3f2232b0bec",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "Sunset gradient beta fish rainbow emoji.png",
@@ -187,7 +187,7 @@
     {
         "name" : "AI Emoji Generator (18).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/535b68b7-7cc7-4a0c-804f-6f61552b5370",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (19).webp",
@@ -287,7 +287,7 @@
     {
         "name" : "aHByeWxkMmJ1.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/554a5bf9-94ec-4fd6-9c1e-d7c4aad2e2a1",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (32).webp",
@@ -397,7 +397,7 @@
     {
         "name" : "dDE1ZHgwcWRt.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/78891380-0ed3-4253-8fe0-9472a030b872",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "ZnEzZDJ4YWxp.webp",
@@ -522,7 +522,7 @@
     {
         "name" : "cWRldGZpYjJq.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/454b951e-c949-43da-8823-e5ac9d962570",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "bDFpNnd3dGJj.webp",

+ 7 - 7
AIEmoji/Res/Flower💐.json

@@ -32,7 +32,7 @@
     {
         "name" : "ZDFpeHBneWNv.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/f942bd6d-c61a-46cf-8f75-d2cb8b8bed6a",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "YnRnbm9qZGJy.webp",
@@ -187,7 +187,7 @@
     {
         "name" : "Y3NsaWRhaDZi.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/ac9fd327-d21b-4886-85f7-a48ef43b41c3",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "aTJvMTFnOHpr.webp",
@@ -287,7 +287,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (35).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/6466cb39-5969-4b30-9dd1-ab86ee9b1158",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (23).webp",
@@ -382,7 +382,7 @@
     {
         "name" : "opal crystal shaped strawberry emoji AI Emoji Generator (36).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/cbfc486d-be75-40b9-853d-5e9cc0ef9aff",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "eTFma2VsZGVk.webp",
@@ -477,7 +477,7 @@
     {
         "name" : "cXBjeWM0ajhy.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/1bd39183-dc00-471e-82da-cec171c5754b",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "cTIwZnFhczBu.webp",
@@ -577,7 +577,7 @@
     {
         "name" : "AI Emoji Generator (7)-4.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/37a26b7e-a5a1-49ad-9379-ab359034069e",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (7)-3.webp",
@@ -687,7 +687,7 @@
     {
         "name" : "AI Emoji Generator (2)-2.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/42754bfb-4bf1-47cd-88d5-caadd5803b0a",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (2)-1.webp",

+ 9 - 9
AIEmoji/Res/Food🍔.json

@@ -2,7 +2,7 @@
     {
         "name" : "Chinese food emoji.png",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/d87d9006-475f-47e0-a5c3-f90ebfaaeadd",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "chinese food called baozi emoji.png",
@@ -137,7 +137,7 @@
     {
         "name" : "YWZoYzRremY5.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/12021024-a6de-48c2-a83d-10651237f26a",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "MHNmN3VmazNl.webp",
@@ -287,7 +287,7 @@
     {
         "name" : "cWkxdzMxM2Rk.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/97f007a0-5f7b-489b-8bc9-03abee23ce3b",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "bHY1NHQxbTVz.webp",
@@ -412,7 +412,7 @@
     {
         "name" : "N3hoNnVvdTRn.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/44d69cb6-aaeb-45ed-b9c7-4ccfeecfae89",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "eWV6bGdzYjls.webp",
@@ -577,7 +577,7 @@
     {
         "name" : "AI Emoji Generator (34)-1.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/6b3fcc23-c102-43a6-b7e7-96773321ec6b",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (33)-2.webp",
@@ -677,7 +677,7 @@
     {
         "name" : "AI Emoji Generator (24)-1.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/92119426-e785-4bb8-8db6-adf80cbc2d51",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (23)-2.webp",
@@ -812,7 +812,7 @@
     {
         "name" : "AI Emoji Generator (10)-2.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/9a12d25a-376c-4d9f-890b-a13c51edc4ef",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (10)-1.webp",
@@ -872,7 +872,7 @@
     {
         "name" : "AI Emoji Generator (6)-2.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/99d5cfde-491e-4f26-894a-5b5f82406fe1",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (6)-1.webp",
@@ -927,7 +927,7 @@
     {
         "name" : "AI Emoji Generator (2)-3.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/13228582-f275-4859-ad53-c220d14be4af",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (2)-2.webp",

+ 8 - 8
AIEmoji/Res/Fruit🍊.json

@@ -27,7 +27,7 @@
     {
         "name" : "AI Emoji Generator-28.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/2cdbde19-6c56-4a31-a268-16d6f560c3ad",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator-27.webp",
@@ -92,7 +92,7 @@
     {
         "name" : "AI Emoji Generator-19.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/647ccfeb-574f-493c-a206-fb57145814ea",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "YjJjNmdqa290.webp",
@@ -107,7 +107,7 @@
     {
         "name" : "AI Emoji Generator-18.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/1de585d7-277e-4046-ae26-55926ed285b6",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator-17.webp",
@@ -197,7 +197,7 @@
     {
         "name" : "AI Emoji Generator-13.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/6ab8e019-ec40-4862-9f82-acc2eec5e3fb",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator-09.webp",
@@ -257,7 +257,7 @@
     {
         "name" : "orange kiwano fruit emoji AI Emoji Generator-2.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/763af2f4-611d-4239-b4f0-3e675943b345",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "orange kiwano fruit emoji AI Emoji Generator-1.webp",
@@ -357,7 +357,7 @@
     {
         "name" : "Fruit in a basket emoji.png",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/46a8776f-249c-454d-ad60-f6efc9b6c9d9",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "sliced pink dragon fruit emoji.png",
@@ -427,7 +427,7 @@
     {
         "name" : "lychee emoji.png",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/077a7be9-ebf7-4ff9-99b3-920ef290f35f",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "fruit emoji-1.png",
@@ -492,7 +492,7 @@
     {
         "name" : "AI Emoji Generator-01.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/93c40967-ee02-4880-b57d-45529a251c8e",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator-04.webp",

+ 5 - 5
AIEmoji/Res/Pink🩷.json

@@ -12,7 +12,7 @@
     {
         "name" : "Cute pink food emoji.png",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/99c5f176-e25f-4965-be3a-ddde21d7f4ab",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "pink foods emoji.png",
@@ -217,7 +217,7 @@
     {
         "name" : "cW1iYzgwcWts.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/e5d4d871-e65b-4233-9b3b-33c39bb9a287",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (2)-2.webp",
@@ -317,7 +317,7 @@
     {
         "name" : "AI Emoji Generator (11).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/81f0311b-7dca-4970-a041-653ee1eafd8c",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (14).webp",
@@ -417,7 +417,7 @@
     {
         "name" : "NHJyaXE0Ymww.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/10e800b3-d0d9-46ee-a05e-2cc4074dc1fa",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (28).webp",
@@ -487,7 +487,7 @@
     {
         "name" : "MmVrdWowcGV1.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/2894a453-8c58-453b-a30f-dec452fc2eab",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator-1.webp",

+ 11 - 11
AIEmoji/Res/Universe🌍.json

@@ -12,7 +12,7 @@
     {
         "name" : "am4xbzdsbHAx.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/2f246a36-886c-4af8-9802-f2d153ae8ff4",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator.webp",
@@ -47,7 +47,7 @@
     {
         "name" : "AI Emoji Generator (6).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/39f81897-bde9-4b19-8208-27bee959b562",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (7).webp",
@@ -82,7 +82,7 @@
     {
         "name" : "AI Emoji Generator (12).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/155a8300-1830-4210-96f1-cdfc162cbf02",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "djFnOXNqYmhx.webp",
@@ -142,7 +142,7 @@
     {
         "name" : "NnN0NGkwcGJp.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/61419255-4e88-469a-b22f-3bcbb2144f1f",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "ZWpqZTgwNHRv.webp",
@@ -197,7 +197,7 @@
     {
         "name" : "cWgwYng1MjAx.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/16d505e9-a660-464c-bb84-92b21a76546b",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "AI Emoji Generator (22).webp",
@@ -257,7 +257,7 @@
     {
         "name" : "AI Emoji Generator (27).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/4263a6d0-da9b-41a1-a34f-dfb7594ae011",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "OXVqYXVwaHVt.webp",
@@ -322,7 +322,7 @@
     {
         "name" : "MTRlNGhwcnRu.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/5054e351-ef0c-48a3-bfc2-004da7ab6f15",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "bDBqZXUzdDNr.webp",
@@ -382,12 +382,12 @@
     {
         "name" : "AI Emoji Generator (45).webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/2d11fcb9-a34d-41ee-96bd-fe6f196c9db0",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "OXI3azkxNmpu.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/a456b5a2-8b55-450e-b70b-784649d3be1b",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "N2Voamdscmgx.webp",
@@ -427,7 +427,7 @@
     {
         "name" : "dWRxdjF4emkx.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/f98f0536-eb19-4f88-91e3-7ab22e61e727",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "aXgydTdrcjVz.webp",
@@ -477,7 +477,7 @@
     {
         "name" : "bjRleW1lbWhl.webp",
         "imageUrl" : "http://d3a93z8fj970a4.cloudfront.net/3faa8430-4d48-4c40-a742-0d3022bc8129",
-        "vip" : false
+        "vip" : true
     },
     {
         "name" : "cHgxemxtcmJw.webp",