Browse Source

铃声生成和设置流程基本开发完毕

100Years 1 tháng trước cách đây
mục cha
commit
581828d3c6
28 tập tin đã thay đổi với 452 bổ sung164 xóa
  1. 4 0
      AIRingtone.xcodeproj/project.pbxproj
  2. 22 0
      AIRingtone/Assets.xcassets/Theme/icon/sound_loading_icon.imageset/Contents.json
  3. BIN
      AIRingtone/Assets.xcassets/Theme/icon/sound_loading_icon.imageset/sound_loading_icon@2x.png
  4. BIN
      AIRingtone/Assets.xcassets/Theme/icon/sound_loading_icon.imageset/sound_loading_icon@3x.png
  5. 19 0
      AIRingtone/Business/Data/TSUserDefaultData.swift
  6. 18 3
      AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/Model/TSGeneralPicModel.swift
  7. 0 2
      AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/TSTextGeneralPicVC.swift
  8. 14 25
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift
  9. 37 3
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift
  10. 11 8
      AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/ViewModel/TSAIRintoneVM.swift
  11. 5 4
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC+Event.swift
  12. 31 9
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift
  13. 36 45
      AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVM.swift
  14. 4 3
      AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/TSTextGeneralRintoneVC.swift
  15. 15 5
      AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/VM/TSTextGeneralRintoneVM.swift
  16. 1 1
      AIRingtone/Business/TSSetingVC/SetingVC/TSSetingModel.swift
  17. 2 2
      AIRingtone/Business/TSSetingVC/SetingVC/TSSetingVC.swift
  18. 1 0
      AIRingtone/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift
  19. 6 0
      AIRingtone/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift
  20. 37 11
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/TSThemeBrowseVC.swift
  21. 5 36
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/VM/TSThemeBrowseVM.swift
  22. 22 0
      AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/View/TSTBBtnView.swift
  23. 1 0
      AIRingtone/Business/VIewTool/TSRingToneCellView.swift
  24. 1 0
      AIRingtone/Common/Ex/Notification+TSEx.swift
  25. 1 0
      AIRingtone/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift
  26. 22 7
      AIRingtone/Common/NetworkManager/TSNetWork/TSNetworkManager+Loading.swift
  27. 108 0
      AIRingtone/Common/Tool/TSBusinessAudioPlayer.swift
  28. 29 0
      AIRingtone/Common/Tool/TSLoadingAnimation.swift

+ 4 - 0
AIRingtone.xcodeproj/project.pbxproj

@@ -46,6 +46,7 @@
 		A8272E9B2D7A8F1000F1C814 /* TSGeneralRintoneVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272E9A2D7A8F0E00F1C814 /* TSGeneralRintoneVC.swift */; };
 		A8272E9B2D7A8F1000F1C814 /* TSGeneralRintoneVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272E9A2D7A8F0E00F1C814 /* TSGeneralRintoneVC.swift */; };
 		A8272E9D2D7A8F4600F1C814 /* TSGeneralRintoneVC+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272E9C2D7A8F3A00F1C814 /* TSGeneralRintoneVC+Event.swift */; };
 		A8272E9D2D7A8F4600F1C814 /* TSGeneralRintoneVC+Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272E9C2D7A8F3A00F1C814 /* TSGeneralRintoneVC+Event.swift */; };
 		A8272E9F2D7A8F6500F1C814 /* TSGeneralRintoneVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272E9E2D7A8F6000F1C814 /* TSGeneralRintoneVM.swift */; };
 		A8272E9F2D7A8F6500F1C814 /* TSGeneralRintoneVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272E9E2D7A8F6000F1C814 /* TSGeneralRintoneVM.swift */; };
+		A8272EBB2D7AFD0F00F1C814 /* TSBusinessAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8272EBA2D7AFD0D00F1C814 /* TSBusinessAudioPlayer.swift */; };
 		A83F871D2D79409B00D29B1B /* TSUserDefaultData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83F871C2D79409300D29B1B /* TSUserDefaultData.swift */; };
 		A83F871D2D79409B00D29B1B /* TSUserDefaultData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83F871C2D79409300D29B1B /* TSUserDefaultData.swift */; };
 		A83F87202D794FF000D29B1B /* TSAIPhotoImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83F871F2D794FE600D29B1B /* TSAIPhotoImageCell.swift */; };
 		A83F87202D794FF000D29B1B /* TSAIPhotoImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83F871F2D794FE600D29B1B /* TSAIPhotoImageCell.swift */; };
 		A83F87222D7953C000D29B1B /* Notification+TSEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83F87212D7953BA00D29B1B /* Notification+TSEx.swift */; };
 		A83F87222D7953C000D29B1B /* Notification+TSEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = A83F87212D7953BA00D29B1B /* Notification+TSEx.swift */; };
@@ -151,6 +152,7 @@
 		A8272E9A2D7A8F0E00F1C814 /* TSGeneralRintoneVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralRintoneVC.swift; sourceTree = "<group>"; };
 		A8272E9A2D7A8F0E00F1C814 /* TSGeneralRintoneVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralRintoneVC.swift; sourceTree = "<group>"; };
 		A8272E9C2D7A8F3A00F1C814 /* TSGeneralRintoneVC+Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSGeneralRintoneVC+Event.swift"; sourceTree = "<group>"; };
 		A8272E9C2D7A8F3A00F1C814 /* TSGeneralRintoneVC+Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TSGeneralRintoneVC+Event.swift"; sourceTree = "<group>"; };
 		A8272E9E2D7A8F6000F1C814 /* TSGeneralRintoneVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralRintoneVM.swift; sourceTree = "<group>"; };
 		A8272E9E2D7A8F6000F1C814 /* TSGeneralRintoneVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSGeneralRintoneVM.swift; sourceTree = "<group>"; };
+		A8272EBA2D7AFD0D00F1C814 /* TSBusinessAudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBusinessAudioPlayer.swift; sourceTree = "<group>"; };
 		A83F871C2D79409300D29B1B /* TSUserDefaultData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSUserDefaultData.swift; sourceTree = "<group>"; };
 		A83F871C2D79409300D29B1B /* TSUserDefaultData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSUserDefaultData.swift; sourceTree = "<group>"; };
 		A83F871F2D794FE600D29B1B /* TSAIPhotoImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIPhotoImageCell.swift; sourceTree = "<group>"; };
 		A83F871F2D794FE600D29B1B /* TSAIPhotoImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIPhotoImageCell.swift; sourceTree = "<group>"; };
 		A83F87212D7953BA00D29B1B /* Notification+TSEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+TSEx.swift"; sourceTree = "<group>"; };
 		A83F87212D7953BA00D29B1B /* Notification+TSEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+TSEx.swift"; sourceTree = "<group>"; };
@@ -333,6 +335,7 @@
 		A80EDEBA2D718CEA003CD332 /* Tool */ = {
 		A80EDEBA2D718CEA003CD332 /* Tool */ = {
 			isa = PBXGroup;
 			isa = PBXGroup;
 			children = (
 			children = (
+				A8272EBA2D7AFD0D00F1C814 /* TSBusinessAudioPlayer.swift */,
 				A868A8F02D77081B00F6D884 /* TSContactsTool.swift */,
 				A868A8F02D77081B00F6D884 /* TSContactsTool.swift */,
 				A868A8DC2D76F90E00F6D884 /* TSBandRingTool */,
 				A868A8DC2D76F90E00F6D884 /* TSBandRingTool */,
 				A868A8D42D76E40E00F6D884 /* TSSetContactAvatar.swift */,
 				A868A8D42D76E40E00F6D884 /* TSSetContactAvatar.swift */,
@@ -996,6 +999,7 @@
 				A868A8A32D7560B900F6D884 /* TSViewTool.swift in Sources */,
 				A868A8A32D7560B900F6D884 /* TSViewTool.swift in Sources */,
 				A83F871D2D79409B00D29B1B /* TSUserDefaultData.swift in Sources */,
 				A83F871D2D79409B00D29B1B /* TSUserDefaultData.swift in Sources */,
 				A868A9112D784CFB00F6D884 /* TSGeneralPicVC.swift in Sources */,
 				A868A9112D784CFB00F6D884 /* TSGeneralPicVC.swift in Sources */,
+				A8272EBB2D7AFD0F00F1C814 /* TSBusinessAudioPlayer.swift in Sources */,
 				A868A8A42D7560B900F6D884 /* TSCommonloadingView.swift in Sources */,
 				A868A8A42D7560B900F6D884 /* TSCommonloadingView.swift in Sources */,
 				A868A89A2D75505E00F6D884 /* TSThemeBannerCell.swift in Sources */,
 				A868A89A2D75505E00F6D884 /* TSThemeBannerCell.swift in Sources */,
 				A80EDF182D7193EE003CD332 /* TSTutorialsVC.swift in Sources */,
 				A80EDF182D7193EE003CD332 /* TSTutorialsVC.swift in Sources */,

+ 22 - 0
AIRingtone/Assets.xcassets/Theme/icon/sound_loading_icon.imageset/Contents.json

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

BIN
AIRingtone/Assets.xcassets/Theme/icon/sound_loading_icon.imageset/sound_loading_icon@2x.png


BIN
AIRingtone/Assets.xcassets/Theme/icon/sound_loading_icon.imageset/sound_loading_icon@3x.png


+ 19 - 0
AIRingtone/Business/Data/TSUserDefaultData.swift

@@ -44,3 +44,22 @@ class TSPhotoHistory{
         }
         }
     }
     }
 }
 }
+
+//AI铃声历史记录
+class TSAIRintoneHistory{
+    @UserDefault(key: "kRintoneTextMusicHistoryListString", defaultValue: "")
+    static private var historyString: String
+    static var listModelArray: [TSActionInfoModel] = {
+        if let listModelArray = Mapper<TSActionInfoModel>().mapArray(JSONString: historyString){
+            return listModelArray
+        }
+        return []
+    }()
+    
+    static func saveModel(model:TSActionInfoModel){
+        listModelArray.insert(model, at: 0)
+        if let jsonString = listModelArray.toJSONString() {
+            historyString = jsonString
+        }
+    }
+}

+ 18 - 3
AIRingtone/Business/TSAIPhotoVC/TSGeneralPicVC/Model/TSGeneralPicModel.swift

