角色会话长按说话向上取消录音
This commit is contained in:
parent
04b2131efc
commit
8b1727fcaa
|
|
@ -28,6 +28,7 @@ class IMVoiceHoldView: UIView {
|
||||||
var audioDuration: Int = 0
|
var audioDuration: Int = 0
|
||||||
var counting: Int = 0
|
var counting: Int = 0
|
||||||
private var isRecording: Bool = false
|
private var isRecording: Bool = false
|
||||||
|
private var isInCancelArea: Bool = false // 是否在取消区域
|
||||||
public var audioPathUrl: URL?
|
public var audioPathUrl: URL?
|
||||||
|
|
||||||
var recordFinishedAction: ((_ url: URL?) -> Void)?
|
var recordFinishedAction: ((_ url: URL?) -> Void)?
|
||||||
|
|
@ -129,7 +130,8 @@ class IMVoiceHoldView: UIView {
|
||||||
stackView.spacing = 5.0
|
stackView.spacing = 5.0
|
||||||
stackView.distribution = .fill
|
stackView.distribution = .fill
|
||||||
stackView.alignment = .fill
|
stackView.alignment = .fill
|
||||||
insertSubview(stackView, belowSubview: overlayBg)
|
// insertSubview(stackView, belowSubview: overlayBg)
|
||||||
|
insertSubview(stackView, aboveSubview: overlayBg)
|
||||||
stackView.snp.makeConstraints { make in
|
stackView.snp.makeConstraints { make in
|
||||||
make.centerX.equalToSuperview()
|
make.centerX.equalToSuperview()
|
||||||
make.bottom.equalTo(voiceIconDecorationView.snp.top).offset(-15)
|
make.bottom.equalTo(voiceIconDecorationView.snp.top).offset(-15)
|
||||||
|
|
@ -155,6 +157,23 @@ class IMVoiceHoldView: UIView {
|
||||||
private func setupData() {
|
private func setupData() {
|
||||||
recordTool = AudioRecordTool()
|
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() {
|
private func setupEvent() {
|
||||||
recordTool.timerChangedBlock = { [weak self] counting in
|
recordTool.timerChangedBlock = { [weak self] counting in
|
||||||
|
|
@ -178,32 +197,59 @@ class IMVoiceHoldView: UIView {
|
||||||
isRecording = on
|
isRecording = on
|
||||||
|
|
||||||
if on {
|
if on {
|
||||||
|
// 重置取消状态
|
||||||
|
isInCancelArea = false
|
||||||
|
voiceIconDecorationView.image = UIImage(named: "role_chat_voice_normal")
|
||||||
|
tipLabel.text = "Release to send"
|
||||||
|
|
||||||
wave.play()
|
wave.play()
|
||||||
recordTool.startRecord { [weak self] audioURL in
|
recordTool.startRecord { [weak self] audioURL in
|
||||||
self?.audioPathUrl = audioURL
|
self?.audioPathUrl = audioURL
|
||||||
dlog("☁️🎤audioURL: \(String(describing: audioURL))")
|
dlog("☁️🎤audioURL: \(String(describing: audioURL))")
|
||||||
|
|
||||||
if let count = self?.counting, count < 10{
|
guard let self = self else { return }
|
||||||
Hud.toast(str: "语音时间太短")
|
|
||||||
return
|
// 检查是否在取消区域(录音停止时)
|
||||||
|
// 如果不在取消区域,才发送录音
|
||||||
|
if !self.isInCancelArea {
|
||||||
|
if self.counting < 10 {
|
||||||
|
Hud.toast(str: "语音时间太短")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.recordFinishedAction?(audioURL)
|
||||||
|
} else {
|
||||||
|
// 在取消区域,不发送录音
|
||||||
|
dlog("☁️🎤录音已取消(在取消区域)")
|
||||||
}
|
}
|
||||||
self?.recordFinishedAction?(audioURL)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wave.stop()
|
wave.stop()
|
||||||
stopRecord()
|
// 如果松手时在取消区域,取消录音
|
||||||
|
if isInCancelArea {
|
||||||
|
cancelRecord()
|
||||||
|
} else {
|
||||||
|
stopRecord()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Functions
|
// MARK: - Functions
|
||||||
|
|
||||||
func stopRecord() {
|
func stopRecord() {
|
||||||
|
// 停止录音,会触发 startRecord 的回调
|
||||||
recordTool.stopRecord()
|
recordTool.stopRecord()
|
||||||
// if counting < 10{
|
// if counting < 10{
|
||||||
// Hud.toast(str: "语音时间太短")
|
// Hud.toast(str: "语音时间太短")
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelRecord() {
|
||||||
|
// 取消录音:停止录音但不发送
|
||||||
|
// 设置取消标志,确保回调中不会发送录音
|
||||||
|
recordTool.stopRecord()
|
||||||
|
audioPathUrl = nil
|
||||||
|
dlog("☁️🎤录音已取消")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Other
|
// MARK: - Other
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ protocol SessionInputOperateViewDelegate: AnyObject {
|
||||||
func operateTapMoreAction()
|
func operateTapMoreAction()
|
||||||
func operateTapHelpAction()
|
func operateTapHelpAction()
|
||||||
func operateTapInputFieldAction()
|
func operateTapInputFieldAction()
|
||||||
|
// 拖动手势位置变化
|
||||||
|
func operateVoiceDragAction(location: CGPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InputOperateState {
|
enum InputOperateState {
|
||||||
|
|
@ -223,7 +225,7 @@ class SessionInputOperateView: UIView {
|
||||||
|
|
||||||
// 添加长按手势
|
// 添加长按手势
|
||||||
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
||||||
longPress.minimumPressDuration = 0.2 // 长按时间,默认 0.5 秒
|
longPress.minimumPressDuration = 0.2 // 长按时间,默认 0.2 秒
|
||||||
voiceHoldView.addGestureRecognizer(longPress)
|
voiceHoldView.addGestureRecognizer(longPress)
|
||||||
|
|
||||||
$state.sink { [weak self] state in
|
$state.sink { [weak self] state in
|
||||||
|
|
@ -311,7 +313,8 @@ class SessionInputOperateView: UIView {
|
||||||
case .began:
|
case .began:
|
||||||
delegate?.operateVoiceAction(on: true)
|
delegate?.operateVoiceAction(on: true)
|
||||||
case .changed:
|
case .changed:
|
||||||
break
|
// 处理拖动,检测是否超出 voiceIconDecorationView 顶部
|
||||||
|
handleDragGesture(gesture)
|
||||||
case .ended:
|
case .ended:
|
||||||
delegate?.operateVoiceAction(on: false)
|
delegate?.operateVoiceAction(on: false)
|
||||||
case .cancelled, .failed:
|
case .cancelled, .failed:
|
||||||
|
|
@ -320,6 +323,15 @@ class SessionInputOperateView: UIView {
|
||||||
break
|
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 {
|
extension SessionInputOperateView: UITextFieldDelegate {
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,25 @@ extension SessionController: SessionInputOperateViewDelegate{
|
||||||
voiceHoldView.record(on: on)
|
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() {
|
func operateTapMoreAction() {
|
||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue