From 8b1727fcaa1a745c22ba90af99afbe3e9f0eb4e6 Mon Sep 17 00:00:00 2001 From: mh <729263080@qq.com> Date: Thu, 6 Nov 2025 11:50:29 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=92=E8=89=B2=E4=BC=9A=E8=AF=9D=E9=95=BF?= =?UTF-8?q?=E6=8C=89=E8=AF=B4=E8=AF=9D=E5=90=91=E4=B8=8A=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E5=BD=95=E9=9F=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chat/Session/Input/IMVoiceHoldView.swift | 60 ++++++++++++++++--- .../Input/SessionInputOperateView.swift | 16 ++++- .../Session/SessionController+Input.swift | 19 ++++++ 3 files changed, 86 insertions(+), 9 deletions(-) diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/IMVoiceHoldView.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/IMVoiceHoldView.swift index 6a13787..6e93178 100644 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/IMVoiceHoldView.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/IMVoiceHoldView.swift @@ -28,6 +28,7 @@ class IMVoiceHoldView: UIView { var audioDuration: Int = 0 var counting: Int = 0 private var isRecording: Bool = false + private var isInCancelArea: Bool = false // 是否在取消区域 public var audioPathUrl: URL? var recordFinishedAction: ((_ url: URL?) -> Void)? @@ -129,7 +130,8 @@ class IMVoiceHoldView: UIView { stackView.spacing = 5.0 stackView.distribution = .fill stackView.alignment = .fill - insertSubview(stackView, belowSubview: overlayBg) +// insertSubview(stackView, belowSubview: overlayBg) + insertSubview(stackView, aboveSubview: overlayBg) stackView.snp.makeConstraints { make in make.centerX.equalToSuperview() make.bottom.equalTo(voiceIconDecorationView.snp.top).offset(-15) @@ -155,6 +157,23 @@ class IMVoiceHoldView: UIView { private func setupData() { recordTool = AudioRecordTool() } + + // MARK: - 更新取消状态 + func updateCancelState(isInCancelArea: Bool) { + guard self.isInCancelArea != isInCancelArea else { return } + + self.isInCancelArea = isInCancelArea + + if isInCancelArea { + // 进入取消区域 + voiceIconDecorationView.image = UIImage(named: "role_chat_voice_cancel") + tipLabel.text = "Release to cancel" + } else { + // 退出取消区域 + voiceIconDecorationView.image = UIImage(named: "role_chat_voice_normal") + tipLabel.text = "Release to send" + } + } private func setupEvent() { recordTool.timerChangedBlock = { [weak self] counting in @@ -178,32 +197,59 @@ class IMVoiceHoldView: UIView { isRecording = on if on { + // 重置取消状态 + isInCancelArea = false + voiceIconDecorationView.image = UIImage(named: "role_chat_voice_normal") + tipLabel.text = "Release to send" + wave.play() recordTool.startRecord { [weak self] audioURL in self?.audioPathUrl = audioURL dlog("☁️🎤audioURL: \(String(describing: audioURL))") - if let count = self?.counting, count < 10{ - Hud.toast(str: "语音时间太短") - return + guard let self = self else { return } + + // 检查是否在取消区域(录音停止时) + // 如果不在取消区域,才发送录音 + if !self.isInCancelArea { + if self.counting < 10 { + Hud.toast(str: "语音时间太短") + return + } + self.recordFinishedAction?(audioURL) + } else { + // 在取消区域,不发送录音 + dlog("☁️🎤录音已取消(在取消区域)") } - self?.recordFinishedAction?(audioURL) } } else { wave.stop() - stopRecord() + // 如果松手时在取消区域,取消录音 + if isInCancelArea { + cancelRecord() + } else { + stopRecord() + } } } // MARK: - Functions func stopRecord() { + // 停止录音,会触发 startRecord 的回调 recordTool.stopRecord() // if counting < 10{ // Hud.toast(str: "语音时间太短") // return // } - + } + + func cancelRecord() { + // 取消录音:停止录音但不发送 + // 设置取消标志,确保回调中不会发送录音 + recordTool.stopRecord() + audioPathUrl = nil + dlog("☁️🎤录音已取消") } // MARK: - Other diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/SessionInputOperateView.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/SessionInputOperateView.swift index d35b233..bfa54c9 100644 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/SessionInputOperateView.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/Input/SessionInputOperateView.swift @@ -14,6 +14,8 @@ protocol SessionInputOperateViewDelegate: AnyObject { func operateTapMoreAction() func operateTapHelpAction() func operateTapInputFieldAction() + // 拖动手势位置变化 + func operateVoiceDragAction(location: CGPoint) } enum InputOperateState { @@ -223,7 +225,7 @@ class SessionInputOperateView: UIView { // 添加长按手势 let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:))) - longPress.minimumPressDuration = 0.2 // 长按时间,默认 0.5 秒 + longPress.minimumPressDuration = 0.2 // 长按时间,默认 0.2 秒 voiceHoldView.addGestureRecognizer(longPress) $state.sink { [weak self] state in @@ -311,7 +313,8 @@ class SessionInputOperateView: UIView { case .began: delegate?.operateVoiceAction(on: true) case .changed: - break + // 处理拖动,检测是否超出 voiceIconDecorationView 顶部 + handleDragGesture(gesture) case .ended: delegate?.operateVoiceAction(on: false) case .cancelled, .failed: @@ -320,6 +323,15 @@ class SessionInputOperateView: UIView { break } } + + private func handleDragGesture(_ gesture: UILongPressGestureRecognizer) { + // 获取手势在 window 中的位置 + guard let window = self.window else { return } + let location = gesture.location(in: window) + + // 通知 delegate 拖动位置变化 + delegate?.operateVoiceDragAction(location: location) + } } extension SessionInputOperateView: UITextFieldDelegate { diff --git a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Input.swift b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Input.swift index 5281840..ae2d76c 100755 --- a/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Input.swift +++ b/Visual_Novel_iOS/Src/Modules/Chat/Session/SessionController+Input.swift @@ -255,6 +255,25 @@ extension SessionController: SessionInputOperateViewDelegate{ voiceHoldView.record(on: on) } + func operateVoiceDragAction(location: CGPoint) { + // 确保 voiceHoldView 已经布局 + voiceHoldView.layoutIfNeeded() + + // 将 window 坐标转换为 voiceHoldView 中的坐标 + let locationInVoiceHoldView = voiceHoldView.convert(location, from: nil) + + // 获取 voiceIconDecorationView 在 voiceHoldView 中的 frame + // 由于 voiceIconDecorationView 是 voiceHoldView 的子视图,frame 已经是相对于 voiceHoldView 的 + let decorationViewFrame = voiceHoldView.voiceIconDecorationView.frame + + // 判断手势是否超出 voiceIconDecorationView 顶部 + // 如果手势的 y 坐标小于 decorationView 的顶部,说明在取消区域 + let isInCancelArea = locationInVoiceHoldView.y < decorationViewFrame.origin.y + + // 更新取消状态 + voiceHoldView.updateCancelState(isInCancelArea: isInCancelArea) + } + func operateTapMoreAction() { view.endEditing(true)