mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Voice message record button hapitcs (#2013)
* Move tooltip logics in VoiceMessageRecordingButton * Add hapitc feedback when recording a voice message * Cleanup
This commit is contained in:
@@ -42,3 +42,9 @@ struct ViewFrameReader: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func readFrame(_ frame: Binding<CGRect>, in coordinateSpace: CoordinateSpace = .local) -> some View {
|
||||||
|
background(ViewFrameReader(frame: frame, coordinateSpace: coordinateSpace))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,15 +27,6 @@ struct ComposerToolbar: View {
|
|||||||
@ScaledMetric private var sendButtonIconSize = 16
|
@ScaledMetric private var sendButtonIconSize = 16
|
||||||
@ScaledMetric(relativeTo: .title) private var spinnerSize = 44
|
@ScaledMetric(relativeTo: .title) private var spinnerSize = 44
|
||||||
@ScaledMetric(relativeTo: .title) private var closeRTEButtonSize = 30
|
@ScaledMetric(relativeTo: .title) private var closeRTEButtonSize = 30
|
||||||
|
|
||||||
@State private var voiceMessageRecordingStartTime: Date?
|
|
||||||
@State private var showVoiceMessageRecordingTooltip = false
|
|
||||||
@ScaledMetric private var voiceMessageTooltipPointerHeight = 6
|
|
||||||
@State private var voiceMessageRecordingButtonFrame: CGRect = .zero
|
|
||||||
|
|
||||||
private let voiceMessageMinimumRecordingDuration = 1.0
|
|
||||||
private let voiceMessageTooltipDuration = 1.0
|
|
||||||
|
|
||||||
@State private var frame: CGRect = .zero
|
@State private var frame: CGRect = .zero
|
||||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
|
|
||||||
@@ -64,12 +55,6 @@ struct ComposerToolbar: View {
|
|||||||
.offset(y: -frame.height)
|
.offset(y: -frame.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(alignment: .bottomTrailing) {
|
|
||||||
if showVoiceMessageRecordingTooltip {
|
|
||||||
voiceMessageRecordingButtonTooltipView
|
|
||||||
.offset(y: -frame.height - voiceMessageTooltipPointerHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.alert(item: $context.alertInfo)
|
.alert(item: $context.alertInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +80,6 @@ struct ComposerToolbar: View {
|
|||||||
.padding(.leading, 3)
|
.padding(.leading, 3)
|
||||||
} else {
|
} else {
|
||||||
voiceMessageRecordingButton
|
voiceMessageRecordingButton
|
||||||
.background {
|
|
||||||
ViewFrameReader(frame: $voiceMessageRecordingButtonFrame, coordinateSpace: .global)
|
|
||||||
}
|
|
||||||
.padding(.leading, 4)
|
.padding(.leading, 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,17 +252,12 @@ struct ComposerToolbar: View {
|
|||||||
|
|
||||||
private var voiceMessageRecordingButton: some View {
|
private var voiceMessageRecordingButton: some View {
|
||||||
VoiceMessageRecordingButton {
|
VoiceMessageRecordingButton {
|
||||||
showVoiceMessageRecordingTooltip = false
|
|
||||||
voiceMessageRecordingStartTime = Date.now
|
|
||||||
context.send(viewAction: .startVoiceMessageRecording)
|
context.send(viewAction: .startVoiceMessageRecording)
|
||||||
} stopRecording: {
|
} stopRecording: { minimumRecordTimeReached in
|
||||||
if let voiceMessageRecordingStartTime, Date.now.timeIntervalSince(voiceMessageRecordingStartTime) < voiceMessageMinimumRecordingDuration {
|
if minimumRecordTimeReached {
|
||||||
context.send(viewAction: .cancelVoiceMessageRecording)
|
|
||||||
withElementAnimation {
|
|
||||||
showVoiceMessageRecordingTooltip = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.send(viewAction: .stopVoiceMessageRecording)
|
context.send(viewAction: .stopVoiceMessageRecording)
|
||||||
|
} else {
|
||||||
|
context.send(viewAction: .cancelVoiceMessageRecording)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(4)
|
.padding(4)
|
||||||
@@ -297,22 +274,6 @@ struct ComposerToolbar: View {
|
|||||||
.accessibilityLabel(L10n.a11yDelete)
|
.accessibilityLabel(L10n.a11yDelete)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var voiceMessageRecordingButtonTooltipView: some View {
|
|
||||||
VoiceMessageRecordingButtonTooltipView(text: L10n.screenRoomVoiceMessageTooltip,
|
|
||||||
pointerHeight: voiceMessageTooltipPointerHeight,
|
|
||||||
pointerLocation: voiceMessageRecordingButtonFrame.midX,
|
|
||||||
pointerLocationCoordinateSpace: .global)
|
|
||||||
.allowsHitTesting(false)
|
|
||||||
.onAppear {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + voiceMessageTooltipDuration) {
|
|
||||||
withElementAnimation {
|
|
||||||
showVoiceMessageRecordingTooltip = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.horizontal, 8)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func voiceMessagePreviewComposer(audioPlayerState: AudioPlayerState, waveform: WaveformSource) -> some View {
|
private func voiceMessagePreviewComposer(audioPlayerState: AudioPlayerState, waveform: WaveformSource) -> some View {
|
||||||
VoiceMessagePreviewComposer(playerState: audioPlayerState, waveform: waveform) {
|
VoiceMessagePreviewComposer(playerState: audioPlayerState, waveform: waveform) {
|
||||||
context.send(viewAction: .startVoiceMessagePlayback)
|
context.send(viewAction: .startVoiceMessagePlayback)
|
||||||
|
|||||||
@@ -18,33 +18,72 @@ import Compound
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct VoiceMessageRecordingButton: View {
|
struct VoiceMessageRecordingButton: View {
|
||||||
@State private var buttonPressed = false
|
|
||||||
|
|
||||||
var startRecording: (() -> Void)?
|
var startRecording: (() -> Void)?
|
||||||
var stopRecording: (() -> Void)?
|
var stopRecording: ((_ minimumRecordTimeReached: Bool) -> Void)?
|
||||||
|
|
||||||
|
@ScaledMetric private var tooltipPointerHeight = 6
|
||||||
|
|
||||||
|
@State private var buttonPressed = false
|
||||||
|
@State private var recordingStartTime: Date?
|
||||||
|
@State private var showTooltip = false
|
||||||
|
@State private var frame: CGRect = .zero
|
||||||
|
|
||||||
|
private let minimumRecordingDuration = 1.0
|
||||||
|
private let tooltipDuration = 1.0
|
||||||
|
private let impactFeedbackGenerator = UIImpactFeedbackGenerator()
|
||||||
|
private let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button { } label: {
|
Button { } label: {
|
||||||
voiceMessageButtonImage
|
CompoundIcon(buttonPressed ? \.micOnSolid : \.micOnOutline)
|
||||||
|
.foregroundColor(.compound.iconSecondary)
|
||||||
|
.padding(EdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 6))
|
||||||
}
|
}
|
||||||
|
.readFrame($frame, in: .global)
|
||||||
|
.accessibilityLabel(L10n.a11yVoiceMessageRecord)
|
||||||
.onLongPressGesture { } onPressingChanged: { isPressing in
|
.onLongPressGesture { } onPressingChanged: { isPressing in
|
||||||
buttonPressed = isPressing
|
buttonPressed = isPressing
|
||||||
|
|
||||||
if isPressing {
|
if isPressing {
|
||||||
// Start recording
|
showTooltip = false
|
||||||
|
recordingStartTime = Date.now
|
||||||
|
impactFeedbackGenerator.impactOccurred()
|
||||||
startRecording?()
|
startRecording?()
|
||||||
} else {
|
} else {
|
||||||
// Stop recording
|
if let recordingStartTime, Date.now.timeIntervalSince(recordingStartTime) < minimumRecordingDuration {
|
||||||
stopRecording?()
|
withElementAnimation {
|
||||||
|
showTooltip = true
|
||||||
|
}
|
||||||
|
notificationFeedbackGenerator.notificationOccurred(.error)
|
||||||
|
stopRecording?(false)
|
||||||
|
} else {
|
||||||
|
impactFeedbackGenerator.impactOccurred()
|
||||||
|
stopRecording?(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.overlay(alignment: .bottomTrailing) {
|
||||||
|
if showTooltip {
|
||||||
|
tooltipView
|
||||||
|
.offset(y: -frame.height - tooltipPointerHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
private var tooltipView: some View {
|
||||||
private var voiceMessageButtonImage: some View {
|
VoiceMessageRecordingButtonTooltipView(text: L10n.screenRoomVoiceMessageTooltip,
|
||||||
CompoundIcon(buttonPressed ? \.micOnSolid : \.micOnOutline)
|
pointerHeight: tooltipPointerHeight,
|
||||||
.foregroundColor(.compound.iconSecondary)
|
pointerLocation: frame.midX,
|
||||||
.padding(EdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 6))
|
pointerLocationCoordinateSpace: .global)
|
||||||
.accessibilityLabel(L10n.a11yVoiceMessageRecord)
|
.allowsHitTesting(false)
|
||||||
|
.fixedSize()
|
||||||
|
.onAppear {
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + tooltipDuration) {
|
||||||
|
withElementAnimation {
|
||||||
|
showTooltip = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,8 +141,10 @@ class AudioRecorder: NSObject, AudioRecorderProtocol, AVAudioRecorderDelegate {
|
|||||||
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
|
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
|
let audioSession = AVAudioSession.sharedInstance()
|
||||||
try AVAudioSession.sharedInstance().setActive(true)
|
try audioSession.setAllowHapticsAndSystemSoundsDuringRecording(true)
|
||||||
|
try audioSession.setCategory(.playAndRecord, mode: .default)
|
||||||
|
try audioSession.setActive(true)
|
||||||
let url = URL.temporaryDirectory.appendingPathComponent("voice-message-\(recordID.identifier).m4a")
|
let url = URL.temporaryDirectory.appendingPathComponent("voice-message-\(recordID.identifier).m4a")
|
||||||
let audioRecorder = try AVAudioRecorder(url: url, settings: settings)
|
let audioRecorder = try AVAudioRecorder(url: url, settings: settings)
|
||||||
audioRecorder.delegate = self
|
audioRecorder.delegate = self
|
||||||
|
|||||||
Reference in New Issue
Block a user