Kaynağa Gözat

功能开发完毕

100Years 1 ay önce
ebeveyn
işleme
a351c65d4f
39 değiştirilmiş dosya ile 1870 ekleme ve 351 silme
  1. 52 2
      AIRingtone.xcodeproj/project.pbxproj
  2. 75 9
      AIRingtone/AppDelegate.swift
  3. 21 0
      AIRingtone/Assets.xcassets/Launch/bootPage_0.imageset/Contents.json
  4. BIN
      AIRingtone/Assets.xcassets/Launch/bootPage_0.imageset/bootPage_0.png
  5. 21 0
      AIRingtone/Assets.xcassets/Launch/bootPage_1.imageset/Contents.json
  6. BIN
      AIRingtone/Assets.xcassets/Launch/bootPage_1.imageset/bootPage_1.png
  7. 21 0
      AIRingtone/Assets.xcassets/Launch/bootPage_2.imageset/Contents.json
  8. BIN
      AIRingtone/Assets.xcassets/Launch/bootPage_2.imageset/bootPage_2.png
  9. 22 0
      AIRingtone/Assets.xcassets/VIP/ring_vip.imageset/Contents.json
  10. BIN
      AIRingtone/Assets.xcassets/VIP/ring_vip.imageset/ring_vip@2x.png
  11. BIN
      AIRingtone/Assets.xcassets/VIP/ring_vip.imageset/ring_vip@3x.png
  12. 73 29
      AIRingtone/Business/Data/TSUserDefaultData.swift
  13. 21 21
      AIRingtone/Business/LaunchVC/TSBootPageVC.swift
  14. 0 1
      AIRingtone/Business/TSAIPhotoVC/TSAIPhotoChildVC/TSAIPhotoChildVC.swift
  15. 2 3
      AIRingtone/Business/TSAIPhotoVC/TSAIPhotoVC.swift
  16. 36 36
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGenneralPicVM.swift
  17. 4 0
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/Model/TSRingModel.swift
  18. 31 20
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift
  19. 62 63
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift
  20. 91 0
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistorySectionHeaderView.swift
  21. 1 8
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift
  22. 36 36
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVM.swift
  23. 187 0
      AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVC.swift
  24. 46 0
      AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVM.swift
  25. 2 2
      AIRingtone/Business/TSCollectionViewVM/TSCollectionViewVM+Config.swift
  26. 108 42
      AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/TSDiscoverListVC.swift
  27. 127 29
      AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/VM/TSDiscoverListVM.swift
  28. 39 24
      AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/View/TSDiscoverListCell.swift
  29. 12 0
      AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/View/TSDiscoverListCellVM.swift
  30. 24 0
      AIRingtone/Business/TSDiscoverVC/TSDiscoverVC/TSDiscoverVC.swift
  31. 156 6
      AIRingtone/Business/TSDiscoverVC/TSRingDownVC/TSRingDownVC.swift
  32. 50 0
      AIRingtone/Business/TSDiscoverVC/TSRingDownVC/VM/TSRingDownVM.swift
  33. 4 10
      AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetVC.swift
  34. 19 3
      AIRingtone/Common/Purchase/TSPurchaseManager/TSPurchaseTool.swift
  35. 163 0
      AIRingtone/Common/Tool/TSAudioAVPlayer.swift
  36. 63 0
      AIRingtone/Common/Tool/TSAudioPlayerFileTool.swift
  37. 83 7
      AIRingtone/Common/Tool/TSBandRingTool/TSBandRingTool.swift
  38. 4 0
      AIRingtone/Common/Tool/TSBusinessAudioPlayer.swift
  39. 214 0
      AIRingtone/Common/Tool/TSDownloadManager.swift

+ 52 - 2
AIRingtone.xcodeproj/project.pbxproj

@@ -112,6 +112,15 @@
 		A899D3962D89258B00AB9C1C /* TSDiscoverListVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3952D89258600AB9C1C /* TSDiscoverListVM.swift */; };
 		A899D3992D8947D800AB9C1C /* TSDiscoverListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3982D8947D200AB9C1C /* TSDiscoverListCell.swift */; };
 		A899D39C2D894F9800AB9C1C /* TSRingDownVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */; };
+		A899D3A32D896A7B00AB9C1C /* TSRingDownNullView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D39F2D895F3C00AB9C1C /* TSRingDownNullView.swift */; };
+		A899D3A52D89786200AB9C1C /* TSAudioAVPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */; };
+		A899D3CB2D89A53D00AB9C1C /* TSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3CA2D89A53C00AB9C1C /* TSDownloadManager.swift */; };
+		A899D3CF2D89AD4C00AB9C1C /* TSAudioPlayerFileTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */; };
+		A899D3D12D89B14B00AB9C1C /* TSDiscoverListCellVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */; };
+		A899D3D42D8A6B6600AB9C1C /* TSGenerateHistoryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */; };
+		A899D3D62D8A6CC600AB9C1C /* TSGenerateHistoryVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */; };
+		A899D3D82D8A76CE00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */; };
+		A899D3DB2D8A97F100AB9C1C /* TSRingDownVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */; };
 		A8C6436C2D79A8C8001068D0 /* TSAIRintoneHistoryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */; };
 		A8CC55822D797720002E0CAA /* TSGeneralPicBrowseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */; };
 		A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */; };
@@ -236,6 +245,14 @@
 		A899D3982D8947D200AB9C1C /* TSDiscoverListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDiscoverListCell.swift; sourceTree = "<group>"; };
 		A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownVC.swift; sourceTree = "<group>"; };
 		A899D39F2D895F3C00AB9C1C /* TSRingDownNullView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownNullView.swift; sourceTree = "<group>"; };
+		A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAudioAVPlayer.swift; sourceTree = "<group>"; };
+		A899D3CA2D89A53C00AB9C1C /* TSDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDownloadManager.swift; sourceTree = "<group>"; };
+		A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAudioPlayerFileTool.swift; sourceTree = "<group>"; };
+		A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDiscoverListCellVM.swift; sourceTree = "<group>"; };
+		A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateHistoryVC.swift; sourceTree = "<group>"; };
+		A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGenerateHistoryVM.swift; sourceTree = "<group>"; };
+		A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistorySectionHeaderView.swift; sourceTree = "<group>"; };
+		A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSRingDownVM.swift; sourceTree = "<group>"; };
 		A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIRintoneHistoryCell.swift; sourceTree = "<group>"; };
 		A8CC55812D79771F002E0CAA /* TSGeneralPicBrowseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralPicBrowseVC.swift; sourceTree = "<group>"; };
 		A8CC55852D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTextGeneralRintoneVC.swift; sourceTree = "<group>"; };
@@ -370,6 +387,9 @@
 		A80EDEBA2D718CEA003CD332 /* Tool */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3CA2D89A53C00AB9C1C /* TSDownloadManager.swift */,
+				A899D3A42D89785E00AB9C1C /* TSAudioAVPlayer.swift */,
+				A899D3CE2D89AD4800AB9C1C /* TSAudioPlayerFileTool.swift */,
 				A8272EBA2D7AFD0D00F1C814 /* TSBusinessAudioPlayer.swift */,
 				A868A8F02D77081B00F6D884 /* TSContactsTool.swift */,
 				A868A8DC2D76F90E00F6D884 /* TSBandRingTool */,
@@ -491,6 +511,7 @@
 		A80EDF0E2D718E58003CD332 /* TSAIRintoneVC */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3D22D8A6B5A00AB9C1C /* TSGenerateHistoryVC */,
 				A8272E992D7A8F0500F1C814 /* TSGeneralRintoneVC */,
 				A8CC55842D798E26002E0CAA /* TSAIRintoneVC */,
 				A8CC55832D798E05002E0CAA /* TSTextGeneralRintoneVC */,
@@ -888,6 +909,7 @@
 		A899D3972D8947C700AB9C1C /* View */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3D02D89B14400AB9C1C /* TSDiscoverListCellVM.swift */,
 				A899D3982D8947D200AB9C1C /* TSDiscoverListCell.swift */,
 			);
 			path = View;
@@ -896,6 +918,7 @@
 		A899D39A2D894F8600AB9C1C /* TSRingDownVC */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3D92D8A97E300AB9C1C /* VM */,
 				A899D39D2D895F3000AB9C1C /* View */,
 				A899D39B2D894F9600AB9C1C /* TSRingDownVC.swift */,
 			);
@@ -910,9 +933,27 @@
 			path = View;
 			sourceTree = "<group>";
 		};
+		A899D3D22D8A6B5A00AB9C1C /* TSGenerateHistoryVC */ = {
+			isa = PBXGroup;
+			children = (
+				A899D3D52D8A6CC200AB9C1C /* TSGenerateHistoryVM.swift */,
+				A899D3D32D8A6B6500AB9C1C /* TSGenerateHistoryVC.swift */,
+			);
+			path = TSGenerateHistoryVC;
+			sourceTree = "<group>";
+		};
+		A899D3D92D8A97E300AB9C1C /* VM */ = {
+			isa = PBXGroup;
+			children = (
+				A899D3DA2D8A97EA00AB9C1C /* TSRingDownVM.swift */,
+			);
+			path = VM;
+			sourceTree = "<group>";
+		};
 		A8C6436A2D79A8BD001068D0 /* View */ = {
 			isa = PBXGroup;
 			children = (
+				A899D3D72D8A76CB00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift */,
 				A8C6436B2D79A8C7001068D0 /* TSAIRintoneHistoryCell.swift */,
 			);
 			path = View;
@@ -1122,10 +1163,12 @@
 				A899D38D2D891C5600AB9C1C /* TSDiscoverCell.swift in Sources */,
 				A868A9032D77E5A500F6D884 /* TSPTPStyleModel.swift in Sources */,
 				A80EDEC62D718CEA003CD332 /* TSRandomTextPicker.swift in Sources */,
+				A899D3D12D89B14B00AB9C1C /* TSDiscoverListCellVM.swift in Sources */,
 				A80EDEC82D718CEA003CD332 /* TSNetworkManager+Loading.swift in Sources */,
 				A899D34D2D82C61C00AB9C1C /* TSPurchaseVC.swift in Sources */,
 				A868A9182D78555200F6D884 /* TSGenneralPicVM.swift in Sources */,
 				A80EDECB2D718CEA003CD332 /* TSNetWork+Business.swift in Sources */,
+				A899D3D82D8A76CE00AB9C1C /* TSAIRintoneHistorySectionHeaderView.swift in Sources */,
 				A8CC558B2D79905A002E0CAA /* TSCreatBtnView.swift in Sources */,
 				A80EDF0B2D718DF7003CD332 /* TSBootPageVC.swift in Sources */,
 				A80EDF0C2D718DF7003CD332 /* TSLaunchVC.swift in Sources */,
@@ -1136,6 +1179,7 @@
 				A868A8AF2D758BBC00F6D884 /* TSTBDesktopPreviewView.swift in Sources */,
 				A80EDEF52D718DEA003CD332 /* TSTabBarController.swift in Sources */,
 				A899D3872D89024400AB9C1C /* TSRingModel.swift in Sources */,
+				A899D3A32D896A7B00AB9C1C /* TSRingDownNullView.swift in Sources */,
 				A868A8B52D7598C000F6D884 /* TSTSIslandView.swift in Sources */,
 				A899D39C2D894F9800AB9C1C /* TSRingDownVC.swift in Sources */,
 				A8272E9F2D7A8F6500F1C814 /* TSGeneralRintoneVM.swift in Sources */,
@@ -1143,6 +1187,7 @@
 				A899D3962D89258B00AB9C1C /* TSDiscoverListVM.swift in Sources */,
 				A868A8D52D76E41800F6D884 /* TSSetContactAvatar.swift in Sources */,
 				A868A8F52D77179D00F6D884 /* TSAIPhotoChildVC.swift in Sources */,
+				A899D3DB2D8A97F100AB9C1C /* TSRingDownVM.swift in Sources */,
 				A899D38A2D891ADE00AB9C1C /* TSDiscoverVM.swift in Sources */,
 				A868A8E82D76FA4200F6D884 /* ExtAudioConverter.m in Sources */,
 				A80EDF132D718EF7003CD332 /* TSAIPhotoVC.swift in Sources */,
@@ -1150,6 +1195,7 @@
 				A868A8DE2D76F91500F6D884 /* AudioTool.swift in Sources */,
 				A80EDF242D71C0AA003CD332 /* TSColVVMSectionHeaderView.swift in Sources */,
 				A868A90C2D78311A00F6D884 /* TSTextGeneralPicVM.swift in Sources */,
+				A899D3CF2D89AD4C00AB9C1C /* TSAudioPlayerFileTool.swift in Sources */,
 				A80EDF1D2D71BE15003CD332 /* TSCollectionViewVM+Config.swift in Sources */,
 				A868A8E12D76F9BB00F6D884 /* AudioConverter.m in Sources */,
 				A868A8BA2D75C22300F6D884 /* TSThemeBrowseVM.swift in Sources */,
@@ -1167,9 +1213,11 @@
 				A868A8A42D7560B900F6D884 /* TSCommonloadingView.swift in Sources */,
 				A868A89A2D75505E00F6D884 /* TSThemeBannerCell.swift in Sources */,
 				A80EDF182D7193EE003CD332 /* TSTutorialsVC.swift in Sources */,
+				A899D3A52D89786200AB9C1C /* TSAudioAVPlayer.swift in Sources */,
 				A80EDEEA2D718CEA003CD332 /* PaddedLabel.swift in Sources */,
 				A8CC55862D798E2D002E0CAA /* TSTextGeneralRintoneVC.swift in Sources */,
 				A80EDEEB2D718CEA003CD332 /* StreamPostRequest.swift in Sources */,
+				A899D3D62D8A6CC600AB9C1C /* TSGenerateHistoryVM.swift in Sources */,
 				A80EDF2A2D71C215003CD332 /* TSThemeVM.swift in Sources */,
 				A868A8AA2D7588A500F6D884 /* TSThemeBrowseVC.swift in Sources */,
 				A868A8B12D758CA400F6D884 /* TSTBBtnView.swift in Sources */,
@@ -1184,6 +1232,7 @@
 				A868A8A72D757DD600F6D884 /* UICollectionView+Refresh.swift in Sources */,
 				A868A8F82D77E2BC00F6D884 /* TSTextGeneralPicVC.swift in Sources */,
 				A80EDF022D718DF1003CD332 /* TSSettingListView.swift in Sources */,
+				A899D3CB2D89A53D00AB9C1C /* TSDownloadManager.swift in Sources */,
 				A8CC55902D799F9F002E0CAA /* TSAIRintoneVM.swift in Sources */,
 				A80EDF032D718DF1003CD332 /* ShareActivityItemProvider.swift in Sources */,
 				A80EDF042D718DF1003CD332 /* TSSetingModel.swift in Sources */,
@@ -1192,6 +1241,7 @@
 				A80EDF052D718DF1003CD332 /* TSSetingVC.swift in Sources */,
 				A868A8FA2D77E35E00F6D884 /* TSPromptTextView.swift in Sources */,
 				A80EDF062D718DF1003CD332 /* SettingPurchaseTopView.swift in Sources */,
+				A899D3D42D8A6B6600AB9C1C /* TSGenerateHistoryVC.swift in Sources */,
 				A8272E9D2D7A8F4600F1C814 /* TSGeneralRintoneVC+Event.swift in Sources */,
 				A80EDF072D718DF1003CD332 /* TSSetingViewModel.swift in Sources */,
 				A868A8C72D76A44500F6D884 /* TSThemeSetVC.swift in Sources */,
@@ -1240,7 +1290,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/AIRingtone/Common/Tool/TSBandRingTool/libmp3",
 				);
-				MARKETING_VERSION = 1.3;
+				MARKETING_VERSION = 1.4;
 				PRODUCT_BUNDLE_IDENTIFIER = ai.ringtones.com;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -1282,7 +1332,7 @@
 					"$(inherited)",
 					"$(PROJECT_DIR)/AIRingtone/Common/Tool/TSBandRingTool/libmp3",
 				);
-				MARKETING_VERSION = 1.3;
+				MARKETING_VERSION = 1.4;
 				PRODUCT_BUNDLE_IDENTIFIER = ai.ringtones.com;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

+ 75 - 9
AIRingtone/AppDelegate.swift

@@ -38,21 +38,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 
     func JudgmentSkipPage() {
         //去掉引导页
-//        if UserDefaults.standard.string(forKey: "isFirstInstallApp") == nil {
-//            window?.rootViewController = TSBootPageVC { [weak self]  in
-//                guard let self = self else { return }
-//                UserDefaults.standard.set("1", forKey: "isFirstInstallApp")
-//                UserDefaults.standard.synchronize()
-//                goToTab()
-//            }
-//        }else{
+        if UserDefaults.standard.string(forKey: "isFirstInstallApp") == nil {
+            window?.rootViewController = TSBootPageVC { [weak self]  in
+                guard let self = self else { return }
+                UserDefaults.standard.set("1", forKey: "isFirstInstallApp")
+                UserDefaults.standard.synchronize()
+                goToTab()
+                showEveryDayPopPurchase()
+            }
+        }else{
             goToTab()
-//        }
+            showEveryDayPopPurchase()
+        }
     }
     
 
     func initPlatform() {
         TSColorConfigShared.naviMianTextColor = .white
+        purchaseDidChanged()
     }
 
 }
@@ -81,3 +84,66 @@ extension AppDelegate {
     }
     
 }
+
+extension AppDelegate {
+    
+    
+    func purchaseDidChanged(){
+        NotificationCenter.default.addObserver(forName: .kPurchasePrepared, object: nil, queue: OperationQueue.main) {notification in
+            
+//            //用户第一次安装 App 时候,要弹窗会员
+//            if UserDefaults.standard.string(forKey: "isFirstInstallAppPurchaseShow") == nil {
+//                if kPurchaseToolShared.isVip == false{
+//                    self.showTSPurchaseVC()
+//                    UserDefaults.standard.set("1", forKey: "isFirstInstallAppPurchaseShow")
+//                    UserDefaults.standard.synchronize()
+//                }
+//            }
+            
+            
+            
+        }
+    }
+    
+    @objc func vipInfoChanged()  {
+        if UserDefaults.standard.string(forKey: "isFirstInstallApp") == nil {
+            if let vc = self.window?.rootViewController {
+                TSPurchaseVC.show(target: vc, closePageBlock: nil)
+                UserDefaults.standard.set("1", forKey: "isFirstInstallApp")
+                UserDefaults.standard.synchronize()
+            }
+        }else{
+            self.showEveryDayPopPurchase()
+        }
+    }
+    func showEveryDayPopPurchase() {
+            // 1. 获取当前日期
+            let currentDate = Date()
+            let dateFormatter = DateFormatter()
+            dateFormatter.dateFormat = "yyyy-MM-dd"
+            let currentDateString = dateFormatter.string(from: currentDate)
+            
+            // 2. 获取上次弹窗的日期
+            let userDefaults = UserDefaults.standard
+            let lastGreetingDateString = userDefaults.string(forKey: "kEveryDayPopPurchase")
+            
+            // 3. 检查是否需要显示弹窗 "2025-03-19"
+            if lastGreetingDateString != currentDateString {
+                
+                // 4. 弹窗付费引导
+                showTSPurchaseVC()
+  
+                // 5. 更新上次弹窗的日期
+                userDefaults.set(currentDateString, forKey: "kEveryDayPopPurchase")
+            }
+    }
+    
+    func showTSPurchaseVC(){
+        if kPurchaseDefault.products.count > 0 {
+            if let vc = window?.rootViewController {
+                TSPurchaseVC.show(target: vc, closePageBlock: nil)
+            }
+        }
+        
+    }
+}

+ 21 - 0
AIRingtone/Assets.xcassets/Launch/bootPage_0.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/Launch/bootPage_0.imageset/bootPage_0.png


+ 21 - 0
AIRingtone/Assets.xcassets/Launch/bootPage_1.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/Launch/bootPage_1.imageset/bootPage_1.png


+ 21 - 0
AIRingtone/Assets.xcassets/Launch/bootPage_2.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/Launch/bootPage_2.imageset/bootPage_2.png


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

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

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


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


+ 73 - 29
AIRingtone/Business/Data/TSUserDefaultData.swift