@@ -55,22 +55,37 @@ class TSActionInfoModel: TSBaseModel {
 class TSActionInfoRequestModel : TSBaseModel {
 class TSActionInfoRequestModel : TSBaseModel {
     var prompt:String = ""
     var prompt:String = ""
     var promptSort:String = ""
     var promptSort:String = ""
+    //生成图用的
     var width:Int = 0
     var width:Int = 0
     var height:Int = 0
     var height:Int = 0
+    //升成音乐用的
+    var duration:Int = 0
     override func mapping(map: ObjectMapper.Map) {
     override func mapping(map: ObjectMapper.Map) {
         prompt              <- map["prompt"]
         prompt              <- map["prompt"]
         promptSort          <- map["promptSort"]
         promptSort          <- map["promptSort"]
         width               <- map["width"]
         width               <- map["width"]
         height              <- map["height"]
         height              <- map["height"]
+        duration            <- map["duration"]
     }
     }
 }
 }
 
 
 class TSActionInfoResponseModel : TSBaseModel {
 class TSActionInfoResponseModel : TSBaseModel {
-    var resultUrl:String = ""
     var vip:Bool = false
     var vip:Bool = false
+    //生成图片
+    var resultUrl:String = ""
+
+    //生成音乐用的
+    var coverUrl:String = ""
+    var title:String = ""
+    var musicUrl:String = ""
+    
     override func mapping(map: ObjectMapper.Map) {
     override func mapping(map: ObjectMapper.Map) {
-        resultUrl           <- map["resultUrl"]
-        vip                  <- map["vip"]
+        resultUrl          <- map["resultUrl"]
+        vip                <- map["vip"]
+        
+        coverUrl           <- map["coverUrl"]
+        title              <- map["title"]
+        musicUrl           <- map["musicUrl"]
     }
     }
 }
 }
 
 

+ 0 - 2
AIRingtone/Business/TSAIPhotoVC/TSTextGeneralPicVC/TSTextGeneralPicVC.swift

@@ -114,8 +114,6 @@ class TSTextGeneralPicVC: TSBaseVC {
     
     
     override func dealThings() {
     override func dealThings() {
         
         
-        promptTextView.customTextView.text = "1234567"
-        viewModel.promptText = "1234567"
 //        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
 //        NotificationCenter.default.addObserver(self, selector: #selector(vipInfoChanged), name: .kPurchaseDidChanged, object: nil)
     }
     }
     
     

+ 14 - 25
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/TSAIRintoneVC.swift

@@ -5,6 +5,7 @@
 //  Created by 100Years on 2025/2/27.
 //  Created by 100Years on 2025/2/27.
 //
 //
 import SwipeCellKit
 import SwipeCellKit
+
 class TSAIRintoneVC: TSBaseVC {
 class TSAIRintoneVC: TSBaseVC {
     
     
     lazy var viewModel : TSAIRintoneVM = {
     lazy var viewModel : TSAIRintoneVM = {
@@ -52,8 +53,14 @@ class TSAIRintoneVC: TSBaseVC {
         return collectionView
         return collectionView
     }()
     }()
     
     
+
     lazy var generalRintoneVC: TSTextGeneralRintoneVC = {
     lazy var generalRintoneVC: TSTextGeneralRintoneVC = {
         let generalRintoneVC = TSTextGeneralRintoneVC()
         let generalRintoneVC = TSTextGeneralRintoneVC()
+        generalRintoneVC.reloadUIBlock = { [weak self]  in
+            guard let self = self else { return }
+            viewModel.setRecentData()
+            updateListView()
+        }
         return generalRintoneVC
         return generalRintoneVC
     }()
     }()
 
 
@@ -72,23 +79,8 @@ class TSAIRintoneVC: TSBaseVC {
             make.edges.equalToSuperview()
             make.edges.equalToSuperview()
         }
         }
         
         
-        addredView()
-        kDelayOnMainThread(0.5){
-//            self.addredView()
-//            let vcViewH = 464.0//generalRintoneVC.viewH
-//            self.generalRintoneVC.view.frame = CGRectMake(0, -vcViewH, k_ScreenWidth, vcViewH)
-        }
-
-    }
-    
-    func addredView() {
-//        let vcViewH = 464.0
-//        let redView = UIView(frame: CGRectMake(0, -vcViewH, k_ScreenWidth, vcViewH))
-//        redView.backgroundColor = .red
-//        collectionView.addSubview(redView)
-//        collectionView.contentInset = UIEdgeInsets(top: vcViewH, left: 0, bottom: 0, right: 0)
-        let vcViewH = 464.0//generalRintoneVC.viewH
-//        generalRintoneVC.view.frame = CGRectMake(0, -vcViewH, k_ScreenWidth, vcViewH)
+        
+        let vcViewH = generalRintoneVC.viewH
         collectionView.addSubview(generalRintoneVC.view)
         collectionView.addSubview(generalRintoneVC.view)
         generalRintoneVC.view.snp.makeConstraints { make in
         generalRintoneVC.view.snp.makeConstraints { make in
             make.top.equalTo(-vcViewH)
             make.top.equalTo(-vcViewH)
@@ -97,25 +89,22 @@ class TSAIRintoneVC: TSBaseVC {
         }
         }
         
         
         collectionView.contentInset = UIEdgeInsets(top: vcViewH, left: 0, bottom: 0, right: 0)
         collectionView.contentInset = UIEdgeInsets(top: vcViewH, left: 0, bottom: 0, right: 0)
+
     }
     }
-    
+
     override func viewWillAppear(_ animated: Bool) {
     override func viewWillAppear(_ animated: Bool) {
         //
         //
     }
     }
+    
     override func dealThings() {
     override func dealThings() {
-//        updateListView()
+        updateListView()
     }
     }
     
     
     func updateListView(){
     func updateListView(){
         collectionView.reloadData()
         collectionView.reloadData()
     }
     }
     
     
-    override func viewDidLayoutSubviews() {
-        super.viewDidLayoutSubviews()
-//        print("View did layout subviews")
-//        let vcViewH = 464.0//generalRintoneVC.viewH
-//        generalRintoneVC.view.frame = CGRectMake(0, -vcViewH, k_ScreenWidth, vcViewH)
-    }
+
 }
 }
 
 
 extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate {
 extension TSAIRintoneVC: UICollectionViewDataSource ,UICollectionViewDelegate {

+ 37 - 3
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/View/TSAIRintoneHistoryCell.swift

@@ -33,16 +33,23 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
     
     
     var model:TSActionInfoModel?{
     var model:TSActionInfoModel?{
         didSet{
         didSet{
-
+            guard let model = model else { return }
+            ringView.timeLab.text = Float(model.request.duration).floatToMinuteSecond()
+            ringView.nameLab.text = model.response.title
+            ringView.coverImageView.setAsyncImage(urlString: model.response.coverUrl,contentMode: .scaleAspectFill)
         }
         }
     }
     }
     
     
     override var isSelected: Bool{
     override var isSelected: Bool{
         didSet{
         didSet{
             backgroundColor = isSelected ? "#3C213F".uiColor :.cardColor
             backgroundColor = isSelected ? "#3C213F".uiColor :.cardColor
-            
             ringView.isloading = isSelected
             ringView.isloading = isSelected
-            
+
+            if isSelected, self.ringView.isPlay == false{
+                TSBusinessAudioPlayer.shared.playRingtone(ringtone: model?.response.musicUrl)
+            }else{
+                TSBusinessAudioPlayer.shared.stop()
+            }
         }
         }
     }
     }
     
     
@@ -62,8 +69,35 @@ class TSAIRintoneHistoryCell: SwipeCollectionViewCell {
             make.trailing.equalTo(-16)
             make.trailing.equalTo(-16)
             make.width.height.equalTo(20)
             make.width.height.equalTo(20)
         }
         }
+        
+        NotificationCenter.default.addObserver(forName: .kReloadUIData, object: nil, queue: nil) { notification in
+            if let userInfo = notification.userInfo as? [String: TSBusinessAudioPlayer.PlayerState], let state = userInfo["PlayerState"] {
+                if self.isSelected == false {
+                    self.ringView.isPlay = false
+                    return
+                }
+                
+                switch state {
+                case .loading(let progress):
+                    if progress == 0.0 {
+                        self.ringView.isloading = true
+                    }else if progress == 1.0 {
+                        self.ringView.isloading = false
+                    }
+                case .play:
+                    self.ringView.isPlay = true
+                case .stop:
+                    self.ringView.isPlay = false
+                default:
+                    break
+                }
+            }
+        }
     }
     }
 
 
