浏览代码

4.3a改造:完成成功弹窗的混淆

kln 1 周之前
父节点
当前提交
268cd3f7d5

+ 12 - 24
AIPlayRingtones.xcodeproj/project.pbxproj

@@ -20,10 +20,8 @@
 		3DB4D4AA2DDDCEA50082596A /* Poppins-BoldItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = 3DB4D4A92DDDCEA50082596A /* Poppins-BoldItalic.otf */; };
 		3DB4D4B22DDF0B960082596A /* FakeBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB4D4B12DDF0B940082596A /* FakeBlurView.swift */; };
 		3DB4D4B52DE025920082596A /* ASTutorialsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB4D4B42DE025910082596A /* ASTutorialsVC.swift */; };
-		3DBEA0CD2DE69B2E000C6859 /* TSSaveSuccessTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA0B72DE69B2E000C6859 /* TSSaveSuccessTool.swift */; };
 		3DBEA0CE2DE69B2E000C6859 /* TSSimpleCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA0B52DE69B2E000C6859 /* TSSimpleCollectionView.swift */; };
 		3DBEA0FF2DE69B2E000C6859 /* TSCustomStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA0C02DE69B2E000C6859 /* TSCustomStackView.swift */; };
-		3DBEA1072DE69B2E000C6859 /* TSPlaceholderTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA0B12DE69B2E000C6859 /* TSPlaceholderTextView.swift */; };
 		3DBEA1102DE6A04B000C6859 /* SimpleWebViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA10F2DE6A04B000C6859 /* SimpleWebViewController.m */; };
 		3DBEA11A2DE6B086000C6859 /* CustomTabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1192DE6B086000C6859 /* CustomTabBarController.m */; };
 		3DBEA11E2DE6B229000C6859 /* NSString+AS.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA11D2DE6B229000C6859 /* NSString+AS.m */; };
@@ -54,6 +52,9 @@
 		3DBEA1742DE6F8D3000C6859 /* ASFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1732DE6F8D3000C6859 /* ASFileManager.m */; };
 		3DBEA1762DE6FB18000C6859 /* ASFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1752DE6FB0A000C6859 /* ASFileManager.swift */; };
 		3DBEA1782DE6FC66000C6859 /* ASTLLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1772DE6FC59000C6859 /* ASTLLabel.swift */; };
+		3DBEA17A2DE6FFA1000C6859 /* ASInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA1792DE6FFA0000C6859 /* ASInputView.swift */; };
+		3DBEA17C2DE70336000C6859 /* ASSaveResultManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA17B2DE70335000C6859 /* ASSaveResultManager.swift */; };
+		3DBEA17E2DE703D6000C6859 /* ASDynamicContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBEA17D2DE703D1000C6859 /* ASDynamicContainer.swift */; };
 		3DCD56F32DDAE3E3004AAB5B /* ASRingToneCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD56F22DDAE3DF004AAB5B /* ASRingToneCellView.swift */; };
 		3DCD56F52DDAE42A004AAB5B /* ASViewTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD56F42DDAE421004AAB5B /* ASViewTool.swift */; };
 		3DCD56F92DDAE481004AAB5B /* TSBusinessAudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DCD56F62DDAE481004AAB5B /* TSBusinessAudioPlayer.swift */; };