@@ -26,18 +26,18 @@ class TSPosterHistory{
     }()
     
     static func saveModel(model:TSActionInfoModel){
-        listModelArray.insert(model, at: 0)
-        saveHistoryString()
+        self.listModelArray.insert(model, at: 0)
+        self.saveHistoryString()
     }
     
     static func removeModel(model:TSActionInfoModel){
-        listModelArray.removeAll { $0 === model }
-        saveHistoryString()
+        self.listModelArray.removeAll { $0 === model }
+        self.saveHistoryString()
     }
     
     static func removeIndex(index:Int){
-        listModelArray.remove(at: index)
-        saveHistoryString()
+        self.listModelArray.remove(at: index)
+        self.saveHistoryString()
     }
     
     static func saveHistoryString(){
@@ -48,12 +48,12 @@ class TSPosterHistory{
     
     private static func insertExampleData(){
         let array = [
-            createExampleModel(imageName: "poster_example_0"),
-            createExampleModel(imageName: "poster_example_1"),
-            createExampleModel(imageName: "poster_example_2")
+            self.createExampleModel(imageName: "poster_example_0"),
+            self.createExampleModel(imageName: "poster_example_1"),
+            self.createExampleModel(imageName: "poster_example_2")
         ]
         if let jsonString = array.toJSONString() {
-            historyString = jsonString
+            self.historyString = jsonString
         }
     }
     
@@ -86,34 +86,34 @@ class TSPhotoHistory{
     }()
     
     static func saveModel(model:TSActionInfoModel){
-        listModelArray.insert(model, at: 0)
-        saveHistoryString()
+        self.listModelArray.insert(model, at: 0)
+        self.saveHistoryString()
     }
     
     static func removeModel(model:TSActionInfoModel){
-        listModelArray.removeAll { $0 === model }
-        saveHistoryString()
+        self.listModelArray.removeAll { $0 === model }
+        self.saveHistoryString()
     }
     
     static func removeIndex(index:Int){
-        listModelArray.remove(at: index)
-        saveHistoryString()
+        self.listModelArray.remove(at: index)
+        self.saveHistoryString()
     }
     
     static func saveHistoryString(){
         if let jsonString = listModelArray.toJSONString() {
-            historyString = jsonString
+            self.historyString = jsonString
         }
     }
     
     private static func insertExampleData(){
         let array = [
-            createExampleModel(imageName: "photo_example_0"),
-            createExampleModel(imageName: "photo_example_1"),
-            createExampleModel(imageName: "photo_example_2")
+            self.createExampleModel(imageName: "photo_example_0"),
+            self.createExampleModel(imageName: "photo_example_1"),
+            self.createExampleModel(imageName: "photo_example_2")
         ]
         if let jsonString = array.toJSONString() {
-            historyString = jsonString
+            self.historyString = jsonString
         }
     }
     
@@ -148,30 +148,30 @@ class TSAIRintoneHistory{
     }()
     
     static func saveModel(model:TSActionInfoModel){
-        listModelArray.insert(model, at: 0)
-        saveHistoryString()
+        self.listModelArray.insert(model, at: 0)
+        self.saveHistoryString()
     }
     
     static func removeModel(model:TSActionInfoModel){
-        listModelArray.removeAll { $0 === model }
-        saveHistoryString()
+        self.listModelArray.removeAll { $0 === model }
+        self.saveHistoryString()
     }
     
     static func removeIndex(index:Int){
-        listModelArray.remove(at: index)
-        saveHistoryString()
+        self.listModelArray.remove(at: index)
+        self.saveHistoryString()
     }
     
     static func saveHistoryString(){
         if let jsonString = listModelArray.toJSONString() {
-            historyString = jsonString
+            self.historyString = jsonString
         }
     }
     
 
     private static func insertExampleData(){
         let array = [
-            createExampleModel(),
+            self.createExampleModel(),
         ]
         if let jsonString = array.toJSONString() {
             historyString = jsonString
@@ -190,3 +190,47 @@ class TSAIRintoneHistory{
         return model
     }
 }
+
+
+//AI铃声历史记录
+class TSRecommendRintoneHistory{
+    @UserDefault(key: "kRecommendRintoneHistoryListString", defaultValue: "")
+    static private var historyString: String
+    static var listModelArray: [TSRingModel] = {
+
+        if let listModelArray = Mapper<TSRingModel>().mapArray(JSONString: historyString){
+            return listModelArray
+        }
+        return []
+    }()
+    
+    static func saveModel(model:TSRingModel){
+        //若存在,则不保存
+        if let _ = self.listModelArray.first(where: { $0.audioUrl == model.audioUrl }) {
+            return
+        }
+//        TSToastShared.showToast(text: "保存成功")
+        self.listModelArray.insert(model, at: 0)
+        self.saveHistoryString()
+        isHaveNew = true
+    }
+    
+    static func removeModel(model:TSRingModel){
+        listModelArray.removeAll { $0 === model }
+        self.saveHistoryString()
+    }
+    
+    static func removeIndex(index:Int){
+        listModelArray.remove(at: index)
+        self.saveHistoryString()
+    }
+    
+    static func saveHistoryString(){
+        if let jsonString = listModelArray.toJSONString() {
+            historyString = jsonString
+        }
+    }
+    
+    
+    static var isHaveNew:Bool = false
+}

+ 21 - 21
AIRingtone/Business/LaunchVC/TSBootPageVC.swift

@@ -19,37 +19,37 @@ class TSBootPageVC: TSBaseVC {
     }
     
     
-    lazy var continueBtn: UIButton = {
-        let continueBtn = kCreateNormalSubmitBtn(title: "", frame: CGRectMake(0, 0, 343, 50)) {
-            
-        }
-        
-        let imageView = UIImageView.createImageView(imageName: "launch_rightArrow")
-        continueBtn.addSubview(imageView)
-        imageView.snp.makeConstraints { make in
-            make.width.height.equalTo(24)
-            make.centerY.equalToSuperview()
-            make.trailing.equalTo(-24)
-        }
-        
-        return continueBtn
-    }()
+//    lazy var continueBtn: UIButton = {
+//        let continueBtn = kCreateNormalSubmitBtn(title: "", frame: CGRectMake(0, 0, 343, 50)) {
+//            
+//        }
+//        
+//        let imageView = UIImageView.createImageView(imageName: "launch_rightArrow")
+//        continueBtn.addSubview(imageView)
+//        imageView.snp.makeConstraints { make in
+//            make.width.height.equalTo(24)
+//            make.centerY.equalToSuperview()
+//            make.trailing.equalTo(-24)
+//        }
+//        
+//        return continueBtn
+//    }()
     
     lazy var scrollView: UIScrollView = {
         let scrollView = UIScrollView()
         scrollView.isScrollEnabled = false
         scrollView.frame = self.view.bounds
 
-        let imageView0 = UIImageView.createImageView(imageName: "bootPage_0")
-        imageView0.frame = CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight)// 813*kDesignScale)
+        let imageView0 = UIImageView.createImageView(imageName: "bootPage_0",contentMode: .scaleAspectFill)
+        imageView0.frame = CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight)
         scrollView.addSubview(imageView0)
         
-        let imageView1 = UIImageView.createImageView(imageName: "bootPage_1")
-        imageView1.frame = CGRectMake(k_ScreenWidth, 0, k_ScreenWidth, 813*kDesignScale)
+        let imageView1 = UIImageView.createImageView(imageName: "bootPage_1",contentMode: .scaleAspectFill)
+        imageView1.frame = CGRectMake(k_ScreenWidth, 0, k_ScreenWidth, k_ScreenHeight)
         scrollView.addSubview(imageView1)
         
-        let imageView2 = UIImageView.createImageView(imageName: "bootPage_2")
-        imageView2.frame = CGRectMake(k_ScreenWidth*2, 0, k_ScreenWidth, 813*kDesignScale)
+        let imageView2 = UIImageView.createImageView(imageName: "bootPage_2",contentMode: .scaleAspectFill)
+        imageView2.frame = CGRectMake(k_ScreenWidth*2, 0, k_ScreenWidth, k_ScreenHeight)
         scrollView.addSubview(imageView2)
         
         let btnH = k_ScreenHeight/5

+ 0 - 1
AIRingtone/Business/TSAIPhotoVC/TSAIPhotoChildVC/TSAIPhotoChildVC.swift

@@ -56,7 +56,6 @@ class TSAIPhotoChildVC: TSBaseVC {
     }()
     
     override func createView() {
-        edgesForExtendedLayout = []
         setNavBarViewHidden(true)
         contentView.addSubview(collectionComponent.collectionView)
     }

+ 2 - 3
AIRingtone/Business/TSAIPhotoVC/TSAIPhotoVC.swift

@@ -52,7 +52,7 @@ class TSAIPhotoVC: TSBaseVC {
 //        let pagingView = JXPagingListRefreshView(delegate: self) //list 刷新
         let pagingView = JXPagingView(delegate: self)
         pagingView.mainTableView.backgroundColor = .clear
-        pagingView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: k_ScreenHeight - k_Nav_Height  - k_Height_safeAreaInsetsBottom() - CGFloat(headerInSectionHeight) + 10)
+        pagingView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: k_ScreenHeight - k_Nav_Height)// - CGFloat(headerInSectionHeight) + 10)
         pagingView.listContainerView.listCellBackgroundColor = .clear
         //扣边返回处理,下面的代码要加上
         pagingView.listContainerView.scrollView.panGestureRecognizer.require(toFail: self.navigationController!.interactivePopGestureRecognizer!)
@@ -112,8 +112,7 @@ class TSAIPhotoVC: TSBaseVC {
  
     override func createView() {
         setViewBgImageNamed(named: kViewBJ)
-        edgesForExtendedLayout = []
-        
+  
 //        navBarContentView.addSubview(navBarView)
 //        navBarView.snp.makeConstraints { make in
 //            make.edges.equalToSuperview()

+ 36 - 36
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/TSGenneralPicVM.swift

@@ -48,50 +48,50 @@ class TSGenneralPicVM {
     var gennerateType:TSGennerateType = .poster
     var generatingProgress = 0
     
-//    //模拟数据
-//    func creatImageEmoji(text:String) {
-//        stateDatauPblished = (.start,nil)
-//        stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-//
-//        kDelayOnMainThread(0.2) {
-//            self.stateDatauPblished = (.progressString(self.generating(progress: 0.5)),nil)
-//        }
-//
-//        kDelayOnMainThread(1.0) {
-//            if Bool.random() {
-//                let infoModel = TSActionInfoModel(JSON: self.gennerateType == .poster ? actionInfoDictPoster : actionInfoDictPhoto)
-//                self.stateDatauPblished = (.success(nil),infoModel)
-//            }else{
-//                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
-//            }
-//        }
-//    }
-    
-    //width 和 height 必须是 32 的倍数
+    //模拟数据
     func creatImageEmoji(text:String) {
-        generatingProgress = 0
-        aiText = text
-        let postDict:[String : Any] = [
-            "prompt":text,
-            "width":textPicW,
-            "height":textPicH
-        ]
         stateDatauPblished = (.start,nil)
         stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-        creatRequest = TSNetworkShared.post(urlType: .textPicCreate,parameters: postDict) { [weak self] data,error in
-            guard let self = self else { return }
-            if let dataDict = data as? [String:Any] ,
-               dataDict.safeInt(forKey: "code") == 200,
-               let actionId = dataDict["actionId"] as? Int{
-                if stopNetwork == false {
-                    self.getActionInfo(action_id:actionId)
-                }
+
+        kDelayOnMainThread(0.2) {
+            self.stateDatauPblished = (.progressString(self.generating(progress: 0.5)),nil)
+        }
+
+        kDelayOnMainThread(1.0) {
+            if Bool.random() {
+                let infoModel = TSActionInfoModel(JSON: self.gennerateType == .poster ? actionInfoDictPoster : actionInfoDictPhoto)
+                self.stateDatauPblished = (.success(nil),infoModel)
             }else{
-                self.stateDatauPblished = (.failed(error?.localizedDescription ?? ""),nil)
+                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
             }
         }
     }
     
+//    //width 和 height 必须是 32 的倍数
+//    func creatImageEmoji(text:String) {
+//        generatingProgress = 0
+//        aiText = text
+//        let postDict:[String : Any] = [
+//            "prompt":text,
+//            "width":textPicW,
+//            "height":textPicH
+//        ]
+//        stateDatauPblished = (.start,nil)
+//        stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
+//        creatRequest = TSNetworkShared.post(urlType: .textPicCreate,parameters: postDict) { [weak self] data,error in
+//            guard let self = self else { return }
+//            if let dataDict = data as? [String:Any] ,
+//               dataDict.safeInt(forKey: "code") == 200,
+//               let actionId = dataDict["actionId"] as? Int{
+//                if stopNetwork == false {
+//                    self.getActionInfo(action_id:actionId)
+//                }
+//            }else{
+//                self.stateDatauPblished = (.failed(error?.localizedDescription ?? ""),nil)
+//            }
+//        }
+//    }
+    
     func getActionInfo(action_id:Int){
         queryRequest = TSNetworkShared.get(urlType: .actionInfo,parameters: ["action_id":action_id]) { [weak self] data,error in
             guard let self = self else { return }

+ 4 - 0
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/Model/TSRingModel.swift

@@ -24,6 +24,10 @@ class TSRingModel:TSBaseModel {
         categoryId <- map["categoryId"]
     }
     
+    
+    var downloadOp:DownloadOperation?
+    var audioPlayerFileTool:TSAudioPlayerFileTool?
+    
 }
 
 class TSRingCategoryModel:TSBaseModel {

+ 31 - 20
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift

@@ -12,12 +12,7 @@ class TSAIRintoneVC: TSBaseVC {
         let viewModel = TSAIRintoneVM()
         return viewModel
     }()
-    
-    lazy var ringTool: TSBandRingTool = {
-        let ringTool = TSBandRingTool(targetVC: self)
-        return ringTool
-    }()
-    
+
     lazy var navBarView: TSBaseNavContentBarView = {
         let navBarView = TSBaseNavContentBarView()
 
@@ -109,17 +104,10 @@ class TSAIRintoneVC: TSBaseVC {
     
     override func dealThings() {
         updateListView()
-        
-//        //先拿下缓存的
-//        viewModel.getRingRecommend(useCache: true) { [weak self]  in guard
-//            let self = self else { return }
-//            collectionView.reloadData()
-//            //再拿下最新的数据
-            viewModel.getRingRecommend(useCache: false) { [weak self]  in guard
-                let self = self else { return }
-                collectionView.reloadData()
-            }
-//        }
+        viewModel.getRingRecommend(useCache: false) { [weak self]  in guard
+            let self = self else { return }
+            collectionView.reloadData()
+        }
     }
     
     func updateListView(){
@@ -170,9 +158,12 @@ extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate,UI
         if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
         let model = sectionModel.list.safeObj(At: indexPath.item){
             if let model = model as? TSActionInfoModel {
-                ringTool.shareBand(with: model.response.musicUrl, fileName: model.response.title)
+                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
             }else if let ringModel = model as? TSRingModel {
-                ringTool.shareBand(with: ringModel.audioUrl, fileName: ringModel.title)
+                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title){[weak self]  in
+                    guard let self = self else { return }
+                    TSRecommendRintoneHistory.saveModel(model: ringModel)
+                }
             }
         }
         
@@ -189,12 +180,32 @@ extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate,UI
             if kind == UICollectionView.elementKindSectionHeader {
                 if let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TSAIRintoneHistorySectionHeaderView.reuseIdentifier, for: indexPath) as? TSAIRintoneHistorySectionHeaderView {
                     header.titleView.titleLab.text = sectionModel.title
+                    header.rightBtn.isHidden = true
+                    if indexPath.section == 0 {
+                        header.rightBtn.isHidden = false
+                        header.rightBtn.setUpButton{ [weak self]  in
+                            guard let self = self else { return }
+                            
+                            let vc =  TSGenerateHistoryVC()
+                            vc.reloadUIBlock = { [weak self]  in
+                                guard let self = self else { return }
+                                viewModel.updateRecentData()
+                                updateListView()
+                            }
+                            kPushVC(target: self, modelVC: vc)
+                        }
+                    }
+        
                     return header
                 }
             }else if kind == UICollectionView.elementKindSectionFooter{//},indexPath.section == (viewModel.modelList.count-1) {
                 if let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: TSAIRintoneHistorySectionFooterView.reuseIdentifier, for: indexPath) as? TSAIRintoneHistorySectionFooterView {
                     footer.clickView = {
-                        TSTabBarController.selectedIndex(1)
+                        let ringCategoryModel = TSRingCategoryModel()
+                        ringCategoryModel.title = "TikTok"
+                        ringCategoryModel.cover = "http://d3a93z8fj970a4.cloudfront.net/0c359196-aeeb-45e6-bc98-eddf50f1dd53"
+                        ringCategoryModel.categoryId = "Best of TikTok"
+                        kPushVC(target: self, modelVC: TSDiscoverListVC(ringCategoryModel: ringCategoryModel))
                     }
                     return footer
                 }

+ 62 - 63
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift

@@ -6,11 +6,15 @@
 //
 
 import SwipeCellKit
-class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
+class TSAIRintoneHistoryCell: SwipeCollectionViewCell  {
+    
+    public var colComponent:TSCollectionViewComponent?
+    public var colAttributes:[String : Any]?
     
     override init(frame: CGRect) {
         super.init(frame: frame)
         creatUI()
+        dealThings()
     }
     
     required public init?(coder: NSCoder) {
@@ -21,6 +25,10 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
 
     lazy var setRingBtn: TSUIExpandedTouchButton = {
         let setRingBtn = TSUIExpandedTouchButton()
+        setRingBtn.setUpButton(image: UIImage(named: "ai_setRing_icon")){[weak self]  in
+            guard let self = self else { return }
+            actionHandler(any: "SetNow")
+        }
         setRingBtn.setImage(UIImage(named: "ai_setRing_icon"), for: .normal)
         return setRingBtn
     }()
@@ -54,6 +62,13 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
         return exampleView
     }()
     
+    
+    lazy var vipView: UIImageView = {
+        let vipView = UIImageView.createImageView(imageName: "ring_vip")
+        vipView.isHidden = true
+        return vipView
+    }()
+    
     var model:TSActionInfoModel?{
         didSet{
             guard let model = model else { return }
@@ -61,6 +76,7 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
             ringView.nameLab.text = model.response.title
             ringView.setCoverImageView(urlString: model.response.coverUrl)
             exampleView.isHidden = model.modelType != .example
+            vipView.isHidden = true
         }
     }
     
@@ -71,6 +87,7 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
             ringView.nameLab.text = ringModel.title
             ringView.setCoverImageView(urlString: "")
             exampleView.isHidden = true
+            vipView.isHidden = !ringModel.vip
         }
     }
     
@@ -78,7 +95,13 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
         didSet{
             ringView.isloading = isSelected
             if isSelected, self.ringView.isPlay == false{
-                TSBusinessAudioPlayer.shared.playUrlString( model?.response.musicUrl)
+            
+                if let model = model{
+                    TSBusinessAudioPlayer.shared.playUrlString(model.response.musicUrl)
+                }else if let ringModel = ringModel{
+                    TSBusinessAudioPlayer.shared.playUrlString(ringModel.audioUrl)
+                }
+ 
             }else{
                 TSBusinessAudioPlayer.shared.stop()
                 backgroundColor = .cardColor
@@ -109,6 +132,16 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
             make.width.height.equalTo(20)
         }
         
+        contentView.addSubview(vipView)
+        vipView.snp.makeConstraints { make in
+            make.top.trailing.equalTo(0)
+            make.width.height.equalTo(24)
+        }
+        
+
+    }
+    
+    func dealThings(){
         NotificationCenter.default.addObserver(forName: .kBusinessAudioStateChange, object: nil, queue: nil) { notification in
             if let userInfo = notification.userInfo as? [String: TSBusinessAudioPlayer.PlayerState], let state = userInfo["PlayerState"] {
                 kExecuteOnMainThread {
@@ -136,6 +169,10 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
         case .play:
             self.ringView.isPlay = true
             backgroundColor = "#3C213F".uiColor
+            
+            if let ringModel = ringModel {
+                TSRecommendRintoneHistory.saveModel(model: ringModel)
+            }
         case .stop:
             self.ringView.isPlay = false
             backgroundColor = .cardColor
@@ -149,76 +186,38 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
     }
 }
 
-class TSAIRintoneHistorySectionHeaderView: UICollectionReusableView {
-    static let reuseIdentifier = "TSAIRintoneHistorySectionHeaderView"
+extension TSAIRintoneHistoryCell : TSComponentView {
     
-    let titleView: TSTGPTitleView = {
-        let titleView = TSTGPTitleView()
-        return titleView
-    }()
-    
-    
-    
-    override init(frame: CGRect) {
-        super.init(frame: frame)
+    func renderView(with object: Any?, component: TSCollectionViewComponent, attributes: [String : Any]?) {
+        self.colComponent = component
+        self.colAttributes = attributes
         
-        addSubview(titleView)
-        titleView.snp.makeConstraints { make in
-            make.edges.equalToSuperview()
-        }
-    }
-    
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-}
-
-class TSAIRintoneHistorySectionFooterView: UICollectionReusableView {
-    static let reuseIdentifier = "TSAIRintoneHistorySectionFooterView"
-    var clickView:(()->Void)?
-    
-    lazy var moreView: UIView = {
-        let moreView = UIView()
-        moreView.backgroundColor = .white.withAlphaComponent(0.1)
-        moreView.cornerRadius = 10.0
-        let textLabel = UILabel.createLabel(text: "more".localized,font: .font(size: 12),textColor: .white.withAlphaComponent(0.6))
-        moreView.addSubview(textLabel)
         
-        let imageView = UIImageView.createImageView(imageName: "gary_right_arrow")
-        moreView.addSubview(imageView)
-        
-        textLabel.snp.makeConstraints { make in
-            make.leading.equalTo(8)
-            make.centerY.equalToSuperview()
-        }
-        
-        imageView.snp.makeConstraints { make in
-            make.centerY.equalToSuperview()
-            make.leading.equalTo(textLabel.snp.trailing).offset(2)
-            make.width.height.equalTo(12)
-            make.trailing.equalTo(-4)
+        if let itemModel = object as? TSColVVMItemModel{
+            if let dataModel = itemModel.dataModel as? TSRingModel{
+                ringModel = dataModel
+            }
         }
-        moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickMoreView)))
-        return moreView
-    }()
-    
-    
+
+    }
     
-    override init(frame: CGRect) {
-        super.init(frame: frame)
-        
-        addSubview(moreView)
-        moreView.snp.makeConstraints { make in
-            make.center.equalToSuperview()
-            make.height.equalTo(20)
+    public var indexPath:IndexPath?{
+        if let attributes = colAttributes , let IndexPath = attributes[kIndexPath] as? IndexPath{
+            return IndexPath
         }
+        return nil
     }
     
-    required init?(coder: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
+    public var itemActionHandler: ((Any?, IndexPath) -> Void)?{
+        if let colComponent = colComponent,let itemActionHandler = colComponent.itemActionHandler {
+            return itemActionHandler
+        }
+        return nil
     }
     
-    @objc func clickMoreView() {
-        clickView?()
+    public func actionHandler(any:Any?){
+        if let sectionActionHandler = itemActionHandler ,let indexPath = indexPath{
+            sectionActionHandler(any,indexPath)
+        }
     }
 }

+ 91 - 0
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistorySectionHeaderView.swift

@@ -0,0 +1,91 @@
+//
+//  TSAIRintoneHistorySectionHeaderView.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+
+class TSAIRintoneHistorySectionHeaderView: UICollectionReusableView {
+    static let reuseIdentifier = "TSAIRintoneHistorySectionHeaderView"
+    
+    let titleView: TSTGPTitleView = {
+        let titleView = TSTGPTitleView()
+        return titleView
+    }()
+    
+    lazy var rightBtn: TSUIExpandedTouchButton = {
+        let rightBtn = TSUIExpandedTouchButton()
+        rightBtn.setUpButton(title: "See All".localized,font: .font(size: 14),titleColor: .themeColor)
+        return rightBtn
+    }()
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        addSubview(titleView)
+        titleView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+        
+        addSubview(rightBtn)
+        rightBtn.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.trailing.equalTo(-16)
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}
+
+
+class TSAIRintoneHistorySectionFooterView: UICollectionReusableView {
+    static let reuseIdentifier = "TSAIRintoneHistorySectionFooterView"
+    var clickView:(()->Void)?
+    
+    lazy var moreView: UIView = {
+        let moreView = UIView()
+        moreView.backgroundColor = .white.withAlphaComponent(0.1)
+        moreView.cornerRadius = 10.0
+        let textLabel = UILabel.createLabel(text: "more".localized,font: .font(size: 12),textColor: .white.withAlphaComponent(0.6))
+        moreView.addSubview(textLabel)
+        
+        let imageView = UIImageView.createImageView(imageName: "gary_right_arrow")
+        moreView.addSubview(imageView)
+        
+        textLabel.snp.makeConstraints { make in
+            make.leading.equalTo(8)
+            make.centerY.equalToSuperview()
+        }
+        
+        imageView.snp.makeConstraints { make in
+            make.centerY.equalToSuperview()
+            make.leading.equalTo(textLabel.snp.trailing).offset(2)
+            make.width.height.equalTo(12)
+            make.trailing.equalTo(-4)
+        }
+        moreView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickMoreView)))
+        return moreView
+    }()
+    
+    
+    
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        
+        addSubview(moreView)
+        moreView.snp.makeConstraints { make in
+            make.center.equalToSuperview()
+            make.height.equalTo(20)
+        }
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    @objc func clickMoreView() {
+        clickView?()
+    }
+}

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

@@ -45,11 +45,6 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
         return generateInView
     }()
 
-    lazy var ringTool: TSBandRingTool = {
-        let ringTool = TSBandRingTool(targetVC: self)
-        return ringTool
-    }()
-    
     override func createView() {
         super.createView()
         
@@ -100,9 +95,7 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
             return
         }
         audioPlayer.stop()
-        ringTool.shareBand(with: model.response.musicUrl, fileName: model.response.title) { success in
-            
-        }
+        _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
     }
     
     @objc func clickPlay(){

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

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

+ 187 - 0
AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVC.swift

@@ -0,0 +1,187 @@
+//
+//  TSGenerateHistoryVC.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+import SwipeCellKit
+class TSGenerateHistoryVC: TSBaseVC {
+
+    lazy var viewModel : TSGenerateHistoryVM = {
+        let viewModel = TSGenerateHistoryVM()
+        return viewModel
+    }()
+    
+    var reloadUIBlock:(()->Void)?
+    
+    lazy var layout: UICollectionViewFlowLayout = {
+        let layout = UICollectionViewFlowLayout()
+        layout.scrollDirection = .vertical
+        layout.itemSize = CGSize(width: k_ScreenWidth-32, height: 74)
+        layout.minimumInteritemSpacing = 10.0
+        layout.minimumLineSpacing = 18.0
+        return layout
+    }()
+    
+    lazy var collectionView: UICollectionView = {
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.showsHorizontalScrollIndicator = false
+        collectionView.backgroundColor = .clear
+        collectionView.contentInset = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0)
+        collectionView.register(TSAIRintoneHistoryCell.self, forCellWithReuseIdentifier: TSAIRintoneHistoryCell.cellID)
+        if #available(iOS 11.0, *) {
+            collectionView.contentInsetAdjustmentBehavior = .never
+        }
+        return collectionView
+    }()
+    
+
+    lazy var generalRintoneVC: TSTextGeneralRintoneVC = {
+        let generalRintoneVC = TSTextGeneralRintoneVC()
+        generalRintoneVC.reloadUIBlock = { [weak self]  in
+            guard let self = self else { return }
+            viewModel.updateRecentData()
+            updateListView()
+        }
+        return generalRintoneVC
+    }()
+
+    override func createView() {
+        
+        setViewBgImageNamed(named: kViewBJ)
+        
+        addNormalNavBarView()
+        setPageTitle("Generate History".localized)
+
+        contentView.addSubview(collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+    }
+    
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        TSBusinessAudioPlayer.shared.stop()
+    }
+    
+    override func dealThings() {
+        updateListView()
+    }
+    
+    func updateListView(){
+        collectionView.reloadData()
+    }
+    
+    override func navBarClickLeftAction() {
+        super.navBarClickLeftAction()
+        self.reloadUIBlock?()
+    }
+}
+
+extension TSGenerateHistoryVC: UICollectionViewDataSource ,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
+    
+    public func numberOfSections(in collectionView: UICollectionView) -> Int {
+        return viewModel.modelList.count
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        if let sectionModel = viewModel.modelList.safeObj(At: section) {
+            return sectionModel.list.count
+        }
+        return 0
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TSAIRintoneHistoryCell.cellID, for: indexPath)
+        
+        if let sectionModel = viewModel.modelList.safeObj(At: indexPath.section),
+            let itemModel = sectionModel.list.safeObj(At: indexPath.item),
+           let cell = cell as? TSAIRintoneHistoryCell
+        {
+            cell.delegate = self
+            
+            if let model = itemModel as? TSActionInfoModel {
+                cell.model = model
+            }else if let ringModel = itemModel as? TSRingModel {
+                cell.ringModel = ringModel
+            }
+            
+            cell.setRingBtn.indexPath = indexPath
+            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+        }
+        
+        return cell
+    }
+
+    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+        let indexPath = btn.indexPath
+        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+        let model = sectionModel.list.safeObj(At: indexPath.item){
+            if let model = model as? TSActionInfoModel {
+                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+            }else if let ringModel = model as? TSRingModel {
+                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title)
+            }
+        }
+        
+        collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .top)
+        TSBusinessAudioPlayer.shared.stop()
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+    }
+    
+}
+
+extension TSGenerateHistoryVC: SwipeCollectionViewCellDelegate {
+    
+    func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
+        guard orientation == .right else { return nil }
+        
+        if indexPath.section != 0 {
+            return nil
+        }
+        
+        // 删除操作
+        let deleteAction = SwipeAction(style: .destructive, title: nil) {[weak self] action, indexPath in
+            guard let self = self else { return }
+            showCustomAlert(message: "Are you sure to delete".localized, deleteHandler:  {
+                if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+                let model = sectionModel.list.safeObj(At: indexPath.item){
+                    if let model = model as? TSActionInfoModel {
+                        collectionView.performBatchUpdates({
+                            self.viewModel.removeModel(model: model)
+                            if sectionModel.list.count == 0 {
+                                collectionView.deleteSections([indexPath.section])
+                            }else{
+                                collectionView.deleteItems(at: [indexPath])
+                            }
+                        })
+                    }
+                }
+            })
+        }
+        
+        deleteAction.backgroundColor = "#E83E3E".uiColor
+        deleteAction.image = UIImage(named: "delete_white")
+        
+        return [deleteAction]
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
+        var options = SwipeOptions()
+//        options.expansionStyle = .destructive(automaticallyDelete: false) // 完全滑动时是否自动触发操作
+        options.transitionStyle = .border // 滑动动画样式
+        return options
+    }
+    
+}

+ 46 - 0
AIRingtone/Business/TSAIRintoneVC/TSGenerateHistoryVC/TSGenerateHistoryVM.swift

@@ -0,0 +1,46 @@
+//
+//  TSGenerateHistoryVM.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+
+import ObjectMapper
+
+class TSGenerateHistoryVM {
+    
+    lazy var modelList: [TSAIRintoneHistoryModel] = {
+        getModelList()
+    }()
+    
+    private func getModelList()->[TSAIRintoneHistoryModel]{
+        var list = [TSAIRintoneHistoryModel]()
+        
+        if aiRintoneHistoryModel.list.count > 0 {
+            list.append(aiRintoneHistoryModel)
+        }
+        return list
+    }
+    
+    //生成的历史
+    lazy var aiRintoneHistoryModel: TSAIRintoneHistoryModel = {
+        let model = TSAIRintoneHistoryModel(title: "Generate History", list:TSAIRintoneHistory.listModelArray)
+        return model
+    }()
+
+}
+
+
+extension TSGenerateHistoryVM {
+    
+    func updateRecentData() {
+        aiRintoneHistoryModel.list = Array(TSAIRintoneHistory.listModelArray.prefix(2))
+        modelList = getModelList()
+    }
+    
+    func removeModel(model:TSActionInfoModel){
+        TSAIRintoneHistory.removeModel(model: model)
+        updateRecentData()
+    }
+    
+}

+ 2 - 2
AIRingtone/Business/TSCollectionViewVM/TSCollectionViewVM+Config.swift

@@ -246,7 +246,7 @@ let ringCategoriesConfig:TSColVVMSizeConfig = {
 }()
 
 
-//MARK: ring list 分类配置
+//MARK: ring list 配置
 let ringListConfig:TSColVVMSizeConfig = {
 
     let sectionInset = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
@@ -264,7 +264,7 @@ let ringListConfig:TSColVVMSizeConfig = {
     let h = w/originalScale
     
     let cellSize = CGSizeMake(w, h)
-    let cellClass = TSDiscoverListCell.self
+    let cellClass = TSAIRintoneHistoryCell.self // TSDiscoverListCell.self
     
     let headerViewSize = CGSizeMake(k_ScreenWidth, 0)
     let headerView = TSColVVMSectionHeaderView.self

+ 108 - 42
AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/TSDiscoverListVC.swift

@@ -12,90 +12,156 @@ class TSDiscoverListVC: TSBaseVC {
         self.ringCategoryModel = ringCategoryModel
         super.init()
     }
-    
+
     @MainActor required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
-    
+
     lazy var viewModel:TSDiscoverListVM = {
         let viewModel = TSDiscoverListVM(categoryID:ringCategoryModel.categoryId,animationView: self.view)
         return viewModel
     }()
     
-
-    lazy var collectionComponent: TSCollectionViewComponent = {
+    lazy var layout: UICollectionViewFlowLayout = {
         let layout = UICollectionViewFlowLayout()
-        let cp = TSCollectionViewComponent(frame: CGRect.zero, layout: layout, attributes: [ :])
-        cp.collectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 20, right: 0)
-        
-//        cp.itemDidSelectedHandler = { [weak self] (object, indexPath) in
-//            guard let self = self else { return }
-//            if indexPath.section == 0{
-//                kPushVC(target: self, modelVC: TSAIPhotoVC())
-//                return
-//            }
-//
-//            if let sections = viewModel.colDataArray.safeObj(At: indexPath.section) as? TSColVVMSectionModel{
-//                let browseVC = TSThemeBrowseVC(currentIndex: indexPath.row, themeViewModel: viewModel) { [weak self]  in
-//                    guard let self = self else { return }
-//                    collectionComponent.reloadData()
-//                }
-//                kPushVC(target: self, modelVC: browseVC)
-//            }
-//        }
+        layout.scrollDirection = .vertical
+        layout.itemSize = CGSize(width: k_ScreenWidth-32, height: 74)
+        layout.minimumInteritemSpacing = 10.0
+        layout.minimumLineSpacing = 18.0
+        return layout
+    }()
+    
+    lazy var collectionView: UICollectionView = {
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.showsHorizontalScrollIndicator = false
+        collectionView.backgroundColor = .clear
+        collectionView.contentInset = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0)
+        collectionView.register(TSAIRintoneHistoryCell.self, forCellWithReuseIdentifier: TSAIRintoneHistoryCell.cellID)
+        if #available(iOS 11.0, *) {
+            collectionView.contentInsetAdjustmentBehavior = .never
+        }
         
-        cp.collectionView.addRefresh{ [weak self]  in
+        collectionView.addRefresh{ [weak self]  in
             guard let self = self else { return }
             viewModel.reloadAllData { [weak self] success  in
                 guard let self = self else { return }
                 if success {
-                    collectionComponent.clear()
-                    collectionComponent.reloadView(with:viewModel.colDataArray)
+                    updateListView()
                 }
-                collectionComponent.collectionView.endRefreshing()
+                collectionView.endRefreshing()
             }
         }
-        
-        cp.collectionView.addLoadMore { [weak self]  in
+
+        collectionView.addLoadMore { [weak self]  in
             guard let self = self else { return }
-            
+
             viewModel.getMoreData { [weak self] success,haveMore in
                 guard let self = self else { return }
                 if success {
-                    collectionComponent.reloadData()
+                    updateListView()
                 }
-                collectionComponent.collectionView.endRefreshing(noMore: !haveMore)
+                collectionView.endRefreshing(noMore: !haveMore)
             }
         }
-        return cp
+        
+        return collectionView
     }()
     
-    
     override func createView() {
+        
+        setViewBgImageNamed(named: kViewBJ)
+        
         addNormalNavBarView()
         setPageTitle(ringCategoryModel.title)
-        _ = setNavigationItem("", imageName: "tutorials", direction: .right, action: #selector(clickRight))
-        
-        contentView.addSubview(collectionComponent.collectionView)
-        collectionComponent.collectionView.snp.makeConstraints { make in
+
+        contentView.addSubview(collectionView)
+        collectionView.snp.makeConstraints { make in
             make.edges.equalToSuperview()
         }
+    }
 
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+    }
+    
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        TSBusinessAudioPlayer.shared.stop()
     }
     
     override func dealThings() {
         viewModel.getData {[weak self] success in
             guard let self = self else { return }
             if success {
-                collectionComponent.clear()
-                collectionComponent.reloadView(with:viewModel.colDataArray)
+                updateListView()
             }
         }
     }
     
-    @objc func clickRight(){
-        let browseVC = TSThemeTutorialsVC()
-        kPushVC(target: self, modelVC: browseVC)
+    func updateListView(){
+        collectionView.reloadData()
     }
+    
+
 }
 
+extension TSDiscoverListVC: UICollectionViewDataSource ,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
+    
+    public func numberOfSections(in collectionView: UICollectionView) -> Int {
+        return viewModel.modelList.count
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        if let sectionModel = viewModel.modelList.safeObj(At: section) {
+            return sectionModel.list.count
+        }
+        return 0
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TSAIRintoneHistoryCell.cellID, for: indexPath)
+        
+        if let sectionModel = viewModel.modelList.safeObj(At: indexPath.section),
+            let itemModel = sectionModel.list.safeObj(At: indexPath.item),
+           let cell = cell as? TSAIRintoneHistoryCell
+        {
+            if let model = itemModel as? TSActionInfoModel {
+                cell.model = model
+            }else if let ringModel = itemModel as? TSRingModel {
+                cell.ringModel = ringModel
+            }
+            
+            cell.setRingBtn.indexPath = indexPath
+            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+        }
+        
+        return cell
+    }
+
+    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+        let indexPath = btn.indexPath
+        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+        let model = sectionModel.list.safeObj(At: indexPath.item){
+            if let model = model as? TSActionInfoModel {
+                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+            }else if let ringModel = model as? TSRingModel {
+                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title){[weak self]  in
+                    guard let self = self else { return }
+                    TSRecommendRintoneHistory.saveModel(model: ringModel)
+                }
+            }
+        }
+        
+        collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .top)
+        TSBusinessAudioPlayer.shared.stop()
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+    }
+    
+}

