mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-26 11:30:50 +02:00
#4094 - Added voice messages locked mode playback.
This commit is contained in:
@@ -33,7 +33,6 @@ enum VoiceMessageAudioPlayerError: Error {
|
||||
|
||||
class VoiceMessageAudioPlayer: NSObject {
|
||||
|
||||
private var contentURL: URL!
|
||||
private var playerItem: AVPlayerItem?
|
||||
private var audioPlayer: AVPlayer?
|
||||
|
||||
@@ -44,6 +43,8 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
|
||||
weak var delegate: VoiceMessageAudioPlayerDelegate?
|
||||
|
||||
private(set) var url: URL?
|
||||
|
||||
var isPlaying: Bool {
|
||||
guard let audioPlayer = audioPlayer else {
|
||||
return false
|
||||
@@ -57,7 +58,9 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
return 0
|
||||
}
|
||||
|
||||
return CMTimeGetSeconds(item.duration)
|
||||
let duration = CMTimeGetSeconds(item.duration)
|
||||
|
||||
return duration.isNaN ? 0.0 : duration
|
||||
}
|
||||
|
||||
var currentTime: TimeInterval {
|
||||
@@ -76,21 +79,18 @@ class VoiceMessageAudioPlayer: NSObject {
|
||||
removeObservers()
|
||||
}
|
||||
|
||||
override init() {
|
||||
audioPlayer = AVPlayer()
|
||||
}
|
||||
|
||||
func loadContentFromURL(_ url: URL) {
|
||||
if contentURL == url {
|
||||
if self.url == url {
|
||||
return
|
||||
}
|
||||
|
||||
self.url = url
|
||||
|
||||
removeObservers()
|
||||
|
||||
delegate?.audioPlayerDidStartLoading(self)
|
||||
|
||||
contentURL = url
|
||||
playerItem = AVPlayerItem(url: contentURL)
|
||||
|
||||
playerItem = AVPlayerItem(url: url)
|
||||
audioPlayer = AVPlayer(playerItem: playerItem)
|
||||
|
||||
addObservers()
|
||||
|
||||
@@ -64,7 +64,6 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
|
||||
} catch {
|
||||
delegate?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func stopRecording() {
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
import DSWaveformImage
|
||||
|
||||
@objc public protocol VoiceMessageControllerDelegate: AnyObject {
|
||||
func voiceMessageControllerDidRequestMicrophonePermission(_ voiceMessageController: VoiceMessageController)
|
||||
func voiceMessageController(_ voiceMessageController: VoiceMessageController, didRequestSendForFileAtURL url: URL, completion: @escaping (Bool) -> Void)
|
||||
}
|
||||
|
||||
public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, VoiceMessageAudioRecorderDelegate {
|
||||
public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, VoiceMessageAudioRecorderDelegate, VoiceMessageAudioPlayerDelegate {
|
||||
|
||||
private let themeService: ThemeService
|
||||
private let _voiceMessageToolbarView: VoiceMessageToolbarView
|
||||
@@ -31,6 +32,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
|
||||
private var audioRecorder: VoiceMessageAudioRecorder?
|
||||
|
||||
private var audioPlayer: VoiceMessageAudioPlayer?
|
||||
private var waveformAnalyser: WaveformAnalyzer?
|
||||
|
||||
private var audioSamples: [Float] = []
|
||||
private var isInLockedMode: Bool = false
|
||||
|
||||
@@ -55,7 +59,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
displayLink.isPaused = true
|
||||
displayLink.add(to: .current, forMode: .common)
|
||||
|
||||
self._voiceMessageToolbarView.update(theme: self.themeService.theme)
|
||||
_voiceMessageToolbarView.update(theme: themeService.theme)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(handleThemeDidChange), name: .themeServiceDidChangeTheme, object: nil)
|
||||
|
||||
updateUI()
|
||||
@@ -72,9 +76,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString).appendingPathExtension("m4a")
|
||||
|
||||
self.audioRecorder = VoiceMessageAudioRecorder()
|
||||
self.audioRecorder?.delegate = self
|
||||
self.audioRecorder?.recordWithOuputURL(temporaryFileURL)
|
||||
audioRecorder = VoiceMessageAudioRecorder()
|
||||
audioRecorder?.delegate = self
|
||||
audioRecorder?.recordWithOuputURL(temporaryFileURL)
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView) {
|
||||
@@ -85,10 +89,17 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
return
|
||||
}
|
||||
|
||||
delegate?.voiceMessageController(self, didRequestSendForFileAtURL: url) { [weak self] success in
|
||||
UINotificationFeedbackGenerator().notificationOccurred( (success ? .success : .error))
|
||||
self?.deleteRecordingAtURL(url)
|
||||
guard isInLockedMode else {
|
||||
sendRecordingAtURL(url)
|
||||
return
|
||||
}
|
||||
|
||||
audioPlayer = VoiceMessageAudioPlayer()
|
||||
audioPlayer?.delegate = self
|
||||
audioPlayer?.loadContentFromURL(url)
|
||||
audioSamples = []
|
||||
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView) {
|
||||
@@ -96,6 +107,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
audioRecorder?.stopRecording()
|
||||
deleteRecordingAtURL(audioRecorder?.url)
|
||||
UINotificationFeedbackGenerator().notificationOccurred(.error)
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView) {
|
||||
@@ -103,6 +115,26 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestPlaybackToggle(_ toolbarView: VoiceMessageToolbarView) {
|
||||
if audioPlayer?.isPlaying ?? false {
|
||||
audioPlayer?.pause()
|
||||
} else {
|
||||
audioPlayer?.play()
|
||||
}
|
||||
}
|
||||
|
||||
func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView) {
|
||||
guard let url = audioRecorder?.url else {
|
||||
MXLog.error("Invalid audio recording URL")
|
||||
return
|
||||
}
|
||||
|
||||
sendRecordingAtURL(url)
|
||||
|
||||
isInLockedMode = false
|
||||
updateUI()
|
||||
}
|
||||
|
||||
// MARK: - AudioRecorderDelegate
|
||||
|
||||
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
|
||||
@@ -120,8 +152,36 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
MXLog.error("Failed recording voice message.")
|
||||
}
|
||||
|
||||
// MARK: - VoiceMessageAudioPlayerDelegate
|
||||
|
||||
func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) {
|
||||
audioPlayer.seekToTime(0.0)
|
||||
updateUI()
|
||||
}
|
||||
|
||||
func audioPlayer(_ audioPlayer: VoiceMessageAudioPlayer, didFailWithError: Error) {
|
||||
updateUI()
|
||||
|
||||
MXLog.error("Failed playing voice message.")
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func sendRecordingAtURL(_ url: URL) {
|
||||
delegate?.voiceMessageController(self, didRequestSendForFileAtURL: url) { [weak self] success in
|
||||
UINotificationFeedbackGenerator().notificationOccurred( (success ? .success : .error))
|
||||
self?.deleteRecordingAtURL(url)
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteRecordingAtURL(_ url: URL?) {
|
||||
guard let url = url else {
|
||||
return
|
||||
@@ -135,7 +195,7 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
}
|
||||
|
||||
@objc private func handleThemeDidChange() {
|
||||
self._voiceMessageToolbarView.update(theme: self.themeService.theme)
|
||||
_voiceMessageToolbarView.update(theme: themeService.theme)
|
||||
}
|
||||
|
||||
@objc private func handleDisplayLinkTick() {
|
||||
@@ -143,7 +203,19 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
}
|
||||
|
||||
private func updateUI() {
|
||||
displayLink.isPaused = !(audioRecorder?.isRecording ?? false)
|
||||
|
||||
let shouldUpdateFromAudioPlayer = isInLockedMode && !(audioRecorder?.isRecording ?? false)
|
||||
|
||||
guard shouldUpdateFromAudioPlayer else {
|
||||
updateUIFromAudioRecorder()
|
||||
return
|
||||
}
|
||||
|
||||
updateUIFromAudioPlayer()
|
||||
}
|
||||
|
||||
private func updateUIFromAudioRecorder() {
|
||||
displayLink.isPaused = !(self.audioRecorder?.isRecording ?? false)
|
||||
|
||||
let requiredNumberOfSamples = _voiceMessageToolbarView.getRequiredNumberOfSamples()
|
||||
|
||||
@@ -151,15 +223,53 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
|
||||
audioSamples = [Float](repeating: 0.0, count: requiredNumberOfSamples)
|
||||
}
|
||||
|
||||
if let sample = audioRecorder?.averagePowerForChannelNumber(0) {
|
||||
audioSamples.append(sample)
|
||||
audioSamples.remove(at: 0)
|
||||
let sample = audioRecorder?.averagePowerForChannelNumber(0) ?? 0.0
|
||||
audioSamples.append(sample)
|
||||
audioSamples.remove(at: 0)
|
||||
|
||||
var details = VoiceMessageToolbarViewDetails()
|
||||
details.state = (self.audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
|
||||
details.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: self.audioRecorder?.currentTime ?? 0.0))
|
||||
details.audioSamples = audioSamples
|
||||
_voiceMessageToolbarView.configureWithDetails(details)
|
||||
}
|
||||
|
||||
private func updateUIFromAudioPlayer() {
|
||||
guard let audioPlayer = audioPlayer else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let url = audioPlayer.url else {
|
||||
MXLog.error("Invalid audio player url.")
|
||||
return
|
||||
}
|
||||
|
||||
displayLink.isPaused = !audioPlayer.isPlaying
|
||||
|
||||
let requiredNumberOfSamples = _voiceMessageToolbarView.getRequiredNumberOfSamples()
|
||||
if audioSamples.count != requiredNumberOfSamples {
|
||||
audioSamples = [Float](repeating: 0.0, count: requiredNumberOfSamples)
|
||||
|
||||
waveformAnalyser = WaveformAnalyzer(audioAssetURL: url)
|
||||
waveformAnalyser?.samples(count: requiredNumberOfSamples, completionHandler: { [weak self] samples in
|
||||
guard let samples = samples else {
|
||||
MXLog.error("Could not sample audio recording.")
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self?.audioSamples = samples
|
||||
self?.updateUIFromAudioPlayer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var details = VoiceMessageToolbarViewDetails()
|
||||
details.state = (audioRecorder?.isRecording ?? false ? (isInLockedMode ? .lockedModeRecord : .record) : (isInLockedMode ? .lockedModePlayback : .idle))
|
||||
details.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: audioRecorder?.currentTime ?? 0.0))
|
||||
details.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: (audioPlayer.isPlaying ? audioPlayer.currentTime : audioPlayer.duration)))
|
||||
details.audioSamples = audioSamples
|
||||
details.isPlaying = audioPlayer.isPlaying
|
||||
details.progress = (audioPlayer.duration > 0.0 ? audioPlayer.currentTime / audioPlayer.duration : 0.0)
|
||||
_voiceMessageToolbarView.configureWithDetails(details)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
|
||||
// MARK: - VoiceMessagePlaybackViewDelegate
|
||||
|
||||
func voiceMessagePlaybackViewDidRequestToggle() {
|
||||
func voiceMessagePlaybackViewDidRequestPlaybackToggle() {
|
||||
if audioPlayer.isPlaying {
|
||||
audioPlayer.pause()
|
||||
} else {
|
||||
@@ -149,8 +149,11 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess
|
||||
attachment.prepare({ [weak self] in
|
||||
self?.loadFileAtPath(attachment.cacheFilePath)
|
||||
}, failure: { [weak self] error in
|
||||
MXLog.error("Failed preparing attachment with error: \(String(describing: error))")
|
||||
self?.state = .error
|
||||
// A nil error in this case is a cancellation on the MXMediaLoader
|
||||
if let error = error {
|
||||
MXLog.error("Failed preparing attachment with error: \(String(describing: error))")
|
||||
self?.state = .error
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import Foundation
|
||||
|
||||
protocol VoiceMessagePlaybackViewDelegate: AnyObject {
|
||||
func voiceMessagePlaybackViewDidRequestToggle()
|
||||
func voiceMessagePlaybackViewDidRequestPlaybackToggle()
|
||||
}
|
||||
|
||||
struct VoiceMessagePlaybackViewDetails {
|
||||
@@ -31,7 +31,7 @@ struct VoiceMessagePlaybackViewDetails {
|
||||
|
||||
class VoiceMessagePlaybackView: UIView {
|
||||
|
||||
private var waveformView: VoiceMessageWaveformView!
|
||||
private var _waveformView: VoiceMessageWaveformView!
|
||||
|
||||
@IBOutlet private var backgroundView: UIView!
|
||||
@IBOutlet private var recordingIcon: UIView!
|
||||
@@ -43,6 +43,10 @@ class VoiceMessagePlaybackView: UIView {
|
||||
|
||||
var details: VoiceMessagePlaybackViewDetails?
|
||||
|
||||
var waveformView: UIView {
|
||||
return _waveformView
|
||||
}
|
||||
|
||||
static func instanceFromNib() -> VoiceMessagePlaybackView {
|
||||
let nib = UINib(nibName: "VoiceMessagePlaybackView", bundle: nil)
|
||||
guard let view = nib.instantiate(withOwner: nil, options: nil).first as? Self else {
|
||||
@@ -58,8 +62,8 @@ class VoiceMessagePlaybackView: UIView {
|
||||
|
||||
backgroundView.layer.cornerRadius = 12.0
|
||||
|
||||
waveformView = VoiceMessageWaveformView(frame: waveformContainerView.bounds)
|
||||
waveformContainerView.vc_addSubViewMatchingParent(waveformView)
|
||||
_waveformView = VoiceMessageWaveformView(frame: waveformContainerView.bounds)
|
||||
waveformContainerView.vc_addSubViewMatchingParent(_waveformView)
|
||||
}
|
||||
|
||||
func configureWithDetails(_ details: VoiceMessagePlaybackViewDetails?) {
|
||||
@@ -68,40 +72,51 @@ class VoiceMessagePlaybackView: UIView {
|
||||
}
|
||||
|
||||
playButton.isEnabled = details.playbackEnabled
|
||||
playButton.isHidden = details.recording
|
||||
recordingIcon.isHidden = !details.recording
|
||||
|
||||
UIView.performWithoutAnimation {
|
||||
// UIStackView doesn't respond well to re-setting hidden states https://openradar.appspot.com/22819594
|
||||
if playButton.isHidden != details.recording {
|
||||
playButton.isHidden = details.recording
|
||||
}
|
||||
|
||||
// UIStackView doesn't respond well to re-setting hidden states https://openradar.appspot.com/22819594
|
||||
if recordingIcon.isHidden != !details.recording {
|
||||
recordingIcon.isHidden = !details.recording
|
||||
}
|
||||
}
|
||||
|
||||
elapsedTimeLabel.text = details.currentTime
|
||||
waveformView.progress = details.progress
|
||||
_waveformView.progress = details.progress
|
||||
|
||||
if ThemeService.shared().isCurrentThemeDark() {
|
||||
playButton.setImage((details.playing ? Asset.Images.voiceMessagePauseButtonDark.image : Asset.Images.voiceMessagePlayButtonDark.image), for: .normal)
|
||||
backgroundView.backgroundColor = UIColor(rgb: 0x394049)
|
||||
waveformView.primarylineColor = ThemeService.shared().theme.colors.quarterlyContent
|
||||
waveformView.secondaryLineColor = ThemeService.shared().theme.colors.secondaryContent
|
||||
_waveformView.primarylineColor = ThemeService.shared().theme.colors.quarterlyContent
|
||||
_waveformView.secondaryLineColor = ThemeService.shared().theme.colors.secondaryContent
|
||||
elapsedTimeLabel.textColor = UIColor(rgb: 0x8E99A4)
|
||||
} else {
|
||||
playButton.setImage((details.playing ? Asset.Images.voiceMessagePauseButtonLight.image : Asset.Images.voiceMessagePlayButtonLight.image), for: .normal)
|
||||
backgroundView.backgroundColor = UIColor(rgb: 0xE3E8F0)
|
||||
waveformView.primarylineColor = ThemeService.shared().theme.colors.quarterlyContent
|
||||
waveformView.secondaryLineColor = ThemeService.shared().theme.colors.secondaryContent
|
||||
_waveformView.primarylineColor = ThemeService.shared().theme.colors.quarterlyContent
|
||||
_waveformView.secondaryLineColor = ThemeService.shared().theme.colors.secondaryContent
|
||||
elapsedTimeLabel.textColor = UIColor(rgb: 0x737D8C)
|
||||
}
|
||||
|
||||
waveformView.setSamples(details.samples)
|
||||
_waveformView.setSamples(details.samples)
|
||||
|
||||
self.details = details
|
||||
}
|
||||
|
||||
func getRequiredNumberOfSamples() -> Int {
|
||||
waveformView.setNeedsLayout()
|
||||
waveformView.layoutIfNeeded()
|
||||
return waveformView.requiredNumberOfSamples
|
||||
_waveformView.setNeedsLayout()
|
||||
_waveformView.layoutIfNeeded()
|
||||
return _waveformView.requiredNumberOfSamples
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@IBAction private func onPlayButtonTap() {
|
||||
delegate?.voiceMessagePlaybackViewDidRequestToggle()
|
||||
delegate?.voiceMessagePlaybackViewDidRequestPlaybackToggle()
|
||||
}
|
||||
|
||||
@objc private func handleThemeDidChange() {
|
||||
|
||||
@@ -21,6 +21,8 @@ protocol VoiceMessageToolbarViewDelegate: AnyObject {
|
||||
func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewDidRequestPlaybackToggle(_ toolbarView: VoiceMessageToolbarView)
|
||||
func voiceMessageToolbarViewDidRequestSend(_ toolbarView: VoiceMessageToolbarView)
|
||||
}
|
||||
|
||||
enum VoiceMessageToolbarViewUIState {
|
||||
@@ -34,9 +36,11 @@ struct VoiceMessageToolbarViewDetails {
|
||||
var state: VoiceMessageToolbarViewUIState = .idle
|
||||
var elapsedTime: String = ""
|
||||
var audioSamples: [Float] = []
|
||||
var isPlaying: Bool = false
|
||||
var progress: Double = 0.0
|
||||
}
|
||||
|
||||
class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDelegate {
|
||||
class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDelegate, VoiceMessagePlaybackViewDelegate {
|
||||
@IBOutlet private var backgroundView: UIView!
|
||||
|
||||
@IBOutlet private var recordingContainerView: UIView!
|
||||
@@ -107,8 +111,12 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
|
||||
recordButtonsContainerView.addGestureRecognizer(panGesture)
|
||||
|
||||
playbackView = VoiceMessagePlaybackView.instanceFromNib()
|
||||
playbackView.delegate = self
|
||||
playbackViewContainerView.vc_addSubViewMatchingParent(playbackView)
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleWaveformTap))
|
||||
playbackView.waveformView.addGestureRecognizer(tapGesture)
|
||||
|
||||
updateUIWithDetails(VoiceMessageToolbarViewDetails(), animated: false)
|
||||
}
|
||||
|
||||
@@ -166,6 +174,12 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - VoiceMessagePlaybackViewDelegate
|
||||
|
||||
func voiceMessagePlaybackViewDidRequestPlaybackToggle() {
|
||||
delegate?.voiceMessageToolbarViewDidRequestPlaybackToggle(self)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@objc private func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
@@ -249,6 +263,14 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
|
||||
self.lockContainerBackgroundView.alpha = 1.0
|
||||
self.lockedModeContainerView.alpha = 0.0
|
||||
self.recordingContainerView.alpha = 1.0
|
||||
case .lockedModePlayback:
|
||||
self.backgroundView.alpha = 1.0
|
||||
self.primaryRecordButton.alpha = 0.0
|
||||
self.secondaryRecordButton.alpha = 0.0
|
||||
self.recordingChromeContainerView.alpha = 0.0
|
||||
self.lockContainerView.alpha = 0.0
|
||||
self.lockedModeContainerView.alpha = 1.0
|
||||
self.recordingContainerView.alpha = 0.0
|
||||
case .lockedModeRecord:
|
||||
self.backgroundView.alpha = 1.0
|
||||
self.primaryRecordButton.alpha = 0.0
|
||||
@@ -257,7 +279,7 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
|
||||
self.lockContainerView.alpha = 0.0
|
||||
self.lockedModeContainerView.alpha = 1.0
|
||||
self.recordingContainerView.alpha = 0.0
|
||||
default:
|
||||
case .idle:
|
||||
self.backgroundView.alpha = 0.0
|
||||
self.primaryRecordButton.alpha = 1.0
|
||||
self.secondaryRecordButton.alpha = 0.0
|
||||
@@ -298,10 +320,11 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
|
||||
private func updatePlaybackViewWithDetails(_ details: VoiceMessageToolbarViewDetails) {
|
||||
var playbackViewDetails = VoiceMessagePlaybackViewDetails()
|
||||
playbackViewDetails.recording = (details.state == .record || details.state == .lockedModeRecord)
|
||||
playbackViewDetails.playing = details.isPlaying
|
||||
playbackViewDetails.progress = details.progress
|
||||
playbackViewDetails.currentTime = details.elapsedTime
|
||||
playbackViewDetails.samples = details.audioSamples
|
||||
playbackViewDetails.playbackEnabled = true
|
||||
playbackViewDetails.progress = 0.0
|
||||
playbackView.configureWithDetails(playbackViewDetails)
|
||||
}
|
||||
|
||||
@@ -327,6 +350,10 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
@IBAction private func onSendButtonTap(_ sender: UIBarItem) {
|
||||
delegate?.voiceMessageToolbarViewDidRequestSend(self)
|
||||
}
|
||||
|
||||
@objc private func handleWaveformTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
delegate?.voiceMessageToolbarViewDidRequestRecordingFinish(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +147,8 @@
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pkc-LT-lE6">
|
||||
<rect key="frame" x="0.0" y="0.0" width="544" height="72"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="16" translatesAutoresizingMaskIntoConstraints="NO" id="wL2-0Z-cvF">
|
||||
<rect key="frame" x="4" y="0.0" width="532" height="72"/>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wL2-0Z-cvF">
|
||||
<rect key="frame" x="8" y="0.0" width="528" height="72"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="U4V-EC-Ffy">
|
||||
<rect key="frame" x="0.0" y="14" width="44" height="44"/>
|
||||
@@ -163,14 +163,14 @@
|
||||
</connections>
|
||||
</button>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RWp-zw-zVq">
|
||||
<rect key="frame" x="60" y="14" width="412" height="44"/>
|
||||
<rect key="frame" x="52" y="14" width="424" height="44"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="H6t-Lp-spE"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="UuF-HN-cAU">
|
||||
<rect key="frame" x="488" y="14" width="44" height="44"/>
|
||||
<rect key="frame" x="484" y="14" width="44" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="44" id="HKq-XS-LDC"/>
|
||||
<constraint firstAttribute="height" constant="44" id="ZuT-pR-osp"/>
|
||||
@@ -188,7 +188,7 @@
|
||||
<constraint firstItem="wL2-0Z-cvF" firstAttribute="top" secondItem="pkc-LT-lE6" secondAttribute="top" id="2Na-3x-Ri6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="wL2-0Z-cvF" secondAttribute="trailing" constant="8" id="7oK-QU-5uP"/>
|
||||
<constraint firstAttribute="bottom" secondItem="wL2-0Z-cvF" secondAttribute="bottom" id="IKw-iw-tWg"/>
|
||||
<constraint firstItem="wL2-0Z-cvF" firstAttribute="leading" secondItem="pkc-LT-lE6" secondAttribute="leading" constant="4" id="cG3-Fr-Auu"/>
|
||||
<constraint firstItem="wL2-0Z-cvF" firstAttribute="leading" secondItem="pkc-LT-lE6" secondAttribute="leading" constant="8" id="cG3-Fr-Auu"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
|
||||
Reference in New Issue
Block a user