+    deinit {
+        NotificationCenter.default.removeObserver(self)
+    }
 }
 }
 
 
 class TSAIRintoneHistorySectionHeaderView: UICollectionReusableView {
 class TSAIRintoneHistorySectionHeaderView: UICollectionReusableView {

+ 11 - 8
AIRingtone/Business/TSAIRintoneVC/TSAIRintoneVC/ViewModel/TSAIRintoneVM.swift

@@ -6,18 +6,21 @@
 //
 //
 
 
 class TSAIRintoneVM {
 class TSAIRintoneVM {
-    
+    lazy var aiRintoneHistoryModel: TSAIRintoneHistoryModel = {
+        let model = TSAIRintoneHistoryModel(title: "Generate History", list:TSAIRintoneHistory.listModelArray)
+        return model
+    }()
     
     
     lazy var historyModelList: [TSAIRintoneHistoryModel] = {
     lazy var historyModelList: [TSAIRintoneHistoryModel] = {
-        var historyModelList =  [TSAIRintoneHistoryModel]()
-        
-        let model = TSAIRintoneHistoryModel(title: "Generate History", list: [TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel(),TSActionInfoModel()])
-        historyModelList.append(model)
-        
-        return historyModelList
+        if aiRintoneHistoryModel.list.count > 0 {
+            return [aiRintoneHistoryModel]
+        }
+        return []
     }()
     }()
     
     
-    
+    func setRecentData() {
+        aiRintoneHistoryModel.list = TSAIRintoneHistory.listModelArray
+    }
 }
 }
 
 
 
 

+ 5 - 4
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC+Event.swift

@@ -87,15 +87,16 @@ extension TSGeneralRintoneVC {
         regenerateBtn.isHidden = false
         regenerateBtn.isHidden = false
         
         
         
         
-        TSCommonTool.downloadAndCacheFile(from: "") { [weak self] path, error in
-            guard let self = self else { return }
-        
-        }
+//        TSCommonTool.downloadAndCacheFile(from: "") { [weak self] path, error in
+//            guard let self = self else { return }
+//        
+//        }
         
         
 //        kPurchaseDefault.useOnceForFree(type: .generatePic)
 //        kPurchaseDefault.useOnceForFree(type: .generatePic)
         
         
         if let model = infoModel {
         if let model = infoModel {
             complete(model)
             complete(model)
+            setRingViewData(model: model)
         }
         }
     }
     }
 }
 }

+ 31 - 9
AIRingtone/Business/TSAIRintoneVC/TSGeneralRintoneVC/TSGeneralRintoneVC.swift

@@ -23,13 +23,18 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
         return viewModel
         return viewModel
     }()
     }()
     
     
+    lazy var audioPlayer = TSBusinessAudioPlayer{ [weak self] state in
+        guard let self = self else { return }
+        audioPlayerStateChange(state: state)
+    }
+    
     @MainActor required init?(coder: NSCoder) {
     @MainActor required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
         fatalError("init(coder:) has not been implemented")
     }
     }
     
     
     lazy var ringView: TSRingToneCellView = {
     lazy var ringView: TSRingToneCellView = {
         let ringToneView = TSRingToneCellView()
         let ringToneView = TSRingToneCellView()
-        ringToneView.playBtn.addTarget(self, action: #selector(clickPlay), for: .touchUpInside)
+        ringToneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickPlay)))
         return ringToneView
         return ringToneView
     }()
     }()
     
     
@@ -88,8 +93,11 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
     }
     }
     
     
     @objc func clickPlay(){
     @objc func clickPlay(){
-        ringView.isPlay = !ringView.isPlay
-        viewModel.changeAudioState(isPlay: ringView.isPlay)
+        if audioPlayer.isPlaying{
+            audioPlayer.stop()
+        }else{
+            audioPlayer.playRingtone(ringtone: infoModel?.response.musicUrl)
+        }
     }
     }
     
     
     override func dealThings() {
     override func dealThings() {
@@ -103,12 +111,26 @@ class TSGeneralRintoneVC: TSBottomAlertVC {
 
 
 extension TSGeneralRintoneVC{
 extension TSGeneralRintoneVC{
     func setRingViewData(model:TSActionInfoModel) {
     func setRingViewData(model:TSActionInfoModel) {
-        
-//        ringView.cellView.nameLab.text = model.ringtoneName
-//        ringView.cellView.coverImageView.setAsyncImage(urlString: model.ringtoneCover,contentMode: .scaleAspectFill)
-//        ringView.cellView.timeLab.text = duration.floatToMinuteSecond()
-//        vm.playRingtone(ringtone: model.ringtone)
-        
+        ringView.timeLab.text = Float(model.request.duration).floatToMinuteSecond()
+        ringView.nameLab.text = model.response.title
+        ringView.coverImageView.setAsyncImage(urlString: model.response.coverUrl,contentMode: .scaleAspectFill)
+    }
+    
+    func audioPlayerStateChange(state:TSBusinessAudioPlayer.PlayerState){
+        switch state {
+        case .loading(let progress):
+            if progress == 0.0 {
+                ringView.isloading = true
+            }else if progress == 1.0 {
+                ringView.isloading = false
+            }
+        case .play:
+            ringView.isPlay = true
+        case .stop:
+            ringView.isPlay = false
+        default:
+            break
+        }
     }
     }
 }
 }
 
 

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

@@ -7,11 +7,36 @@
 
 
 import Combine
 import Combine
 import Alamofire
 import Alamofire
+//let actionInfoDict:[String:Any] = [
+//    "code":200,
+//    "message": "Success",
+//    "result": [
+//        "actionType":"music_create",
+//        "comments": "Success",
+//        "costTime":15,
+//        "createdTimestamp":1741338454,
+//        "id":1536,
+//        "percent":1,
+//        "request":"{\"prompt\": \"Create a Techno ringtone with a repetitive bassline, crisp hi-hats, and subtle synth textures. Use a BPM of 125-130 for a sleek, modern sound., Create a uplifting and modern music track blending Pop, Electronic, and Ambient elements. Use a BPM of 100-120, a catchy melody with synth or piano, warm harmonies, and a mix of electronic and organic sounds. Ensure a clear structure (Intro, Verse, Chorus, Outro) and a light, positive vibe suitable for background or casual listening\", \"duration\": 5}",
+//        "response":"{\"coverUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/f0fb7739-a5cc-4805-9b68-b4a5890eb285.png\", \"title\": \"Neon Pulse\\\"  \\n\\\"Horizon Glow\", \"musicUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/c47d40dd-d07c-4edc-a6d9-8382438149d1.wav\"}",
+//        "status":"success"
+//    ]
+//]
+
+let actionInfoDict:[String:Any] = [
+    "actionType":"music_create",
+    "comments": "Success",
+    "costTime":15,
+    "createdTimestamp":1741338454,
+    "id":1536,
+    "percent":1,
+    "request":"{\"prompt\": \"Create a Techno ringtone with a repetitive bassline, crisp hi-hats, and subtle synth textures. Use a BPM of 125-130 for a sleek, modern sound., Create a uplifting and modern music track blending Pop, Electronic, and Ambient elements. Use a BPM of 100-120, a catchy melody with synth or piano, warm harmonies, and a mix of electronic and organic sounds. Ensure a clear structure (Intro, Verse, Chorus, Outro) and a light, positive vibe suitable for background or casual listening\", \"duration\": 5}",
+    "response":"{\"coverUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/f0fb7739-a5cc-4805-9b68-b4a5890eb285.png\", \"title\": \"Neon Pulse\\\"  \\n\\\"Horizon Glow\", \"musicUrl\": \"https://be-aigc.s3-accelerate.amazonaws.com/c47d40dd-d07c-4edc-a6d9-8382438149d1.wav\"}",
+    "status":"success"
+]
 
 
 class TSGeneralRintoneVM {
 class TSGeneralRintoneVM {
-    
-    var audioPlayer: TSAudioPlayer?
-    
+
     var creatRequest:Request?
     var creatRequest:Request?
     var queryRequest:Request?
     var queryRequest:Request?
     var stopNetwork = false
     var stopNetwork = false
@@ -24,15 +49,14 @@ class TSGeneralRintoneVM {
 
 
         stateDatauPblished = (.start,nil)
         stateDatauPblished = (.start,nil)
         stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
         stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-
-        kDelayOnMainThread(2.0) {
-            self.stateDatauPblished = (.failed("error?.localizedDescription" ?? ""),nil)
-        }
-
 //        kDelayOnMainThread(2.0) {
 //        kDelayOnMainThread(2.0) {
-//            self.stateDatauPblished = (.success(nil),TSActionInfoModel())
+//            self.stateDatauPblished = (.failed("error?.localizedDescription" ?? ""),nil)
 //        }
 //        }
-
+     
+        kDelayOnMainThread(1.0) {
+            let infoModel = TSActionInfoModel(JSON: actionInfoDict)
+            self.stateDatauPblished = (.success(nil),infoModel)
+        }
     }
     }
     
     
  
  
@@ -41,11 +65,11 @@ class TSGeneralRintoneVM {
         aiText = text
         aiText = text
         let postDict:[String : Any] = [
         let postDict:[String : Any] = [
             "prompt":text,
             "prompt":text,
-            "width":10
+            "duration":5
         ]
         ]
         stateDatauPblished = (.start,nil)
         stateDatauPblished = (.start,nil)
         stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
         stateDatauPblished = (.progressString(generating(progress: 0.0)),nil)
-        creatRequest = TSNetworkShared.post(urlType: .textPicCreate,parameters: postDict) { [weak self] data,error in
+        creatRequest = TSNetworkShared.post(urlType: .musicCreate,parameters: postDict) { [weak self] data,error in
             guard let self = self else { return }
             guard let self = self else { return }
             if let dataDict = data as? [String:Any] ,
             if let dataDict = data as? [String:Any] ,
                dataDict.safeInt(forKey: "code") == 200,
                dataDict.safeInt(forKey: "code") == 200,
@@ -110,36 +134,3 @@ class TSGeneralRintoneVM {
         return "Generating \(progressInt)%"
         return "Generating \(progressInt)%"
     }
     }
 }
 }
-
-extension TSGeneralRintoneVM {
-    
-    func playRingtone(ringtone:String?) {
-        if let ringtone = ringtone {
-            self.audioPlayer?.stop()
-            TSCommonTool.downloadAndCacheFile(from: ringtone) { [self] path, error in
-                if let path = path {
-                    //播放
-                    if let url = URL(string: path) {
-                        self.audioPlayer = TSAudioPlayer(url: url)
-                        self.audioPlayer?.setLoop(true)
-                        self.audioPlayer?.setVolume(1.0)
-                        dePrint(self.audioPlayer?.duration)
-                    }
-                    
-                }else{
-                    //暂停
-                    self.audioPlayer?.stop()
-                }
-            }
-        }
-    }
-
-    func changeAudioState(isPlay:Bool) {
-        if isPlay {
-            self.audioPlayer?.play()
-        }else{
-            self.audioPlayer?.pause()
-        }
-    }
-    
-}

+ 4 - 3
AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/TSTextGeneralRintoneVC.swift

@@ -17,6 +17,8 @@ class TSTextGeneralRintoneVC: TSBaseVC {
         }
         }
     }
     }
     
     
+    var reloadUIBlock:(()->Void)?
+    
     lazy var viewModel: TSTextGeneralRintoneVM = {
     lazy var viewModel: TSTextGeneralRintoneVM = {
         let viewModel:TSTextGeneralRintoneVM = TSTextGeneralRintoneVM()
         let viewModel:TSTextGeneralRintoneVM = TSTextGeneralRintoneVM()
         viewModel.isCanGennerateBlock = { [weak self] enabled in
         viewModel.isCanGennerateBlock = { [weak self] enabled in
@@ -34,7 +36,7 @@ class TSTextGeneralRintoneVC: TSBaseVC {
     
     
     //###################################### 输入框 ######################################
     //###################################### 输入框 ######################################
     lazy var promptTextView: TSPromptTextView = {
     lazy var promptTextView: TSPromptTextView = {
-        let promptTextView = TSPromptTextView(randomTextArray: kRandomTextToPicArray) { [weak self] text in
+        let promptTextView = TSPromptTextView(randomTextArray: kRandomTextToRintone) { [weak self] text in
             guard let self = self else { return }
             guard let self = self else { return }
             viewModel.promptText = text
             viewModel.promptText = text
         }
         }
@@ -130,8 +132,7 @@ extension TSTextGeneralRintoneVC {
             guard let self = self else { return }
             guard let self = self else { return }
             model.request.promptSort = viewModel.promptText
             model.request.promptSort = viewModel.promptText
             viewModel.saveModel(model:model)
             viewModel.saveModel(model:model)
-        
-            NotificationCenter.default.post(name: .kReloadUIData, object: nil, userInfo: ["TSGennerateType": viewModel.gennerateType])
+            reloadUIBlock?()
         }
         }
         
         
         kPresentModalVC(target: self, modelVC: gennerateVC,transitionStyle: .crossDissolve)
         kPresentModalVC(target: self, modelVC: gennerateVC,transitionStyle: .crossDissolve)

+ 15 - 5
AIRingtone/Business/TSAIRintoneVC/TSTextGeneralRintoneVC/VM/TSTextGeneralRintoneVM.swift

@@ -8,6 +8,20 @@
 import Alamofire
 import Alamofire
 import ObjectMapper
 import ObjectMapper
 
 
+
+let kRandomTextToRintone:[String] = [
+    "Create a upbeat Pop ringtone with a catchy melody, bright synths, and a cheerful vibe. Use a BPM of 120-130 and keep it energetic and memorable.",
+    "Generate a cinematic ringtone with orchestral strings, powerful brass, and a dramatic build-up. Use a BPM of 60-80 for a grand, inspiring feel.",
+    "Produce a Lo-Fi ringtone with a relaxed piano melody, soft beats, and warm vinyl crackle. Use a BPM of 70-90 for a cozy, laid-back vibe.",
+    "Create a EDM ringtone with a pulsating bassline, uplifting synths, and a quick build-up. Use a BPM of 128-132 for a high-energy, festival-ready sound.",
+    "Generate a Jazz ringtone with a smooth saxophone melody, soft piano chords, and a gentle swing rhythm. Use a BPM of 90-100 for a classy, sophisticated tone.",
+    "Produce a Funk ringtone with a groovy bassline, rhythmic guitar chops, and punchy brass hits. Use a BPM of 100-110 for a lively, danceable vibe.",
+    "Create a Dubstep ringtone with heavy bass wobbles, aggressive synths, and a quick drop. Use a BPM of 140-150 for an intense, edgy sound.",
+    "Generate a Folk ringtone with a fingerpicked acoustic guitar melody and light percussion. Use a BPM of 80-90 for a warm, earthy feel.",
+    "Produce a Synthwave ringtone with retro arpeggiated synths, a driving beat, and an 80s-inspired vibe. Use a BPM of 100-110 for a nostalgic, futuristic tone.",
+    "Create a Techno ringtone with a repetitive bassline, crisp hi-hats, and subtle synth textures. Use a BPM of 125-130 for a sleek, modern sound.",
+]
+
 class TSTextGeneralRintoneVM {
 class TSTextGeneralRintoneVM {
     
     
     //选择 prompt 类型组
     //选择 prompt 类型组
@@ -71,10 +85,6 @@ extension TSTextGeneralRintoneVM {
     }
     }
 
 
     func saveModel(model:TSActionInfoModel){
     func saveModel(model:TSActionInfoModel){
-        if gennerateType == .poster {
-            TSPosterHistory.saveModel(model: model)
-        }else if gennerateType == .photo{
-            TSPhotoHistory.saveModel(model: model)
-        }
+        TSAIRintoneHistory.saveModel(model: model)
     }
     }
 }
 }

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

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

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

@@ -72,8 +72,8 @@ class TSSetingVC: TSBaseVC {
             }
             }
             
             
             switch type {
             switch type {
-            case .howToUse:
-                viewModel.pushTutorials(parent: self)
+//            case .howToUse:
+//                viewModel.pushTutorials(parent: self)
             case .shareus:
             case .shareus:
                 viewModel.shareApp(parent: self)
                 viewModel.shareApp(parent: self)
             case .agreement:
             case .agreement:

+ 1 - 0
AIRingtone/Business/TSSetingVC/SetingVC/TSSetingViewModel.swift

@@ -82,6 +82,7 @@ class TSSetingViewModel: ObservableObject {
 //            guard let self = self else { return }
 //            guard let self = self else { return }
 //            isViper = PurchaseManager.default.isVip
 //            isViper = PurchaseManager.default.isVip
 //        }
 //        }
+        pushTutorials(parent: parent)
     }
     }
 }
 }
 
 

+ 6 - 0
AIRingtone/Business/TSSetingVC/SetingVC/View/TSSettingListView.swift

@@ -16,6 +16,12 @@ struct TSSettingListView: View {
             VStack(spacing: 0) {
             VStack(spacing: 0) {
                 Spacer().frame(height: 16)
                 Spacer().frame(height: 16)
                 
                 
+                Image("theme_banner").resizable()
+                    .frame(height: 109*kDesignScale)
+                    .onTapGesture {
+                        publisher.enterPurchasePublisher.send(true)
+                    }
+                
 //                SettingPurchaseTopView(eventPublisher: publisher, isViper: $viewModel.isViper)
 //                SettingPurchaseTopView(eventPublisher: publisher, isViper: $viewModel.isViper)
 //                    .frame(height: 117*kDesignScale)
 //                    .frame(height: 117*kDesignScale)
 //                    .onTapGesture {
 //                    .onTapGesture {

+ 37 - 11
AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/TSThemeBrowseVC.swift

@@ -14,8 +14,19 @@ class TSThemeBrowseVC: TSBaseVC {
     }
     }
     var themeViewModel:TSThemeVM
     var themeViewModel:TSThemeVM
     var closePage:()->Void
     var closePage:()->Void
-    var browseViewModel:TSThemeBrowseVM = TSThemeBrowseVM()
-     
+    
+
+    lazy var audioPlayer = TSBusinessAudioPlayer{ [weak self] state in
+        guard let self = self else { return }
+        audioPlayerStateChange(state: state)
+    }
+    
+    lazy var browseViewModel: TSThemeBrowseVM = {
+        browseViewModel = TSThemeBrowseVM()
+        browseViewModel.audioPlayer = audioPlayer
+        return browseViewModel
+    }()
+
     var currentRingtone:String?
     var currentRingtone:String?
     
     
     var currentModel:TSThemeModel?{
     var currentModel:TSThemeModel?{
@@ -140,6 +151,21 @@ class TSThemeBrowseVC: TSBaseVC {
 
 
 extension TSThemeBrowseVC {
 extension TSThemeBrowseVC {
     
     
+    func audioPlayerStateChange(state:TSBusinessAudioPlayer.PlayerState){
+        switch state {
+        case .loading(let progress):
+            if progress == 0.0 {
+                btnView.isSoundloadling = true
+            }else if progress == 1.0 {
+                btnView.isSoundloadling = false
+            }
+        case .volume(let volume):
+            btnView.soundVolume = volume
+        default:
+            break
+        }
+    }
+    
     func setUpRingtone() {
     func setUpRingtone() {
         if let ringtone = currentModel?.ringtone {
         if let ringtone = currentModel?.ringtone {
             TSCommonTool.downloadAndCacheFile(from: ringtone) { path, error in
             TSCommonTool.downloadAndCacheFile(from: ringtone) { path, error in
@@ -156,7 +182,12 @@ extension TSThemeBrowseVC {
     
     
     override func viewDidDisappear(_ animated: Bool) {
     override func viewDidDisappear(_ animated: Bool) {
         super.viewDidDisappear(animated)
         super.viewDidDisappear(animated)
-        browseViewModel.audioPlayer?.stop()
+        browseViewModel.audioPlayer?.pause()
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        browseViewModel.audioPlayer?.play()
     }
     }
 }
 }
 
 
@@ -183,20 +214,15 @@ extension TSThemeBrowseVC {
     }
     }
     
     
     @objc func clickSound(){
     @objc func clickSound(){
-        
-        let volume = browseViewModel.changeAudioState()
-        btnView.soundBtn.setImage(UIImage(named: volume == 0 ? "sound_off_icon" : "sound_on_icon"), for: .normal)
-        
+        let volume = audioPlayer.changeAudioSwitch()
+        btnView.soundVolume = volume
     }
     }
     
     
     @objc func clickDone(){
     @objc func clickDone(){
-        
         if let currentModel = currentModel {
         if let currentModel = currentModel {
-            let setVC = TSThemeSetVC(model: currentModel, duration: Float(browseViewModel.audioPlayer?.duration ?? 0.0))
+            let setVC = TSThemeSetVC(model: currentModel, duration: Float(audioPlayer.duration))
             kPushVC(target: self, modelVC: setVC)
             kPushVC(target: self, modelVC: setVC)
         }
         }
-        
-   
     }
     }
     
     
     
     

+ 5 - 36
AIRingtone/Business/TSThemeVC/TSThemeBrowseVC/VM/TSThemeBrowseVM.swift

@@ -6,46 +6,15 @@
 //
 //
 
 
 class TSThemeBrowseVM {
 class TSThemeBrowseVM {
-
-    var audioPlayer: TSAudioPlayer?
-    
+    var audioPlayer:TSBusinessAudioPlayer?
     //vc 的 currentIndex 是根据滚动视图平凡变动的,finallyIndex只会在有新值时候产生变动
     //vc 的 currentIndex 是根据滚动视图平凡变动的,finallyIndex只会在有新值时候产生变动
     var finallyIndex:Int = -1
     var finallyIndex:Int = -1
-    
-    func playRingtone(ringtone:String?) {
-        if let ringtone = ringtone {
-            self.audioPlayer?.stop()
-            TSCommonTool.downloadAndCacheFile(from: ringtone) { [weak self] path, error in
-                guard let self = self else { return }
-                
-                if let path = path {
-                    //播放
-                    if let url = URL(string: path) {
-                        self.audioPlayer = TSAudioPlayer(url: url)
-                        self.audioPlayer?.setLoop(true)
-                        self.audioPlayer?.setVolume(1.0)
-                        self.audioPlayer?.play()
-                        dePrint(self.audioPlayer?.duration)
-                    }
-                    
-                }else{
-                    //暂停
-                    self.audioPlayer?.stop()
-                }
-            }
-        }
-    }
-    
-    func setFinallyIndex(_ currentIndex:Int,ringtone:String?)  {
+
+    //return  发生了改变
+    func setFinallyIndex(_ currentIndex:Int,ringtone:String?){
         if finallyIndex != currentIndex{
         if finallyIndex != currentIndex{
             finallyIndex = currentIndex
             finallyIndex = currentIndex
-            playRingtone(ringtone: ringtone)
+            audioPlayer?.playRingtone(ringtone: ringtone)
         }
         }
     }
     }
-
-    func changeAudioState()->Float {
-        let volume:Float = self.audioPlayer?.volume == 0.0 ? 1.0 : 0.0
-        self.audioPlayer?.setVolume(volume)
-        return volume
-    }
 }
 }

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

@@ -23,6 +23,28 @@ class TSTBBtnView: TSBaseView {
     }()
     }()
     
     
     
     
+    var isSoundloadling:Bool = false {
+        didSet {
+            if isSoundloadling {
+                soundBtn.setImage(UIImage(named: "sound_loading_icon"), for: .normal)
+                soundBtn.startRotating()
+            }else {
+                soundBtn.stopRotating()
+                let volume = soundVolume
+                soundVolume = volume
+            }
+        }
+    }
+    
+    var soundVolume:Float = 1.0{
+        didSet{
+            DispatchQueue.main.async {
+                self.soundBtn.setImage(UIImage(named: self.soundVolume == 0 ? "sound_off_icon" : "sound_on_icon"), for: .normal)
+            }
+        }
+    }
+    
+    
     lazy var lockBtn: UIButton = {
     lazy var lockBtn: UIButton = {
         let btn = UIButton.createButton(image: UIImage(named: "lock_screen_icon"))
         let btn = UIButton.createButton(image: UIImage(named: "lock_screen_icon"))
         btn.addTarget(targetVC, action: lockSelector, for: .touchUpInside)
         btn.addTarget(targetVC, action: lockSelector, for: .touchUpInside)

+ 1 - 0
AIRingtone/Business/VIewTool/TSRingToneCellView.swift

@@ -15,6 +15,7 @@ class TSRingToneCellView: TSBaseView {
     
     
     var isPlay:Bool = false {
     var isPlay:Bool = false {
         didSet{
         didSet{
+            playBtn.stopRotating()
             playBtn.setImage(UIImage(named: isPlay ? "ringtone_pause" : "ringtone_play"), for: .normal)
             playBtn.setImage(UIImage(named: isPlay ? "ringtone_pause" : "ringtone_play"), for: .normal)
         }
         }
     }
     }

+ 1 - 0
AIRingtone/Common/Ex/Notification+TSEx.swift

@@ -8,6 +8,7 @@
 // 扩展 Notification.Name
 // 扩展 Notification.Name
 extension Notification.Name {
 extension Notification.Name {
     static let kReloadUIData = Notification.Name("kReloadUIData")   //通知页面刷新数据,具体类型,根据参数来区分
     static let kReloadUIData = Notification.Name("kReloadUIData")   //通知页面刷新数据,具体类型,根据参数来区分
+    static let kBusinessAudioStateChange = Notification.Name("kBusinessAudioStateChange")   //通知页面刷新数据,具体类型,根据参数来区分
 }
 }
 
 
 
 

+ 1 - 0
AIRingtone/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift

@@ -17,6 +17,7 @@ enum TSNeURLType:String {
     case upload = "/api/upload"                  //上传图片
     case upload = "/api/upload"                  //上传图片
     case imageRewrite = "/api/image/rewrite"     //图生图
     case imageRewrite = "/api/image/rewrite"     //图生图
     case themes = "/api/ops/themes"              //主体查询
     case themes = "/api/ops/themes"              //主体查询
+    case musicCreate = "/api/music/create"       //铃声创建
     
     
     func getUrlString() -> String {
     func getUrlString() -> String {
         return baseURL + self.rawValue
         return baseURL + self.rawValue

+ 22 - 7
AIRingtone/Common/NetworkManager/TSNetWork/TSNetworkManager+Loading.swift

@@ -17,12 +17,20 @@ extension TSNetworkManager {
         completion: @escaping (Any?, Error?) -> Void
         completion: @escaping (Any?, Error?) -> Void
     ) -> Request? {
     ) -> Request? {
         
         
-        TSLoadingAnimation.showLoading(in: animationView)
+        
+        let loadingLogic = kCreateLoadingLogic {
+            guard let view = animationView else { return }
+            TSToastShared.showLoading(containerView: view)
+        } hideBlock: {
+            TSToastShared.hideLoading()
+        }
 
 
+//        TSLoadingAnimation.showLoading(in: animationView)
+        loadingLogic.show()
 
 
         return get(urlType: urlType, parameters:parameters,responseType:responseType,useCache: useCache) { result in
         return get(urlType: urlType, parameters:parameters,responseType:responseType,useCache: useCache) { result in
-           
-            TSLoadingAnimation.hideLoading()
+            loadingLogic.hide()
+//            TSLoadingAnimation.hideLoading()
             
             
             switch result {
             switch result {
             case .success(let data):
             case .success(let data):
@@ -50,11 +58,18 @@ extension TSNetworkManager {
         useCache: Bool = false,
         useCache: Bool = false,
         completion: @escaping (Any?, Error?) -> Void
         completion: @escaping (Any?, Error?) -> Void
     ) -> Request? {
     ) -> Request? {
-
-        TSLoadingAnimation.showLoading(in: animationView)
+        
+        let loadingLogic = kCreateLoadingLogic {
+            guard let view = animationView else { return }
+            TSToastShared.showLoading(containerView: view)
+        } hideBlock: {
+            TSToastShared.hideLoading()
+        }
+        loadingLogic.show()
+//        TSLoadingAnimation.showLoading(in: animationView)
         return post(urlType: urlType, parameters:parameters,responseType:responseType,useCache: useCache){ result in
         return post(urlType: urlType, parameters:parameters,responseType:responseType,useCache: useCache){ result in
-            
-            TSLoadingAnimation.hideLoading()
+            loadingLogic.hide()
+//            TSLoadingAnimation.hideLoading()
     
     
             switch result {
             switch result {
             case .success(let data):
             case .success(let data):

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

@@ -0,0 +1,108 @@
+//
+//  TSBusinessAudioPlayer.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/7.
+//
+
+
+class TSBusinessAudioPlayer {
+    
+    static let shared = TSBusinessAudioPlayer { state in
+        NotificationCenter.default.post(name: .kReloadUIData, object: nil, userInfo: ["PlayerState": state])
+    }
+    
+    enum PlayerState:Equatable {
+        case play
+        case pause
+        case stop
+        case loading(Float)
+        case volume(Float)
+    }
+    
+    private var audioPlayer: TSAudioPlayer?
+    
+    var stateChangeBlock:(PlayerState) -> Void
+    
+    var duration:Double{
+        if let audioPlayer = audioPlayer {
+            return audioPlayer.duration
+        }
+        return 0.0
+    }
+    
+    var isPlaying:Bool{
+        if let audioPlayer = audioPlayer {
+            return audioPlayer.isPlaying
+        }
+        return false
+    }
+    
+    init(stateChangeBlock:@escaping (PlayerState) -> Void) {
+        self.stateChangeBlock = stateChangeBlock
+    }
+    
+    func playRingtone(ringtone:String?) {
+        if let ringtone = ringtone {
+        
+            self.stop()
+            let loadingLogic = kCreateLoadingLogic {
+                self.stateChangeBlock(.loading(0.0))
+            } hideBlock: {
+                self.stateChangeBlock(.loading(1.0))
+            }
+
+            loadingLogic.show()
+            TSCommonTool.downloadAndCacheFile(from: ringtone) { [weak self] path, error in
+                guard let self = self else { return }
+                loadingLogic.hide()
+                
+                if let path = path {
+                    //播放
+                    if let url = URL(string: path) {
+                        self.audioPlayer = TSAudioPlayer(url: url)
+                        self.audioPlayer?.setLoop(true)
+                        setVolume(volume: 1.0)
+                        self.play()
+                        dePrint(self.audioPlayer?.duration)
+                    }
+                    
+                }else{
+                    //暂停
+                    self.stop()
+                }
+            }
+        }
+    }
+    
+    
+    func play() {
+        self.audioPlayer?.play()
+        stateChangeBlock(.play)
+    }
+    
+    func stop() {
+        self.audioPlayer?.stop()
+        stateChangeBlock(.stop)
+    }
+    
+    func pause() {
+        self.audioPlayer?.pause()
+        stateChangeBlock(.pause)
+    }
+    
+    func setVolume(volume:Float){
+        self.audioPlayer?.setVolume(volume)
+        stateChangeBlock(.volume(volume))
+    }
+    
+    func changeAudioSwitch()->Float {
+        let volume:Float = self.audioPlayer?.volume == 0.0 ? 1.0 : 0.0
+        setVolume(volume: volume)
+        return volume
+    }
+     
+    deinit {
+        dePrint("TSBusinessAudioPlayer deinit")
+    }
+}

+ 29 - 0
AIRingtone/Common/Tool/TSLoadingAnimation.swift

@@ -38,3 +38,32 @@ class TSLoadingAnimation {
         TSToastShared.hideLoading()
         TSToastShared.hideLoading()
     }
     }
 }
 }
+
+
+/// 封装的加载动画显示和隐藏逻辑闭包函数
+/// - Parameters:
+///   - delay: 延迟显示的时间(默认 0.75 秒)
+///   - showBlock: 显示加载动画的闭包
+///   - hideBlock: 隐藏加载动画的闭包
+/// - Returns: 一个包含显示和隐藏功能的闭包,可通过调用该闭包的不同属性来显示或隐藏动画
+func kCreateLoadingLogic(delay: TimeInterval = 0.5, showBlock: @escaping () -> Void, hideBlock: @escaping () -> Void) -> (show: () -> Void, hide: () -> Void) {
+    var isAnimationHidden = false
+
+    let showLoading = {
+        isAnimationHidden = false
+        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+            if !isAnimationHidden {
+                showBlock()
+            }
+        }
+    }
+
+    let hideLoading = {
+        isAnimationHidden = true
+        DispatchQueue.main.async {
+            hideBlock()
+        }
+    }
+
+    return (show: showLoading, hide: hideLoading)
+}