+ 127 - 29
AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/VM/TSDiscoverListVM.swift

@@ -9,16 +9,27 @@
 import ObjectMapper
 class TSDiscoverListVM {
 
-    var colDataArray:[TSComponent] = [TSComponent]()
+//    var colDataArray:[TSComponent] = [TSComponent]()
+//    
+//    lazy var contentSecitonModel: TSColVVMSectionModel = {
+//        let contentSeciton = TSColVVMSectionModel()
+//        contentSeciton.style = .ringList
+//        return contentSeciton
+//    }()
+//    
+//    
+  
+    lazy var modelList: [TSAIRintoneHistoryModel] = {
+        return [discoverList]
+    }()
     
-    lazy var contentSecitonModel: TSColVVMSectionModel = {
-        let contentSeciton = TSColVVMSectionModel()
-        contentSeciton.style = .ringList
-        return contentSeciton
+    lazy var discoverList: TSAIRintoneHistoryModel = {
+        let model = TSAIRintoneHistoryModel(title: "Generate History", list:[])
+        return model
     }()
     
+//    var listModelArray: [TSRingModel] = []
     
-    var listModelArray: [TSRingModel] = []
     
     init(categoryID: String,animationView:UIView) {
         self.categoryID = categoryID
@@ -27,8 +38,6 @@ class TSDiscoverListVM {
     }
     
     func combinedData(){
-        colDataArray.removeAll()
-        colDataArray.append(contentSecitonModel)
     }
     
     weak var animationView:UIView?
@@ -38,19 +47,6 @@ class TSDiscoverListVM {
 
 }
 
-extension TSDiscoverListVM {
-    func setContentItemModels(){
-        var array = [TSColVVMItemModel]()
-        listModelArray.forEach { themeModel in
-            let itemModel = TSColVVMItemModel()
-            itemModel.dataModel = themeModel
-            itemModel.style = .ringList
-            array.append(itemModel)
-        }
-        contentSecitonModel.items = array
-    }
-}
- 
 extension TSDiscoverListVM {
     
     func getData(complete:@escaping (Bool)->Void){
@@ -58,9 +54,7 @@ extension TSDiscoverListVM {
         getNetWorkData(useCache: true,animationView: animationView) { [weak self] modelArray,success  in
             guard let self = self else { return }
             if modelArray.count > 0 {
-                listModelArray.removeAll()
-                listModelArray.append(contentsOf: modelArray)
-                setContentItemModels()
+                discoverList.list = modelArray
                 complete(true)
             }else{
                 complete(false)
@@ -76,9 +70,7 @@ extension TSDiscoverListVM {
         getNetWorkData(useCache: false,animationView: nil) {[weak self] modelArray,success  in
             guard let self = self else { return }
             if modelArray.count > 0 {
-                listModelArray.removeAll()
-                listModelArray.append(contentsOf: modelArray)
-                setContentItemModels()
+                discoverList.list = modelArray
                 complete(true)
             }else{
                 complete(false)
@@ -93,8 +85,7 @@ extension TSDiscoverListVM {
         getNetWorkData(useCache: true,animationView: nil) { [weak self] modelArray,success in
             guard let self = self else { return }
             if modelArray.count > 0 {
-                listModelArray.append(contentsOf: modelArray)
-                setContentItemModels()
+                discoverList.list = discoverList.list + modelArray
                 complete(success,true)
             }else{
                 complete(success,false)
@@ -128,3 +119,110 @@ extension TSDiscoverListVM {
 }
 
 
+
+//
+//extension TSDiscoverListVM {
+//
+//    
+////    func setContentItemModels(){
+////        var array = [TSColVVMItemModel]()
+////        listModelArray.forEach { themeModel in
+////            array.append(getTSColVVMItemModel(model: themeModel))
+////        }
+////        contentSecitonModel.items = array
+////    }
+////    
+////    func appendContentItemModels(modelArray:[TSRingModel]){
+////        var array = [TSColVVMItemModel]()
+////        modelArray.forEach { themeModel in
+////            array.append(getTSColVVMItemModel(model: themeModel))
+////        }
+////        contentSecitonModel.items = contentSecitonModel.items + array
+////    }
+////    
+////    func getTSColVVMItemModel(model:TSRingModel)->TSColVVMItemModel{
+////        let itemModel = TSColVVMItemModel()
+////        itemModel.dataModel = model
+////        itemModel.style = .ringList
+////        return itemModel
+////    }
+//}
+// 
+//extension TSDiscoverListVM {
+//    
+//    func getData(complete:@escaping (Bool)->Void){
+//        pageNum = kPageNum
+//        getNetWorkData(useCache: true,animationView: animationView) { [weak self] modelArray,success  in
+//            guard let self = self else { return }
+//            if modelArray.count > 0 {
+//                listModelArray.removeAll()
+//                listModelArray.append(contentsOf: modelArray)
+//                setContentItemModels()
+//                complete(true)
+//            }else{
+//                complete(false)
+//            }
+//        }
+//    }
+//    
+//    /// 上拉刷新
+//    /// - Parameter complete: (是否成功)
+//    func reloadAllData(complete:@escaping (Bool)->Void){
+//        TSNetworkShared.removeCache(urlType:.rings)
+//        pageNum = kPageNum
+//        getNetWorkData(useCache: false,animationView: nil) {[weak self] modelArray,success  in
+//            guard let self = self else { return }
+//            if modelArray.count > 0 {
+//                listModelArray.removeAll()
+//                listModelArray.append(contentsOf: modelArray)
+//                setContentItemModels()
+//                complete(true)
+//            }else{
+//                complete(false)
+//            }
+//        }
+//    }
+//    
+//    /// 获取更多数据
+//    /// - Parameter complete: (是否成功,还有更多数据)
+//    func getMoreData(complete:@escaping (Bool,Bool)->Void){
+//        pageNum+=1
+//        getNetWorkData(useCache: true,animationView: nil) { [weak self] modelArray,success in
+//            guard let self = self else { return }
+//            if modelArray.count > 0 {
+//                listModelArray.append(contentsOf: modelArray)
+////                setContentItemModels()
+//                appendContentItemModels(modelArray: modelArray)
+//                complete(success,true)
+//            }else{
+//                complete(success,false)
+//            }
+//        }
+//    }
+//
+//    func getNetWorkData(useCache:Bool,animationView:UIView?,complete:@escaping ([TSRingModel],Bool)->Void){
+//        
+//        let parameters:[String:Any] = [
+//            "page_size":pageSize,
+//            "page":pageNum,
+//            "category_id":categoryID,
+//        ]
+//        
+//        _ = TSNetworkShared.get(
+//            urlType: .rings,
+//            parameters: parameters,
+//            animationView: animationView,
+//            useCache: useCache)
+//        {  data, error in
+//            if let result = kNetWorkResultArraySuccess(data: data) {
+//                let modelArray = Mapper<TSRingModel>().mapArray(JSONArray: result)
+//                complete(modelArray,true)
+//            }else{
+//                complete([],false)
+//            }
+//        }
+//    }
+//    
+//}
+//
+

+ 39 - 24
AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/View/TSDiscoverListCell.swift

@@ -17,6 +17,7 @@ class TSDiscoverListCell: TSBaseCollectionCell {
     lazy var ringView: TSRingToneCellView = {
         let ringToneView = TSRingToneCellView()
         ringToneView.backgroundColor = .clear
+        ringToneView.playBtn.addTarget(self, action: #selector(clickPlay), for: .touchUpInside)
         return ringToneView
     }()
     
@@ -43,16 +44,6 @@ class TSDiscoverListCell: TSBaseCollectionCell {
         return exampleView
     }()
     
-    var model:TSActionInfoModel?{
-        didSet{
-            guard let model = model else { return }
-            ringView.timeLab.text = Float(model.request.duration).floatToMinuteSecond()
-            ringView.nameLab.text = model.response.title
-            ringView.setCoverImageView(urlString: model.response.coverUrl)
-            exampleView.isHidden = model.modelType != .example
-        }
-    }
-    
     var ringModel:TSRingModel?{
         didSet{
             guard let ringModel = ringModel else { return }
@@ -67,7 +58,7 @@ class TSDiscoverListCell: TSBaseCollectionCell {
         didSet{
             ringView.isloading = isSelected
             if isSelected, self.ringView.isPlay == false{
-                TSBusinessAudioPlayer.shared.playUrlString( model?.response.musicUrl)
+                TSBusinessAudioPlayer.shared.playUrlString( ringModel?.audioUrl)
             }else{
                 TSBusinessAudioPlayer.shared.stop()
                 backgroundColor = .cardColor
@@ -107,6 +98,39 @@ class TSDiscoverListCell: TSBaseCollectionCell {
             }
         }
     }
+
+    override func dealThings() {
+        
+    }
+    override func renderView(with object: Any?, component: TSCollectionViewComponent, attributes: [String : Any]?) {
+        super.renderView(with: object, component: component, attributes: attributes)
+        if let itemModel = object as? TSColVVMItemModel{
+            if let dataModel = itemModel.dataModel as? TSRingModel{
+                ringModel = dataModel
+                
+                
+//                if let ringModel = ringModel {
+//                    if ringModel.audioPlayerFileTool == nil {
+//                        ringModel.audioPlayerFileTool = TSAudioPlayerFileTool(urlString: ringModel.audioUrl)
+//                    }
+//                }
+                
+                
+            }
+        }
+    }
+
+    deinit {
+        NotificationCenter.default.removeObserver(self)
+    }
+}
+extension TSDiscoverListCell{
+    
+    @objc func clickPlay(){
+        
+        
+    }
+    
     
     func changePlayerState(state:TSBusinessAudioPlayer.PlayerState){
         if self.isSelected == false {
@@ -124,6 +148,10 @@ class TSDiscoverListCell: TSBaseCollectionCell {
         case .play:
             self.ringView.isPlay = true
             backgroundColor = "#3C213F".uiColor
+            
+            if let ringModel = ringModel {
+                TSRecommendRintoneHistory.saveModel(model: ringModel)
+            }
         case .stop:
             self.ringView.isPlay = false
             backgroundColor = .cardColor
@@ -131,17 +159,4 @@ class TSDiscoverListCell: TSBaseCollectionCell {
             break
         }
     }
-
-    override func renderView(with object: Any?, component: TSCollectionViewComponent, attributes: [String : Any]?) {
-        super.renderView(with: object, component: component, attributes: attributes)
-        if let itemModel = object as? TSColVVMItemModel{
-            if let dataModel = itemModel.dataModel as? TSRingModel{
-                ringModel = dataModel
-            }
-        }
-    }
-    
-    deinit {
-        NotificationCenter.default.removeObserver(self)
-    }
 }

+ 12 - 0
AIRingtone/Business/TSDiscoverVC/TSDiscoverListVC/View/TSDiscoverListCellVM.swift

@@ -0,0 +1,12 @@
+//
+//  TSDiscoverListCellVM.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+
+class TSDiscoverListCellVM {
+    
+    
+    
+}

+ 24 - 0
AIRingtone/Business/TSDiscoverVC/TSDiscoverVC/TSDiscoverVC.swift

@@ -12,6 +12,14 @@ class TSDiscoverVC: TSBaseVC {
         return viewModel
     }()
     
+    lazy var redDot: UIView = {
+        let redDot = UIView()
+        redDot.backgroundColor = .themeColor
+        redDot.cornerRadius = 3
+        redDot.isHidden = true
+        return redDot
+    }()
+    
     lazy var navBarView: TSBaseNavContentBarView = {
         let navBarView = TSBaseNavContentBarView()
 
@@ -35,6 +43,16 @@ class TSDiscoverVC: TSBaseVC {
             make.height.equalTo(24)
         }
         
+        rightBtn.addSubview(redDot)
+        redDot.snp.makeConstraints { make in
+            make.trailing.equalTo(2)
+            make.top.equalTo(1)
+            make.width.equalTo(6)
+            make.height.equalTo(6)
+        }
+        
+        
+        
         return navBarView
     }()
     
@@ -95,4 +113,10 @@ class TSDiscoverVC: TSBaseVC {
             }
         }
     }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        
+        redDot.isHidden = !TSRecommendRintoneHistory.isHaveNew
+    }
 }

+ 156 - 6
AIRingtone/Business/TSDiscoverVC/TSRingDownVC/TSRingDownVC.swift

@@ -4,13 +4,11 @@
 //
 //  Created by 100Years on 2025/3/17.
 //
-
+import SwipeCellKit
 class TSRingDownVC: TSBaseVC {
     
-    
-    lazy var nullView:TSRingDownNullView = TSRingDownNullView { [weak self]  in
+    lazy var nullView:TSRingDownNullView = TSRingDownNullView{ [weak self]  in
         guard let self = self else { return }
-         
         let ringCategoryModel = TSRingCategoryModel()
         ringCategoryModel.title = "Popular"
         ringCategoryModel.cover = "http://d3a93z8fj970a4.cloudfront.net/fbae944d-9e49-46ee-a51f-78f75a1952e8"
@@ -18,19 +16,171 @@ class TSRingDownVC: TSBaseVC {
         kPushVC(target: self, modelVC: TSDiscoverListVC(ringCategoryModel: ringCategoryModel))
     }
     
+    
+    lazy var viewModel:TSRingDownVM = {
+        let viewModel = TSRingDownVM()
+        return viewModel
+    }()
+    
+    lazy var layout: UICollectionViewFlowLayout = {
+        let layout = UICollectionViewFlowLayout()
+        layout.scrollDirection = .vertical
+        layout.itemSize = CGSize(width: k_ScreenWidth-32, height: 74)
+        layout.minimumInteritemSpacing = 10.0
+        layout.minimumLineSpacing = 18.0
+        return layout
+    }()
+    
+    lazy var collectionView: UICollectionView = {
+        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
+        collectionView.delegate = self
+        collectionView.dataSource = self
+        collectionView.showsVerticalScrollIndicator = false
+        collectionView.showsHorizontalScrollIndicator = false
+        collectionView.backgroundColor = .clear
+        collectionView.contentInset = UIEdgeInsets(top: 16, left: 0, bottom: 16, right: 0)
+        collectionView.register(TSAIRintoneHistoryCell.self, forCellWithReuseIdentifier: TSAIRintoneHistoryCell.cellID)
+        if #available(iOS 11.0, *) {
+            collectionView.contentInsetAdjustmentBehavior = .never
+        }
+
+        return collectionView
+    }()
+    
+    
     override func createView() {
  
         addNormalNavBarView()
-        setPageTitle("Downloaded".localized)
+        setPageTitle("Mine".localized)
         _ = setNavigationItem("", imageName: "tutorials", direction: .right, action: #selector(clickRight))
 
-        contentView.addSubview(nullView)
+        contentView.addSubview(collectionView)
+        collectionView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
         
+        nullView.isHidden = true
+        contentView.addSubview(nullView)
     }
     
     @objc func clickRight(){
         kPushVC(target: self, modelVC: TSThemeTutorialsVC())
     }
+    
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        TSRecommendRintoneHistory.isHaveNew = false
+    }
+    
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        TSBusinessAudioPlayer.shared.stop()
+    }
+    
+    override func dealThings() {
+        updateListView()
+    }
+    
+    func updateListView(){
+        nullView.isHidden = viewModel.modelList.count > 0
+        collectionView.reloadData()
+    }
 }
 
+extension TSRingDownVC: UICollectionViewDataSource ,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout {
+    
+    public func numberOfSections(in collectionView: UICollectionView) -> Int {
+        return viewModel.modelList.count
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+        if let sectionModel = viewModel.modelList.safeObj(At: section) {
+            return sectionModel.list.count
+        }
+        return 0
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TSAIRintoneHistoryCell.cellID, for: indexPath)
+        
+        if let sectionModel = viewModel.modelList.safeObj(At: indexPath.section),
+            let itemModel = sectionModel.list.safeObj(At: indexPath.item),
+           let cell = cell as? TSAIRintoneHistoryCell
+        {
+            cell.delegate = self
+            
+            if let model = itemModel as? TSActionInfoModel {
+                cell.model = model
+            }else if let ringModel = itemModel as? TSRingModel {
+                cell.ringModel = ringModel
+            }
+            
+            cell.setRingBtn.indexPath = indexPath
+            cell.setRingBtn.addTarget(self, action: #selector(clickSetRingBtn(_ :)), for: .touchUpInside)
+        }
+        
+        return cell
+    }
+
+    @objc func clickSetRingBtn(_ btn:TSUIExpandedTouchButton){
+        let indexPath = btn.indexPath
+        if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+        let model = sectionModel.list.safeObj(At: indexPath.item){
+            if let model = model as? TSActionInfoModel {
+                _ = kPurchaseToolShared.kshareBand(needVip: model.response.vip, vc: self, urlString: model.response.musicUrl, fileName: model.response.title)
+            }else if let ringModel = model as? TSRingModel {
+                _ = kPurchaseToolShared.kshareBand(needVip: ringModel.vip, vc: self, urlString: ringModel.audioUrl, fileName: ringModel.title)
+            }
+        }
+        
+        collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .top)
+        TSBusinessAudioPlayer.shared.stop()
+    }
+    
+    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+        
+    }
+    
+}
+
+extension TSRingDownVC: SwipeCollectionViewCellDelegate {
+    
+    func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
+        guard orientation == .right else { return nil }
 
+        // 删除操作
+        let deleteAction = SwipeAction(style: .destructive, title: nil) {[weak self] action, indexPath in
+            guard let self = self else { return }
+            showCustomAlert(message: "Are you sure to delete".localized, deleteHandler:  {
+                if let sectionModel = self.viewModel.modelList.safeObj(At: indexPath.section),
+                let model = sectionModel.list.safeObj(At: indexPath.item){
+                    if let model = model as? TSRingModel {
+                        collectionView.performBatchUpdates({
+                            self.viewModel.removeModel(model: model)
+                            if sectionModel.list.count == 0 {
+                                collectionView.deleteSections([indexPath.section])
+                            }else{
+                                collectionView.deleteItems(at: [indexPath])
+                            }
+                        })
+                    }
+                }
+            })
+        }
+        
+        deleteAction.backgroundColor = "#E83E3E".uiColor
+        deleteAction.image = UIImage(named: "delete_white")
+        
+        return [deleteAction]
+    }
+    
+    func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
+        var options = SwipeOptions()
+//        options.expansionStyle = .destructive(automaticallyDelete: false) // 完全滑动时是否自动触发操作
+        options.transitionStyle = .border // 滑动动画样式
+        return options
+    }
+    
+}

+ 50 - 0
AIRingtone/Business/TSDiscoverVC/TSRingDownVC/VM/TSRingDownVM.swift

@@ -0,0 +1,50 @@
+//
+//  TSRingDownVM.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+import ObjectMapper
+
+class TSRingDownVM {
+
+    lazy var modelList: [TSAIRintoneHistoryModel] = {
+        getModelList()
+    }()
+    
+    private func getModelList()->[TSAIRintoneHistoryModel]{
+        var list = [TSAIRintoneHistoryModel]()
+        
+        if aiRintoneHistoryModel.list.count > 0 {
+            list.append(aiRintoneHistoryModel)
+        }
+
+        return list
+    }
+    
+    //保存的历史
+    lazy var aiRintoneHistoryModel: TSAIRintoneHistoryModel = {
+        let model = TSAIRintoneHistoryModel(title: "Generate History", list:TSRecommendRintoneHistory.listModelArray)
+        return model
+    }()
+    
+}
+
+extension TSRingDownVM {
+    
+    func updateRecentData() {
+        aiRintoneHistoryModel.list = TSRecommendRintoneHistory.listModelArray
+        modelList = getModelList()
+    }
+    
+    func removeModel(model:TSRingModel){
+        TSRecommendRintoneHistory.removeModel(model: model)
+        updateRecentData()
+        
+        if let pathURL = TSCommonTool.getCachedURLString(from: model.audioUrl) {
+            TSFileManagerTool.removeItem(from: pathURL)
+        }
+
+    }
+    
+}

+ 4 - 10
AIRingtone/Business/TSThemeVC/TSThemeSetVC/TSThemeSetVC.swift

@@ -22,12 +22,7 @@ class TSThemeSetVC: TSBaseVC {
         guard let self = self else { return }
         audioPlayerStateChange(state: state)
     }
-    
-    lazy var ringTool: TSBandRingTool = {
-        let ringTool = TSBandRingTool(targetVC: self)
-        return ringTool
-    }()
-    
+
     lazy var contactsTool: TSContactsTool = {
         let contactsTool = TSContactsTool(targetVC: self)
         return contactsTool
@@ -163,10 +158,9 @@ extension TSThemeSetVC {
     
     @objc func clickSetRing(){
         audioPlayer.stop()
-        if kPurchaseToolShared.kJudgeVip(externalBool: getNeedVip, vc: self){ return }//判断 vip
-        ringTool.shareBand(with: model.ringtone, fileName: model.name) { success in
-            
-        }
+//        if kPurchaseToolShared.kJudgeVip(externalBool: getNeedVip, vc: self){ return }//判断 vip
+        _ = kPurchaseToolShared.kshareBand(needVip: getNeedVip, vc: self, urlString: model.ringtone, fileName:  model.name)
+        
     }
     
     @objc func clickSetPoster(){

+ 19 - 3
AIRingtone/Common/Purchase/TSPurchaseManager/TSPurchaseTool.swift

@@ -35,9 +35,9 @@ class TSPurchaseTool {
     /// 使用一次免费次数
     func useOnceForFree(type:VipFreeNumType){
         
-        if isVip {
-            return
-        }
+//        if isVip {
+//            return
+//        }
         
         var freeNum = freeDict[type.rawValue] ?? 0
         if freeNum > 0 {
@@ -134,5 +134,21 @@ extension TSPurchaseTool{
         return false
     }
 
+    
+    func kshareBand(needVip:Bool,
+                   vc:UIViewController,
+                    urlString:String,
+                    fileName:String,
+                   closePageBlock:(()->Void)? = nil) -> Bool {
+        //判断 vip
+        if needVip,
+           kPurchaseToolShared.isVip == false
+        {
+            TSPurchaseVC.show(target: vc, closePageBlock: nil)
+            return true
+        }
+        TSBandRingTool.creatBandRingTool().shareBandVC(vc: vc, fileURLString: urlString, fileName: fileName)
+        return false
+    }
 }
 

+ 163 - 0
AIRingtone/Common/Tool/TSAudioAVPlayer.swift

@@ -0,0 +1,163 @@
+//
+//  TSAudioPlayerManager.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+
+import AVFoundation
+import Foundation
+
+
+class TSURL:Equatable{
+    static func == (lhs: TSURL, rhs: TSURL) -> Bool {
+        if lhs.url == rhs.url,
+           lhs.uuid == rhs.uuid {
+            return true
+        }
+        return false
+    }
+    
+    var uuid:String = UUID().uuidString
+    var url:URL
+    init(uuid: String, url: URL) {
+        self.uuid = uuid
+        self.url = url
+    }
+}
+
+import AVFoundation
+import Foundation
+
+let kAudioAVPlayerShared = AudioAVPlayer.shared
+class AudioAVPlayer: NSObject {
+    // MARK: - Properties
+    static let shared = AudioAVPlayer()
+    private var player: AVPlayer?
+    private var playerItem: AVPlayerItem?
+    private var timeObserverToken: Any?
+    
+    // MARK: - Callbacks
+    var onPlaybackProgress: ((Double) -> Void)? // 播放进度回调
+    var onPlaybackStateChanged: ((PlaybackState) -> Void)? // 播放状态回调
+    var onPlaybackFinished: (() -> Void)? // 播放完成回调
+    
+    // MARK: - Enums
+    enum PlaybackState {
+        case notStarted
+        case loading(Double)
+        case playing
+        case playProgress(Double)
+        case paused
+        case finished
+    }
+    
+    // MARK: - Initialization
+    private override init() {
+        super.init()
+    }
+    
+    // MARK: - Public Methods
+    /// 加载音频
+    func loadAudio(from url: URL) {
+        playerItem = AVPlayerItem(url: url)
+        player = AVPlayer(playerItem: playerItem)
+        player?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
+        addPeriodicTimeObserver()
+        addPlayerItemObservers()
+    }
+    
+    /// 播放音频
+    func play() {
+        player?.play()
+        notifyPlaybackStateChanged(.playing)
+    }
+    
+    /// 暂停音频
+    func pause() {
+        player?.pause()
+        notifyPlaybackStateChanged(.paused)
+    }
+    
+    /// 停止音频
+    func stop() {
+        player?.pause()
+        player?.seek(to: CMTime.zero)
+        notifyPlaybackStateChanged(.notStarted)
+    }
+    
+    /// 跳转到指定时间
+    func seek(to time: Double) {
+        let cmTime = CMTime(seconds: time, preferredTimescale: 1)
+        player?.seek(to: cmTime)
+    }
+    
+    // MARK: - Private Methods
+    /// 添加播放进度监听
+    private func addPeriodicTimeObserver() {
+        let interval = CMTime(seconds: 0.5, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
+        timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self] time in
+            guard let self = self else { return }
+            let currentTime = CMTimeGetSeconds(time)
+            let duration = CMTimeGetSeconds(self.playerItem?.duration ?? CMTime.zero)
+            let progress = duration > 0 ? currentTime / duration : 0
+            self.onPlaybackProgress?(progress)
+        }
+    }
+    
+    /// 添加播放状态监听
+    private func addPlayerItemObservers() {
+        NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEndTime), name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
+    }
+    
+    /// 播放完成
+    @objc private func playerItemDidPlayToEndTime() {
+        notifyPlaybackStateChanged(.finished)
+        onPlaybackFinished?()
+    }
+    
+    /// 监听播放器状态
+    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
+        if keyPath == "status", player?.status == .readyToPlay {
+            notifyPlaybackStateChanged(.notStarted)
+        }
+    }
+    
+    /// 通知播放状态变化
+    private func notifyPlaybackStateChanged(_ state: PlaybackState) {
+        DispatchQueue.main.async {
+            self.onPlaybackStateChanged?(state)
+        }
+    }
+    
+    // MARK: - Deinit
+    deinit {
+        player?.removeObserver(self, forKeyPath: "status")
+        if let token = timeObserverToken {
+            player?.removeTimeObserver(token)
+        }
+        NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
+    }
+}
+
+/*
+ 
+ 
+ let audioURL = URL(string: "https://example.com/audio.mp3")!
+ AudioPlayer.shared.loadAudio(from: audioURL)
+
+ AudioPlayer.shared.onPlaybackStateChanged = { state in
+     print("播放状态: \(state)")
+ }
+
+ AudioPlayer.shared.onPlaybackProgress = { progress in
+     print("播放进度: \(progress)")
+ }
+
+ AudioPlayer.shared.onPlaybackFinished = {
+     print("播放完成")
+ }
+
+ AudioPlayer.shared.play()
+ 
+ */

+ 63 - 0
AIRingtone/Common/Tool/TSAudioPlayerFileTool.swift

@@ -0,0 +1,63 @@
+//
+//  TSAudioPlayer+Ex.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+
+class TSAudioPlayerFileTool {
+    
+    
+    var downloadOp:DownloadOperation?
+    var urlString:String
+    init(urlString:String) {
+        self.urlString = urlString
+
+    }
+    
+    
+    func playAudio(urlString:String){
+        guard let url = URL(string: urlString) else { return }
+        
+        // 检查本地是否已存在文件
+        if let localFilePath = DownloadManager.localFilePath(for: url) {
+            print("File already exists at: \(localFilePath)")
+            kAudioAVPlayerShared.loadAudio(from: localFilePath)
+            kAudioAVPlayerShared.play()
+        } else {
+            // 开始下载任务
+            downloadOp = DownloadManager.shared.downloadFile(from: url)
+
+            // 设置进度回调
+            downloadOp?.progressHandler = { progress in
+                print("Download progress: \(progress)")
+                
+                kAudioAVPlayerShared.onPlaybackStateChanged?(.loading(progress))
+            }
+
+            // 设置完成回调
+            downloadOp?.completionHandler = { tempURL, error in
+                if let tempURL = tempURL {
+                    print("Download completed, file saved at: \(tempURL)")
+                    kAudioAVPlayerShared.loadAudio(from: tempURL)
+                    kAudioAVPlayerShared.play()
+                    
+                } else if let error = error {
+                    print("Download failed: \(error)")
+                }
+            }
+
+            // 开始下载
+            downloadOp?.startDownload()
+        }
+    }
+    
+    func stopAudio(urlString:String){
+        
+        if let downloadOp = downloadOp {
+            // 暂停下载
+            downloadOp.pause()
+            kAudioAVPlayerShared.stop()
+        }
+    }
+}

+ 83 - 7
AIRingtone/Common/Tool/TSBandRingTool/TSBandRingTool.swift

@@ -7,17 +7,25 @@
 import AVKit
 import AVFoundation
 class TSBandRingTool:NSObject {
-    
+    static var shared = TSBandRingTool()
     private var tutorialPlayer: AVPlayer?
     private var pictureController: AVPictureInPictureController?
     private lazy var playContentView = UIView(frame: UIScreen.main.bounds)
     private lazy var audioConvertTool = AudioTool()
     
     weak var targetVC:UIViewController?
-    init(targetVC: UIViewController) {
-        self.targetVC = targetVC
+//    init(targetVC: UIViewController) {
+//        self.targetVC = targetVC
+//    }
+    
+    static func creatBandRingTool() -> TSBandRingTool{
+        TSBandRingTool.shared = TSBandRingTool()
+        return TSBandRingTool.shared
     }
     
+    deinit {
+        dePrint("TSBandRingTool deinit")
+    }
     func checkGarageBandInstallation() -> Bool {
         // GarageBand 的 URL Scheme
         let garageBandScheme = "garageband://"
@@ -41,8 +49,9 @@ class TSBandRingTool:NSObject {
             return false
         }
     }
-
-    func shareBand(with fileURLString: String,
+    
+    func shareBandVC(vc:UIViewController,
+        fileURLString: String,
                    fileName:String?,
                    completion: ((Bool) -> Void)? = nil) {
         
@@ -50,7 +59,7 @@ class TSBandRingTool:NSObject {
             completion?(false)
             return
         }
-        
+        self.targetVC = vc
         if fileURLString.contains("http") {
             TSLoadingAnimation.showLoading(in: self.targetVC?.view)
             TSCommonTool.downloadAndCacheFile(from: fileURLString) { path, error in
@@ -78,6 +87,40 @@ class TSBandRingTool:NSObject {
        
     }
     
+//    func shareBand(with fileURLString: String,
+//                   fileName:String?,
+//                   completion: ((Bool) -> Void)? = nil) {
+//        
+//        if checkGarageBandInstallation() == false {
+//            completion?(false)
+//            return
+//        }
+//        
+//        if fileURLString.contains("http") {
+//            TSLoadingAnimation.showLoading(in: self.targetVC?.view)
+//            TSCommonTool.downloadAndCacheFile(from: fileURLString) { path, error in
+//                TSLoadingAnimation.hideLoading()
+//                if let path = path,let url = URL(string: path) {
+//                    self.createBand(with: url, fileName: fileName) { bandURL in
+//                        if let url = bandURL {
+//                            completion?(true)
+//                            self.shareRing(fileUrl: url)
+//                        }else{
+//                            completion?(false)
+//                            dePrint("Failed to set, please try another")
+//                        }
+//                    }
+//                }else{
+//                    dePrint("downloadAndCacheFile = \(error?.localizedDescription)")
+//                    completion?(false)
+//                }
+//            }
+//        }else{
+//            dePrint("ringtone no http")
+//            completion?(false)
+//        }
+//    }
+    
     // 创建用于分享库乐队的band文件
     func createBand(with fileURL: URL?, fileName: String?,
                     completion: ((URL?) -> Void)?) {
@@ -102,14 +145,47 @@ class TSBandRingTool:NSObject {
             self.showTutorialVideo()
             
             let vc = UIActivityViewController(activityItems: [fileUrl], applicationActivities: nil)
+            // 排除不需要的活动类型
+            vc.excludedActivityTypes = [
+                .postToFacebook,
+                .postToTwitter,
+                .postToWeibo,
+                .message,
+                .mail,
+                .print,
+                .copyToPasteboard,
+                .assignToContact,
+                .saveToCameraRoll,
+                .addToReadingList,
+                .postToFlickr,
+                .postToVimeo,
+                .postToTencentWeibo,
+                .airDrop,
+                .openInIBooks
+            ]
             self.targetVC?.present(vc, animated: true, completion: {
                 self.tryStartPictureInPicture()
             })
+            
+//            self.shareFileToGarageBand(fileURL: fileUrl)
         }
     }
     
     
-
+    func shareFileToGarageBand(fileURL: URL) {
+        // 假设你有一个文件的 URL
+        // 创建 GarageBand 的 URL Scheme
+        let garageBandURL = URL(string: "garageband://mobilegarageband203.audiobus://import?url=\(fileURL.absoluteString)")!
+        
+        // 检查是否可以打开 GarageBand
+        if UIApplication.shared.canOpenURL(garageBandURL) {
+            // 打开 GarageBand
+            UIApplication.shared.open(garageBandURL, options: [:], completionHandler: nil)
+        } else {
+            // 如果 GarageBand 未安装,提示用户
+            print("GarageBand 未安装")
+        }
+    }
     
 }
 

+ 4 - 0
AIRingtone/Common/Tool/TSBusinessAudioPlayer.swift

@@ -94,7 +94,11 @@ class TSBusinessAudioPlayer {
                     self.stop()
                 }
             }
+        }else{
+            self.stop()
         }
+        
+
     }
     
     

+ 214 - 0
AIRingtone/Common/Tool/TSDownloadManager.swift

@@ -0,0 +1,214 @@
+//
+//  TSDownloadManager.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/18.
+//
+import Foundation
+
+// MARK: - DownloadManager
+/// 下载管理器,负责生成和管理下载任务
+class DownloadManager {
+    static let shared = DownloadManager()
+    
+    private var maxConcurrentDownloads: Int
+    private let downloadQueue: OperationQueue
+    private var activeDownloads: [URL: DownloadOperation] = [:]
+    private var expirationInterval: TimeInterval = 7 * 24 * 60 * 60 // 默认 7 天
+    
+    private init() {
+        self.maxConcurrentDownloads = 3 // 默认最大并发下载数为 3
+        self.downloadQueue = OperationQueue()
+        self.downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads
+    }
+    
+    /// 设置最大并发下载数
+    /// - Parameter count: 最大并发下载数
+    func setMaxConcurrentDownloads(_ count: Int) {
+        maxConcurrentDownloads = count
+        downloadQueue.maxConcurrentOperationCount = count
+    }
+    
+    /// 设置缓存文件的过期时长
+    /// - Parameter interval: 过期时长(秒)
+    func setExpirationInterval(_ interval: TimeInterval) {
+        expirationInterval = interval
+    }
+    
+    /// 创建下载任务
+    /// - Parameter url: 下载文件的 URL
+    /// - Returns: 返回 DownloadOperation,用于控制下载任务
+    func downloadFile(from url: URL) -> DownloadOperation {
+        if let downloadOperation = activeDownloads[url] {
+            return downloadOperation
+        }
+        
+        let downloadOperation = DownloadOperation(url: url)
+        activeDownloads[url] = downloadOperation
+        downloadQueue.addOperation(downloadOperation)
+        return downloadOperation
+    }
+    
+    /// 清理过期的缓存文件
+    func cleanExpiredFiles() {
+        let fileManager = FileManager.default
+        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
+        let expirationDate = Date().addingTimeInterval(-expirationInterval)
+        
+        if let files = try? fileManager.contentsOfDirectory(at: cacheDirectory, includingPropertiesForKeys: [.creationDateKey], options: .skipsHiddenFiles) {
+            for file in files {
+                if let attributes = try? fileManager.attributesOfItem(atPath: file.path),
+                   let creationDate = attributes[.creationDate] as? Date,
+                   creationDate < expirationDate {
+                    try? fileManager.removeItem(at: file)
+                }
+            }
+        }
+    }
+    
+    /// 获取本地文件路径(如果存在)
+    /// - Parameter url: 文件的 URL
+    /// - Returns: 本地文件路径(如果存在),否则返回 nil
+    static func localFilePath(for url: URL) -> URL? {
+        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+        let fileName = url.lastPathComponent
+        let fileURL = cacheDirectory.appendingPathComponent(fileName)
+        if FileManager.default.fileExists(atPath: fileURL.path) {
+            return fileURL
+        }
+        return nil
+    }
+    
+    /// 清理所有下载任务
+    func cleanAllTasks() {
+        for (url, downloadOperation) in activeDownloads {
+            downloadOperation.cancel()
+            activeDownloads[url] = nil
+        }
+    }
+    
+    /// 清理某个下载任务
+    /// - Parameter url: 下载任务的 URL
+    func cleanTask(for url: URL) {
+        if let downloadOperation = activeDownloads[url] {
+            downloadOperation.cancel()
+            activeDownloads[url] = nil
+        }
+    }
+}
+
+// MARK: - DownloadOperation
+/// 下载任务,继承自 Operation
+class DownloadOperation: Operation {
+    private let url: URL
+    private var downloadTask: URLSessionDownloadTask?
+    private var resumeData: Data?
+    private var isPaused: Bool = false
+    
+    /// 下载进度回调
+    var progressHandler: ((Double) -> Void)?
+    
+    /// 下载完成回调
+    var completionHandler: ((URL?, Error?) -> Void)?
+    
+    init(url: URL) {
+        self.url = url
+    }
+    
+    /// 开始下载
+    func startDownload() {
+        // 检查本地是否已存在文件
+        if let localFilePath = DownloadManager.localFilePath(for: url) {
+            completionHandler?(localFilePath, nil)
+            return
+        }
+        
+        // 如果没有本地文件,则开始下载
+        let semaphore = DispatchSemaphore(value: 0)
+        
+        // 检查是否有断点续传数据
+        if let resumeData = loadResumeData() {
+            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+            downloadTask = session.downloadTask(withResumeData: resumeData)
+        } else {
+            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+            downloadTask = session.downloadTask(with: url)
+        }
+        
+        downloadTask?.resume()
+        semaphore.wait()
+    }
+    
+    /// 暂停下载
+    func pause() {
+        isPaused = true
+        downloadTask?.cancel { [weak self] resumeData in
+            if let resumeData = resumeData {
+                self?.saveResumeData(resumeData)
+            }
+        }
+    }
+    
+    /// 恢复下载
+    func resume() {
+        isPaused = false
+        if let resumeData = loadResumeData() {
+            let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
+            downloadTask = session.downloadTask(withResumeData: resumeData)
+            downloadTask?.resume()
+        }
+    }
+    
+    /// 取消下载
+    override func cancel() {
+        downloadTask?.cancel()
+        super.cancel()
+    }
+    
+    /// 保存断点续传数据到文件
+    private func saveResumeData(_ data: Data) {
+        let resumeDataURL = getResumeDataURL()
+        try? data.write(to: resumeDataURL)
+    }
+    
+    /// 从文件加载断点续传数据
+    private func loadResumeData() -> Data? {
+        let resumeDataURL = getResumeDataURL()
+        return try? Data(contentsOf: resumeDataURL)
+    }
+    
+    /// 获取断点续传数据的存储路径
+    private func getResumeDataURL() -> URL {
+        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+        let resumeDataFileName = url.lastPathComponent + ".resumeData"
+        return cacheDirectory.appendingPathComponent(resumeDataFileName)
+    }
+}
+
+// MARK: - URLSessionDownloadDelegate
+extension DownloadOperation: URLSessionDownloadDelegate {
+    /// 下载进度更新
+    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
+        let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
+        progressHandler?(progress)
+    }
+    
+    /// 下载完成
+    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
+        if !isPaused {
+            // 将文件移动到缓存目录
+            let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
+            let fileName = url.lastPathComponent
+            let destinationURL = cacheDirectory.appendingPathComponent(fileName)
+            try? FileManager.default.moveItem(at: location, to: destinationURL)
+            completionHandler?(destinationURL, nil)
+        }
+    }
+    
+    /// 任务完成(包括成功或失败)
+    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+        if let error = error {
+            completionHandler?(nil, error)
+        }
+    }
+}