@@ -142,9 +143,7 @@
 		3DB4D4A92DDDCEA50082596A /* Poppins-BoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-BoldItalic.otf"; sourceTree = "<group>"; };
 		3DB4D4B12DDF0B940082596A /* FakeBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeBlurView.swift; sourceTree = "<group>"; };
 		3DB4D4B42DE025910082596A /* ASTutorialsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASTutorialsVC.swift; sourceTree = "<group>"; };
-		3DBEA0B12DE69B2E000C6859 /* TSPlaceholderTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSPlaceholderTextView.swift; sourceTree = "<group>"; };
 		3DBEA0B52DE69B2E000C6859 /* TSSimpleCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSSimpleCollectionView.swift; sourceTree = "<group>"; };
-		3DBEA0B72DE69B2E000C6859 /* TSSaveSuccessTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSSaveSuccessTool.swift; sourceTree = "<group>"; };
 		3DBEA0C02DE69B2E000C6859 /* TSCustomStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSCustomStackView.swift; sourceTree = "<group>"; };
 		3DBEA10E2DE6A04B000C6859 /* SimpleWebViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SimpleWebViewController.h; sourceTree = "<group>"; };
 		3DBEA10F2DE6A04B000C6859 /* SimpleWebViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimpleWebViewController.m; sourceTree = "<group>"; };
@@ -191,6 +190,9 @@
 		3DBEA1732DE6F8D3000C6859 /* ASFileManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASFileManager.m; sourceTree = "<group>"; };
 		3DBEA1752DE6FB0A000C6859 /* ASFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASFileManager.swift; sourceTree = "<group>"; };
 		3DBEA1772DE6FC59000C6859 /* ASTLLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASTLLabel.swift; sourceTree = "<group>"; };
+		3DBEA1792DE6FFA0000C6859 /* ASInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASInputView.swift; sourceTree = "<group>"; };
+		3DBEA17B2DE70335000C6859 /* ASSaveResultManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASSaveResultManager.swift; sourceTree = "<group>"; };
+		3DBEA17D2DE703D1000C6859 /* ASDynamicContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASDynamicContainer.swift; sourceTree = "<group>"; };
 		3DCD56F22DDAE3DF004AAB5B /* ASRingToneCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASRingToneCellView.swift; sourceTree = "<group>"; };
 		3DCD56F42DDAE421004AAB5B /* ASViewTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASViewTool.swift; sourceTree = "<group>"; };
 		3DCD56F62DDAE481004AAB5B /* TSBusinessAudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBusinessAudioPlayer.swift; sourceTree = "<group>"; };
@@ -338,14 +340,6 @@
 			path = ASTutorialsVC;
 			sourceTree = "<group>";
 		};
-		3DBEA0B22DE69B2E000C6859 /* TSPlaceholderTextView */ = {
-			isa = PBXGroup;
-			children = (
-				3DBEA0B12DE69B2E000C6859 /* TSPlaceholderTextView.swift */,
-			);
-			path = TSPlaceholderTextView;
-			sourceTree = "<group>";
-		};
 		3DBEA0B62DE69B2E000C6859 /* TSReusableCollectionView */ = {
 			isa = PBXGroup;
 			children = (
@@ -354,14 +348,6 @@
 			path = TSReusableCollectionView;
 			sourceTree = "<group>";
 		};
-		3DBEA0B82DE69B2E000C6859 /* TSSaveSuccessTool */ = {
-			isa = PBXGroup;
-			children = (
-				3DBEA0B72DE69B2E000C6859 /* TSSaveSuccessTool.swift */,
-			);
-			path = TSSaveSuccessTool;
-			sourceTree = "<group>";
-		};
 		3DBEA0C12DE69B2E000C6859 /* UIStackView */ = {
 			isa = PBXGroup;
 			children = (
@@ -373,9 +359,7 @@
 		3DBEA0C52DE69B2E000C6859 /* View */ = {
 			isa = PBXGroup;
 			children = (
-				3DBEA0B22DE69B2E000C6859 /* TSPlaceholderTextView */,
 				3DBEA0B62DE69B2E000C6859 /* TSReusableCollectionView */,
-				3DBEA0B82DE69B2E000C6859 /* TSSaveSuccessTool */,
 				3DBEA0C12DE69B2E000C6859 /* UIStackView */,
 			);
 			path = View;
@@ -503,6 +487,8 @@
 		A800FEAF2DDAC0E9009DABDC /* CommonView */ = {
 			isa = PBXGroup;
 			children = (
+				3DBEA17D2DE703D1000C6859 /* ASDynamicContainer.swift */,
+				3DBEA1792DE6FFA0000C6859 /* ASInputView.swift */,
 				3DBEA1772DE6FC59000C6859 /* ASTLLabel.swift */,
 				3DBEA1662DE6F439000C6859 /* ASCustomAlert.swift */,
 				3DBEA14E2DE6E912000C6859 /* ASTouchBtn.swift */,
@@ -511,6 +497,7 @@
 				3DCD572E2DDB1D87004AAB5B /* ASRingLoadingView.swift */,
 				3DCD56F42DDAE421004AAB5B /* ASViewTool.swift */,
 				3DCD56F22DDAE3DF004AAB5B /* ASRingToneCellView.swift */,
+				3DBEA17B2DE70335000C6859 /* ASSaveResultManager.swift */,
 				A800FEB02DDAC0F0009DABDC /* ASGeneratorView */,
 			);
 			path = CommonView;
@@ -999,6 +986,7 @@
 				3DBEA1402DE6DD18000C6859 /* ASNormalNavigationBarView.m in Sources */,
 				3DCD571B2DDB1158004AAB5B /* AudioConverter.m in Sources */,
 				3DBEA1742DE6F8D3000C6859 /* ASFileManager.m in Sources */,
+				3DBEA17A2DE6FFA1000C6859 /* ASInputView.swift in Sources */,
 				3DBEA14D2DE6E8AB000C6859 /* UILabel+AS.swift in Sources */,
 				3DB4D4932DDC25C10082596A /* APAudioToRingVM.swift in Sources */,
 				3DCD571C2DDB1158004AAB5B /* ZHCroppedDelegate.swift in Sources */,
@@ -1040,18 +1028,18 @@
 				A848F8CD2DD6EAD300B746EC /* ASPromptTextView.swift in Sources */,
 				A848F8DB2DD7231E00B746EC /* APRingStyleVC.swift in Sources */,
 				A848F8D22DD7149D00B746EC /* ASDurationColView.swift in Sources */,
+				3DBEA17C2DE70336000C6859 /* ASSaveResultManager.swift in Sources */,
 				A848F8C62DD6E72D00B746EC /* APRingTonesVC+View.swift in Sources */,
 				A848F8862DD6D1AF00B746EC /* AppDelegate.swift in Sources */,
-				3DBEA0CD2DE69B2E000C6859 /* TSSaveSuccessTool.swift in Sources */,
 				3DBEA0CE2DE69B2E000C6859 /* TSSimpleCollectionView.swift in Sources */,
 				3DBEA1312DE6D825000C6859 /* ASBaseCollectionCell.m in Sources */,
 				3DBEA12B2DE6BCE3000C6859 /* ASBaseNavigationController.m in Sources */,
 				3DBEA1562DE6EBAD000C6859 /* UIButton+AS.swift in Sources */,
 				3DBEA0FF2DE69B2E000C6859 /* TSCustomStackView.swift in Sources */,
+				3DBEA17E2DE703D6000C6859 /* ASDynamicContainer.swift in Sources */,
 				3DBEA1362DE6D9A3000C6859 /* ASBaseModel.swift in Sources */,
 				3DBEA14B2DE6E731000C6859 /* UIViewController+CustomAlert.m in Sources */,
 				3DBEA1252DE6B36D000C6859 /* UIColor+AS.m in Sources */,
-				3DBEA1072DE69B2E000C6859 /* TSPlaceholderTextView.swift in Sources */,
 				3DB4D4912DDC1F100082596A /* ASRingGeneratorVC+load.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;

+ 2 - 2
AIPlayRingtones/AppPage/APRingTonesVC/APRingTonesVC/View/ASPromptTextView.swift

@@ -50,8 +50,8 @@ class ASPromptTextView : ASBaseView{
         return textBgView
     }()
     
-    lazy var customTextView: XHInputView = {
-        let customTextView = XHInputView(
+    lazy var customTextView: ASInputView = {
+        let customTextView = ASInputView(
             initialText: "Type your idea here.",
             textFont: .font(size: 14),
             contentColor: .white,

+ 1 - 1
AIPlayRingtones/AppPage/APSettingVC/APSettingVC.swift

@@ -129,7 +129,7 @@ class APSettingVC: ASBaseViewController {
                 rightIsHave: false,
                 tapBlock: { [weak self] model, _, _ in
                    guard let self = self else { return }
-      
+                    kSuccessManager.displayNotification(in: self.view,message: "Successfully generated".localized)
         }))
         
         return dataArray

+ 0 - 127
AIPlayRingtones/Classes/View/TSSaveSuccessTool/TSSaveSuccessTool.swift

@@ -1,127 +0,0 @@
-//
-//  TSSaveSuccessTool.swift
-//  Pods
-//
-//  Created by 100Years on 2025/4/24.
-//
-
-public let kSaveSuccesswShared = TSSaveSuccessTool.shared
-open class TSSaveSuccessTool {
-    
-    static let shared = TSSaveSuccessTool()
-    
-    public var clickViewHandle:(()->Void)?
-    
-    private lazy var textLabel:UILabel = {
-        let textLabel = UILabel()
-        textLabel.textColor = UIColor.white
-        textLabel.text = "Save Successfully".localized
-        textLabel.font = UIFont.font(size: 14)
-        return textLabel
-    }()
-    
-    private lazy var saveSuccessBg: UIView = {
-        return creatSaveSuccessBg()
-    }()
-    
-    
-    private lazy var viewButton:UIView = {
-        let color = "4FEA9D".uiColor
-        let viewButton = UIButton.createBtn(title: "View".localized ,backgroundColor: color.withAlphaComponent(0.1),font: UIFont.font(size: 12),titleColor: color,corner: 14) { [weak self]  in
-            guard let self = self else { return }
-            if let clickViewHandle = clickViewHandle {
-                clickViewHandle()
-            }else {
-                if let url = URL(string: "photos-redirect://") {
-                    if UIApplication.shared.canOpenURL(url) {
-                        UIApplication.shared.open(url, options: [:], completionHandler: nil)
-                    }
-                }
-            }
-            
-            DispatchQueue.main.async{
-                self.saveSuccessBg.removeFromSuperview()
-            }
-        }
-        viewButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
-        return viewButton
-    }()
-    
-    func creatSaveSuccessBg() -> UIView {
-        let view = UIView()
-        view.frame = CGRect(x: 0, y: 0, width: 288, height: 48)
-        // 阴影
-        view.backgroundColor = .clear
-        view.layer.shadowColor = UIColor.black.cgColor
-        view.layer.shadowOffset = CGSize(width: 0, height: 2)
-        view.layer.shadowOpacity = 0.1
-        
-        // 圆角
-        let colorBg = UIView()
-        colorBg.backgroundColor = "#333333".uiColor
-        colorBg.layer.cornerRadius = 8
-        colorBg.layer.masksToBounds = true
-        colorBg.clipsToBounds = true
-        
-        view.addSubview(colorBg)
-        colorBg.snp.makeConstraints { make in
-            make.leading.trailing.top.bottom.equalTo(0)
-        }
-        
-        let image = UIImage(named: "success_icon")
-        let iconView = UIImageView(image: image)
-        view.addSubview(iconView)
-        iconView.snp.makeConstraints { make in
-            make.width.height.equalTo(24)
-            make.centerY.equalToSuperview()
-            make.leading.equalTo(12)
-        }
-    
-        view.addSubview(viewButton)
-        view.addSubview(textLabel)
-        viewButton.snp.makeConstraints { make in
-            make.width.equalTo(viewButton.intrinsicContentSize.width)
-            make.height.equalTo(28)
-            make.trailing.equalTo(-8)
-            make.centerY.equalToSuperview()
-        }
-    
-        textLabel.snp.makeConstraints { make in
-            make.leading.equalTo(iconView.snp.trailing).offset(8)
-            make.trailing.equalTo(viewButton.snp.leading).offset(-8)
-            make.centerY.equalToSuperview()
-        }
-
-        return view
-    }
-
-    
-    
-    public func getBottom(topY:CGFloat)->CGFloat{
-        let bottom = -(k_ScreenHeight - 48 - topY)
-        logPrint("bottom=\(bottom)")
-        return bottom
-    }
-    
-    public func show(atView:UIView,text:String = "Save Successfully".localized,deadline:Double = 2.0,bottom:CGFloat = -112,showViewBtn:Bool = true,clickViewHandle:(()->Void)? = nil) {
-        self.clickViewHandle = clickViewHandle
-        kExecuteOnMainThread {
-            self.textLabel.text = text
-            self.viewButton.isHidden = !showViewBtn
-            atView.addSubview(self.saveSuccessBg)
-            self.saveSuccessBg.snp.remakeConstraints { make in
-                make.width.greaterThanOrEqualTo(288)
-                make.width.lessThanOrEqualTo(k_ScreenWidth-32)
-                make.height.equalTo(48)
-                make.centerX.equalToSuperview()
-                make.bottom.equalTo(bottom)
-            }
-        }
-
-        DispatchQueue.main.asyncAfter(deadline: .now() + deadline) {
-            self.saveSuccessBg.removeFromSuperview()
-        }
-    }
-    
-    
-}

+ 3 - 34
AIPlayRingtones/Classes/View/UIStackView/TSCustomStackView.swift

@@ -9,16 +9,10 @@ import UIKit
 import SnapKit
 
 open class TSCustomStackView: UIView {
-    // 内部的 UIScrollView 和 UIStackView
+
     public let scrollView: UIScrollView
-//    public let scrollView: KLMultiScrollContainer
-    
-    
-    
-    
     public let stackView: UIStackView
-    
-    // 开放的属性,用于设置方向和间距
+
     public var axis: NSLayoutConstraint.Axis {
         get {
             return stackView.axis
@@ -106,32 +100,7 @@ open class TSCustomStackView: UIView {
             }
         }
     }
-    
-    //动态添加子视图的方法(添加到白板一个空板 View)
-    public func addSubviewToStackWhiteBoard(_ view: UIView,length:CGFloat? = nil) {
-        let bgView = UIView()
-        bgView.addSubview(view)
-        addSubviewToStack(bgView,length: length)
-    }
-    
-    // 在指定位置插入子视图
-    public func insertViewToStack(_ view: UIView, at stackIndex: Int) {
-        stackView.insertArrangedSubview(view, at: stackIndex)
-        // 可以根据需要对子视图进行额外的布局设置
-        view.snp.makeConstraints { make in
-            if axis == .vertical {
-                make.width.equalTo(stackView)
-            } else {
-                make.height.equalTo(stackView)
-            }
-        }
-    }
-    
-    // 移除子视图
-    public func removeViewToStack(_ view: UIView) {
-        stackView.removeArrangedSubview(view)
-        view.removeFromSuperview()
-    }
+
     
     public func addSpacing(length:CGFloat) {
         let view = UIView()

+ 156 - 0
AIPlayRingtones/CommonView/ASDynamicContainer.swift

@@ -0,0 +1,156 @@
+//
+//  ASDynamicContainer.swift
+//  AIPlayRingtones
+//
+//  Created by mini on 2025/5/28.
+//
+
+import UIKit
+import SnapKit
+
+open class ASDynamicContainer: UIView {
+    
+    // MARK: - UI Components
+    private let contentScroller: UIScrollView
+    private let elementArranger: UIStackView
+    
+    // MARK: - Configuration Properties
+    public var layoutOrientation: NSLayoutConstraint.Axis {
+        get { elementArranger.axis }
+        set {
+            elementArranger.axis = newValue
+            refreshLayoutConstraints()
+        }
+    }
+    
+    public var elementGap: CGFloat {
+        get { elementArranger.spacing }
+        set { elementArranger.spacing = newValue }
+    }
+    
+    public var computedHeight: CGFloat {
+        contentScroller.contentSize.height
+    }
+    
+    // MARK: - Initialization
+    public init(
+        orientation: NSLayoutConstraint.Axis = .vertical,
+        contentAlignment: UIStackView.Alignment = .leading,
+        gapSize: CGFloat = 0
+    ) {
+        self.contentScroller = UIScrollView()
+        self.elementArranger = UIStackView()
+        
+        super.init(frame: .zero)
+        
+        configureScroller()
+        configureArranger(
+            axis: orientation,
+            alignment: contentAlignment,
+            spacing: gapSize
+        )
+        assembleInterface()
+    }
+    
+    required public init?(coder: NSCoder) {
+        fatalError("Unsupported initialization method")
+    }
+    
+    // MARK: - Configuration Methods
+    private func configureScroller() {
+        contentScroller.showsVerticalScrollIndicator = false
+        contentScroller.showsHorizontalScrollIndicator = false
+    }
+    
+    private func configureArranger(
+        axis: NSLayoutConstraint.Axis,
+        alignment: UIStackView.Alignment,
+        spacing: CGFloat
+    ) {
+        elementArranger.axis = axis
+        elementArranger.alignment = alignment
+        elementArranger.spacing = spacing
+        elementArranger.distribution = .fill
+    }
+    
+    private func assembleInterface() {
+        addSubview(contentScroller)
+        contentScroller.snp.makeConstraints { $0.edges.equalToSuperview() }
+        
+        contentScroller.addSubview(elementArranger)
+        refreshLayoutConstraints()
+    }
+    
+    private func refreshLayoutConstraints() {
+        elementArranger.snp.remakeConstraints {
+            $0.edges.equalToSuperview()
+            
+            switch layoutOrientation {
+            case .vertical:
+                $0.width.equalTo(contentScroller)
+            case .horizontal:
+                $0.height.equalTo(contentScroller)
+            @unknown default:
+                break
+            }
+        }
+    }
+    
+    // MARK: - Content Management
+    public func embedView(
+        _ view: UIView,
+        dimension: CGFloat? = nil
+    ) {
+        elementArranger.addArrangedSubview(view)
+        
+        view.snp.makeConstraints {
+            switch layoutOrientation {
+            case .vertical:
+                $0.width.equalTo(elementArranger)
+                if let dim = dimension { $0.height.equalTo(dim) }
+            case .horizontal:
+                $0.height.equalTo(elementArranger)
+                if let dim = dimension { $0.width.equalTo(dim) }
+            @unknown default:
+                break
+            }
+        }
+    }
+    
+    public func insertGap(_ size: CGFloat) {
+        let spacer = UIView()
+        embedView(spacer)
+        
+        spacer.snp.makeConstraints {
+            switch layoutOrientation {
+            case .vertical:
+                $0.height.equalTo(size)
+            case .horizontal:
+                $0.width.equalTo(size)
+            @unknown default:
+                break
+            }
+        }
+    }
+}
+
+// MARK: - StackView Extension
+extension UIStackView {
+    public func appendSpacer(_ length: CGFloat) {
+        let spacer = UIView()
+        self.addArrangedSubview(spacer)
+        
+        spacer.snp.makeConstraints {
+            switch axis {
+            case .vertical:
+                $0.width.equalToSuperview()
+                $0.height.equalTo(length)
+            case .horizontal:
+                $0.width.equalTo(length)
+                $0.height.equalToSuperview()
+            @unknown default:
+                break
+            }
+        }
+    }
+}

+ 4 - 5
AIPlayRingtones/Classes/View/TSPlaceholderTextView/TSPlaceholderTextView.swift → AIPlayRingtones/CommonView/ASInputView.swift

@@ -1,12 +1,11 @@
 //
-//  TSPlaceholderTextView.swift
-//  Pods
+//  ASInputView.swift
+//  AIPlayRingtones
 //
-//  Created by 100Years on 2025/3/4.
+//  Created by mini on 2025/5/28.
 //
 
-
-class XHInputView: UITextView {
+class ASInputView: UITextView {
     
     // MARK: - UI Components
     private lazy var hintLabel: ASTLLabel = {

+ 171 - 0
AIPlayRingtones/CommonView/ASSaveResultManager.swift

@@ -0,0 +1,171 @@
+//
+//  ASSaveResultManager.swift
+//  AIPlayRingtones
+//
+//  Created by mini on 2025/5/28.
+//
+
+public let kSuccessManager = ASSaveResultManager.shared
+
+open class ASSaveResultManager {
+    
+    static let shared = ASSaveResultManager()
+    
+    public var completionHandler: (() -> Void)?
+    
+    private lazy var messageLabel: UILabel = {
+        let label = UILabel()
+        label.textColor = .white
+        label.text = "Save Successfully".localized
+        label.font = .systemFont(ofSize: 14)
+        return label
+    }()
+    
+    private lazy var containerView: UIView = {
+        return createNotificationView()
+    }()
+    
+    private lazy var actionButton: UIView = {
+        let color = "4FEA9D".uiColor
+        let button = UIButton.create(
+            title: "View".localized,
+            backgroundColor: color.withAlphaComponent(0.1),
+            font: .systemFont(ofSize: 12),
+            titleColor: color,
+            cornerRadius: 14
+        ) { [weak self] in
+            self?.handleButtonAction()
+        }
+        button.contentEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
+        return button
+    }()
+    
+    private func createNotificationView() -> UIView {
+        let backgroundView = UIView(frame: CGRect(x: 0, y: 0, width: 288, height: 48))
+        backgroundView.backgroundColor = .clear
+        backgroundView.layer.configureShadow(
+            color: .black,
+            offset: CGSize(width: 0, height: 2),
+            opacity: 0.1
+        )
+        
+        let contentView = UIView()
+        contentView.backgroundColor = "333333".uiColor
+        contentView.layer.cornerRadius = 8
+        contentView.clipsToBounds = true
+        
+        backgroundView.addSubview(contentView)
+        contentView.snp.makeConstraints { $0.edges.equalToSuperview() }
+        
+        let icon = UIImageView(image: UIImage(named: "success_icon"))
+        backgroundView.addSubview(icon)
+        icon.snp.makeConstraints {
+            $0.size.equalTo(24)
+            $0.centerY.equalToSuperview()
+            $0.leading.equalTo(12)
+        }
+        
+        backgroundView.addSubview(actionButton)
+        backgroundView.addSubview(messageLabel)
+        
+        actionButton.snp.makeConstraints {
+            $0.width.equalTo(actionButton.intrinsicContentSize.width)
+            $0.height.equalTo(28)
+            $0.trailing.equalTo(-8)
+            $0.centerY.equalToSuperview()
+        }
+        
+        messageLabel.snp.makeConstraints {
+            $0.leading.equalTo(icon.snp.trailing).offset(8)
+            $0.trailing.equalTo(actionButton.snp.leading).offset(-8)
+            $0.centerY.equalToSuperview()
+        }
+        
+        return backgroundView
+    }
+    
+    public func calculateBottomPosition(from topPosition: CGFloat) -> CGFloat {
+        let position = -(UIScreen.main.bounds.height - 48 - topPosition)
+        print("Calculated position: \(position)")
+        return position
+    }
+    
+    public func displayNotification(
+        in view: UIView,
+        message: String = "Save Successfully".localized,
+        duration: Double = 2.0,
+        position: CGFloat = -112,
+        showAction: Bool = true,
+        handler: (() -> Void)? = nil
+    ) {
+        self.completionHandler = handler
+        
+        DispatchQueue.main.async {
+            self.messageLabel.text = message
+            self.actionButton.isHidden = !showAction
+            view.addSubview(self.containerView)
+            
+            self.containerView.snp.remakeConstraints {
+                $0.width.greaterThanOrEqualTo(288)
+                $0.width.lessThanOrEqualTo(UIScreen.main.bounds.width - 32)
+                $0.height.equalTo(48)
+                $0.centerX.equalToSuperview()
+                $0.bottom.equalTo(position)
+            }
+            
+            DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
+                self.containerView.removeFromSuperview()
+            }
+        }
+    }
+    
+    private func handleButtonAction() {
+        if let handler = completionHandler {
+            handler()
+        } else {
+            if let url = URL(string: "photos-redirect://"),
+               UIApplication.shared.canOpenURL(url) {
+                UIApplication.shared.open(url, options: [:])
+            }
+        }
+        DispatchQueue.main.async {
+            self.containerView.removeFromSuperview()
+        }
+    }
+}
+
+// MARK: - Extensions
+private extension CALayer {
+    func configureShadow(color: UIColor, offset: CGSize, opacity: Float) {
+        shadowColor = color.cgColor
+        shadowOffset = offset
+        shadowOpacity = opacity
+    }
+}
+
+private extension UIButton {
+    static func create(
+        title: String,
+        backgroundColor: UIColor,
+        font: UIFont,
+        titleColor: UIColor,
+        cornerRadius: CGFloat,
+        action: @escaping () -> Void
+    ) -> UIButton {
+        let button = UIButton()
+        button.setTitle(title, for: .normal)
+        button.backgroundColor = backgroundColor
+        button.titleLabel?.font = font
+        button.setTitleColor(titleColor, for: .normal)
+        button.layer.cornerRadius = cornerRadius
+        button.addAction(for: .touchUpInside, action)
+        return button
+    }
+}
+
+private extension UIControl {
+    func addAction(for event: UIControl.Event, _ closure: @escaping () -> Void) {
+        let action = UIAction { _ in closure() }
+        addAction(action, for: event)
+    }
+}

+ 138 - 0
AIPlayRingtones/CommonView/XHInputView.swift

@@ -0,0 +1,138 @@
+//
+//  ASInputView.swift
+//  AIPlayRingtones
+//
+//  Created by mini on 2025/5/28.
+//
+
+class ASInputView: UITextView {
+    
+    // MARK: - UI Components
+    private lazy var hintLabel: ASTLLabel = {
+        let label = ASTLLabel()
+        label.font = self.font
+        label.textColor = hintTextColor
+        label.text = hintText
+        label.numberOfLines = 0
+        label.isUserInteractionEnabled = false
+        label.textAlignment = .left
+        return label
+    }()
+    
+    // MARK: - Properties
+    var maximumCharacters: Int = 1000 {
+        didSet {
+            validateCharacterCount(inputView: self)
+        }
+    }
+    
+    var hintText: String? {
+        didSet {
+            hintLabel.text = hintText
+        }
+    }
+    
+    var hintTextColor: UIColor = .lightGray {
+        didSet {
+            hintLabel.textColor = hintTextColor
+        }
+    }
+    
+    var contentInsets: UIEdgeInsets = .zero {
+        didSet {
+            adjustContentInsets()
+        }
+    }
+    
+    override var text: String! {
+        didSet {
+            toggleHintVisibility()
+        }
+    }
+    
+    var onTextUpdate: (() -> Void)?
+    
+    // MARK: - Initialization
+    init(hint: String? = nil,
+                    initialText: String? = nil,
+                    textFont: UIFont? = nil,
+                    contentColor: UIColor = .black,
+                    background: UIColor = .white,
+                    insets: UIEdgeInsets = .zero) {
+        super.init(frame: .zero, textContainer: nil)
+        
+        self.hintText = hint
+        self.font = textFont
+        self.textColor = contentColor
+        self.backgroundColor = background
+        self.contentInsets = insets
+        
+        configureHintDisplay()
+        self.text = initialText
+    }
+    
+    required init?(coder: NSCoder) {
+        super.init(coder: coder)
+        configureHintDisplay()
+    }
+    
+    deinit {
+        NotificationCenter.default.removeObserver(self)
+    }
+    
+    // MARK: - Configuration
+    private func configureHintDisplay() {
+        addSubview(hintLabel)
+        toggleHintVisibility()
+        
+        NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(handleTextUpdate(_:)),
+            name: UITextView.textDidChangeNotification,
+            object: self
+        )
+        
+        adjustContentInsets()
+    }
+    
+    // MARK: - Layout
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        
+        let xPosition = contentInsets.left + 5
+        let yPosition = contentInsets.top
+        let labelWidth = bounds.width - contentInsets.left - contentInsets.right - 10
+        let labelHeight = bounds.height - contentInsets.top - contentInsets.bottom
+        
+        hintLabel.frame = CGRect(
+            x: xPosition,
+            y: yPosition,
+            width: labelWidth,
+            height: labelHeight
+        )
+    }
+    
+    // MARK: - Helper Methods
+    private func toggleHintVisibility() {
+        hintLabel.isHidden = !text.isEmpty
+    }
+    
+    @objc private func handleTextUpdate(_ notification: Notification) {
+        if let textView = notification.object as? UITextView {
+            validateCharacterCount(inputView: textView)
+            onTextUpdate?()
+        }
+        toggleHintVisibility()
+    }
+    
+    func validateCharacterCount(inputView: UITextView) {
+        if inputView.text.count > maximumCharacters {
+            inputView.text = String(inputView.text.prefix(maximumCharacters))
+        }
+    }
+    
+    private func adjustContentInsets() {
+        textContainerInset = contentInsets
+        setNeedsLayout()
+    }
+}

+ 1 - 1
AIPlayRingtones/OperationQueue/Generate/ASGenerateTextToRingOperation.swift

@@ -100,7 +100,7 @@ class ASGenerateTextToRingOperation: ASGenerateBaseOperation , @unchecked Sendab
             let topY = k_Nav_Height+10
 //            logPrint("topY=\(topY)")
             AudioServicesPlaySystemSound(1520)
-            kSaveSuccesswShared.show(atView: window,text: "Successfully generated".localized,deadline: 5.0,bottom: kSaveSuccesswShared.getBottom(topY: topY)) {
+            kSuccessManager.displayNotification(in: window,message: "Successfully generated".localized,duration:5.0,position: kSuccessManager.calculateBottomPosition(from: topY)){
                 let gennerateVC = ASRingGeneratorVC(generateStyleModel: ASGenerateStyleModel(),infoModel: cyModel) { model in }
                 gennerateVC.modalPresentationStyle = .overFullScreen
                 gennerateVC.modalTransitionStyle = .crossDissolve