diff --git a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json
index bb4344e92..a255919c5 100644
--- a/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json
+++ b/Riot/Assets/Images.xcassets/Room/ContextMenu/room_context_menu_delete.imageset/Contents.json
@@ -19,5 +19,8 @@
"info" : {
"author" : "xcode",
"version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
}
}
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/Contents.json
new file mode 100644
index 000000000..6b40d80e4
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "voice_message_lock_chevron.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "voice_message_lock_chevron@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "voice_message_lock_chevron@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron.png
new file mode 100644
index 000000000..332200ef8
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@2x.png
new file mode 100644
index 000000000..ff29e0808
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@3x.png
new file mode 100644
index 000000000..b9f5c04d5
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_chevron.imageset/voice_message_lock_chevron@3x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/Contents.json
new file mode 100644
index 000000000..93de75077
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "voice_message_lock_icon_locked.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "voice_message_lock_icon_locked@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "voice_message_lock_icon_locked@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked.png
new file mode 100644
index 000000000..bb79d7785
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@2x.png
new file mode 100644
index 000000000..521e0df1f
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@3x.png
new file mode 100644
index 000000000..b7cdaf911
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_locked.imageset/voice_message_lock_icon_locked@3x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/Contents.json
new file mode 100644
index 000000000..43a311cf4
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "voice_message_lock_icon_unlocked.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "voice_message_lock_icon_unlocked@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "voice_message_lock_icon_unlocked@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked.png
new file mode 100644
index 000000000..91b915c03
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@2x.png
new file mode 100644
index 000000000..4a50acc94
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@3x.png
new file mode 100644
index 000000000..4264165d4
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_lock_icon_unlocked.imageset/voice_message_lock_icon_unlocked@3x.png differ
diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift
index c3b0bb50e..aab42d27e 100644
--- a/Riot/Generated/Images.swift
+++ b/Riot/Generated/Images.swift
@@ -131,6 +131,9 @@ internal enum Asset {
internal static let voiceCallHangonIcon = ImageAsset(name: "voice_call_hangon_icon")
internal static let voiceCallHangupIcon = ImageAsset(name: "voice_call_hangup_icon")
internal static let voiceMessageCancelGradient = ImageAsset(name: "voice_message_cancel_gradient")
+ internal static let voiceMessageLockChevron = ImageAsset(name: "voice_message_lock_chevron")
+ internal static let voiceMessageLockIconLocked = ImageAsset(name: "voice_message_lock_icon_locked")
+ internal static let voiceMessageLockIconUnlocked = ImageAsset(name: "voice_message_lock_icon_unlocked")
internal static let voiceMessagePauseButtonDark = ImageAsset(name: "voice_message_pause_button_dark")
internal static let voiceMessagePauseButtonLight = ImageAsset(name: "voice_message_pause_button_light")
internal static let voiceMessagePlayButtonDark = ImageAsset(name: "voice_message_play_button_dark")
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift
index e639c6ce6..635e5ea40 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift
@@ -17,6 +17,8 @@
import Foundation
import AVFoundation
+private let silenceThreshold: Float = -50.0
+
protocol VoiceMessageAudioRecorderDelegate: AnyObject {
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder)
func audioRecorderDidFinishRecording(_ audioRecorder: VoiceMessageAudioRecorder)
@@ -104,11 +106,7 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate {
}
private func normalizedPowerLevelFromDecibels(_ decibels: Float) -> Float {
- if decibels < -60.0 || decibels == 0.0 {
- return 0.0
- }
-
- return powf((powf(10.0, 0.05 * decibels) - powf(10.0, 0.05 * -60.0)) * (1.0 / (1.0 - powf(10.0, 0.05 * -60.0))), 1.0 / 2.0)
+ return decibels / silenceThreshold
}
}
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
index 94306a5ae..b00d68d82 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
@@ -31,6 +31,9 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
private var audioRecorder: VoiceMessageAudioRecorder?
+ private var audioSamples: [Float] = []
+ private var isInLockedMode: Bool = false
+
@objc public weak var delegate: VoiceMessageControllerDelegate?
@objc public var voiceMessageToolbarView: UIView {
@@ -54,6 +57,8 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
self._voiceMessageToolbarView.update(theme: self.themeService.theme)
NotificationCenter.default.addObserver(self, selector: #selector(handleThemeDidChange), name: .themeServiceDidChangeTheme, object: nil)
+
+ updateUI()
}
// MARK: - VoiceMessageToolbarViewDelegate
@@ -87,27 +92,32 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}
func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView) {
+ isInLockedMode = false
audioRecorder?.stopRecording()
deleteRecordingAtURL(audioRecorder?.url)
UINotificationFeedbackGenerator().notificationOccurred(.error)
}
+ func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView) {
+ isInLockedMode = true
+ updateUI()
+ }
+
// MARK: - AudioRecorderDelegate
func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
- _voiceMessageToolbarView.state = .recording
- self.displayLink.isPaused = false
+ updateUI()
}
func audioRecorderDidFinishRecording(_ audioRecorder: VoiceMessageAudioRecorder) {
- _voiceMessageToolbarView.state = .idle
- displayLink.isPaused = true
+ updateUI()
}
func audioRecorder(_ audioRecorder: VoiceMessageAudioRecorder, didFailWithError: Error) {
+ isInLockedMode = false
+ updateUI()
+
MXLog.error("Failed recording voice message.")
- _voiceMessageToolbarView.state = .idle
- displayLink.isPaused = true
}
// MARK: - Private
@@ -129,10 +139,27 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}
@objc private func handleDisplayLinkTick() {
- guard let audioRecorder = audioRecorder else {
- return
+ updateUI()
+ }
+
+ private func updateUI() {
+ displayLink.isPaused = !(audioRecorder?.isRecording ?? false)
+
+ let requiredNumberOfSamples = _voiceMessageToolbarView.getRequiredNumberOfSamples()
+
+ if audioSamples.count != requiredNumberOfSamples {
+ audioSamples = [Float](repeating: 0.0, count: requiredNumberOfSamples)
}
- _voiceMessageToolbarView.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: audioRecorder.currentTime))
+ if let sample = audioRecorder?.averagePowerForChannelNumber(0) {
+ audioSamples.append(sample)
+ audioSamples.remove(at: 0)
+ }
+
+ 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.audioSamples = audioSamples
+ _voiceMessageToolbarView.configureWithDetails(details)
}
}
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift
index 7e9ddce0d..ba0bd2654 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.swift
@@ -26,6 +26,7 @@ struct VoiceMessagePlaybackViewDetails {
var samples: [Float] = []
var playing: Bool = false
var playbackEnabled = false
+ var recording: Bool = false
}
class VoiceMessagePlaybackView: UIView {
@@ -33,6 +34,7 @@ class VoiceMessagePlaybackView: UIView {
private var waveformView: VoiceMessageWaveformView!
@IBOutlet private var backgroundView: UIView!
+ @IBOutlet private var recordingIcon: UIView!
@IBOutlet private var playButton: UIButton!
@IBOutlet private var elapsedTimeLabel: UILabel!
@IBOutlet private var waveformContainerView: UIView!
@@ -66,6 +68,8 @@ class VoiceMessagePlaybackView: UIView {
}
playButton.isEnabled = details.playbackEnabled
+ playButton.isHidden = details.recording
+ recordingIcon.isHidden = !details.recording
elapsedTimeLabel.text = details.currentTime
waveformView.progress = details.progress
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib
index 545fab174..c49272d99 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib
@@ -21,44 +21,48 @@
-
-
-
-
-
-
+
-
-
+
-
-
-
-
-
-
+
-
-
-
+
+
@@ -68,6 +72,7 @@
+
@@ -75,5 +80,6 @@
+
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift
index 8f7b664f9..d46493b81 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift
@@ -20,16 +20,27 @@ protocol VoiceMessageToolbarViewDelegate: AnyObject {
func voiceMessageToolbarViewDidRequestRecordingStart(_ toolbarView: VoiceMessageToolbarView)
func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView)
func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView)
+ func voiceMessageToolbarViewDidRequestLockedModeRecording(_ toolbarView: VoiceMessageToolbarView)
}
enum VoiceMessageToolbarViewUIState {
case idle
- case recording
+ case record
+ case lockedModeRecord
+ case lockedModePlayback
+}
+
+struct VoiceMessageToolbarViewDetails {
+ var state: VoiceMessageToolbarViewUIState = .idle
+ var elapsedTime: String = ""
+ var audioSamples: [Float] = []
}
class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDelegate {
@IBOutlet private var backgroundView: UIView!
+ @IBOutlet private var recordingContainerView: UIView!
+
@IBOutlet private var recordButtonsContainerView: UIView!
@IBOutlet private var primaryRecordButton: UIButton!
@IBOutlet private var secondaryRecordButton: UIButton!
@@ -37,43 +48,40 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
@IBOutlet private var recordingChromeContainerView: UIView!
@IBOutlet private var recordingIndicatorView: UIView!
+ @IBOutlet private var elapsedTimeLabel: UILabel!
+
@IBOutlet private var slideToCancelContainerView: UIView!
@IBOutlet private var slideToCancelLabel: UILabel!
@IBOutlet private var slideToCancelChevron: UIImageView!
@IBOutlet private var slideToCancelGradient: UIImageView!
- @IBOutlet private var elapsedTimeLabel: UILabel!
+ @IBOutlet private var lockContainerView: UIView!
+ @IBOutlet private var lockContainerBackgroundView: UIView!
+ @IBOutlet private var primaryLockButton: UIButton!
+ @IBOutlet private var secondaryLockButton: UIButton!
+ @IBOutlet private var lockChevron: UIView!
+
+ @IBOutlet private var lockedModeContainerView: UIView!
+ @IBOutlet private var deleteButton: UIButton!
+ @IBOutlet private var playbackViewContainerView: UIView!
+ @IBOutlet private var sendButton: UIButton!
+
+ private var playbackView: VoiceMessagePlaybackView!
private var cancelLabelToRecordButtonDistance: CGFloat = 0.0
+ private var lockChevronToRecordButtonDistance: CGFloat = 0.0
+ private var lockChevronToLockButtonDistance: CGFloat = 0.0
+ private var panDirection: UISwipeGestureRecognizer.Direction?
+
+ private var details: VoiceMessageToolbarViewDetails?
private var currentTheme: Theme? {
didSet {
- updateUIAnimated(true)
+ updateUIWithDetails(details, animated: true)
}
}
weak var delegate: VoiceMessageToolbarViewDelegate?
-
- var state: VoiceMessageToolbarViewUIState = .idle {
- didSet {
- switch state {
- case .recording:
- let convertedFrame = self.convert(slideToCancelLabel.frame, from: slideToCancelContainerView)
- cancelLabelToRecordButtonDistance = recordButtonsContainerView.frame.minX - convertedFrame.maxX
- startAnimatingRecordingIndicator()
- case .idle:
- cancelDrag()
- }
-
- updateUIAnimated(true)
- }
- }
-
- var elapsedTime: String? {
- didSet {
- elapsedTimeLabel.text = elapsedTime
- }
- }
@objc static func instanceFromNib() -> VoiceMessageToolbarView {
let nib = UINib(nibName: "VoiceMessageToolbarView", bundle: nil)
@@ -87,6 +95,7 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
super.awakeFromNib()
slideToCancelGradient.image = Asset.Images.voiceMessageCancelGradient.image.withRenderingMode(.alwaysTemplate)
+ lockContainerBackgroundView.layer.cornerRadius = lockContainerBackgroundView.bounds.width / 2.0
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
longPressGesture.delegate = self
@@ -97,7 +106,52 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
longPressGesture.delegate = self
recordButtonsContainerView.addGestureRecognizer(panGesture)
- updateUIAnimated(false)
+ playbackView = VoiceMessagePlaybackView.instanceFromNib()
+ playbackViewContainerView.vc_addSubViewMatchingParent(playbackView)
+
+ updateUIWithDetails(VoiceMessageToolbarViewDetails(), animated: false)
+ }
+
+ func configureWithDetails(_ details: VoiceMessageToolbarViewDetails) {
+ elapsedTimeLabel.text = details.elapsedTime
+
+ UIView.animate(withDuration: 0.25) {
+ self.updatePlaybackViewWithDetails(details)
+ }
+
+ if self.details?.state != details.state {
+ switch details.state {
+ case .record:
+ var convertedFrame = self.convert(slideToCancelLabel.frame, from: slideToCancelContainerView)
+ cancelLabelToRecordButtonDistance = recordButtonsContainerView.frame.minX - convertedFrame.maxX
+
+ convertedFrame = self.convert(lockChevron.frame, from: lockContainerView)
+ lockChevronToRecordButtonDistance = recordButtonsContainerView.frame.midY + convertedFrame.maxY
+
+ lockChevronToLockButtonDistance = lockChevron.frame.minY - primaryLockButton.frame.midY
+
+ startAnimatingRecordingIndicator()
+ default:
+ cancelDrag()
+ }
+
+ if details.state == .lockedModeRecord && self.details?.state == .record {
+ UIView.animate(withDuration: 0.25) {
+ self.secondaryLockButton.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)
+ self.secondaryLockButton.alpha = 0.0
+ } completion: { _ in
+ self.updateUIWithDetails(details, animated: true)
+ }
+ } else {
+ updateUIWithDetails(details, animated: true)
+ }
+ }
+
+ self.details = details
+ }
+
+ func getRequiredNumberOfSamples() -> Int {
+ return playbackView.getRequiredNumberOfSamples()
}
// MARK: - Themable
@@ -119,8 +173,7 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
case UIGestureRecognizer.State.began:
delegate?.voiceMessageToolbarViewDidRequestRecordingStart(self)
case UIGestureRecognizer.State.ended:
- delegate?.voiceMessageToolbarViewDidRequestRecordingFinish(self)
- case UIGestureRecognizer.State.cancelled:
+ // delegate?.voiceMessageToolbarViewDidRequestRecordingFinish(self)
delegate?.voiceMessageToolbarViewDidRequestRecordingCancel(self)
default:
break
@@ -128,17 +181,49 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
}
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
- guard self.state == .recording && gestureRecognizer.state == .changed else {
+ guard details?.state == .record && gestureRecognizer.state == .changed else {
return
}
let translation = gestureRecognizer.translation(in: self)
- secondaryRecordButton.transform = CGAffineTransform(translationX: min(translation.x, 0.0), y: 0.0)
- slideToCancelContainerView.transform = CGAffineTransform(translationX: min(translation.x + cancelLabelToRecordButtonDistance, 0.0), y: 0.0)
+ if abs(translation.x) <= 20.0 && abs(translation.y) <= 20.0 {
+ panDirection = nil
+ } else if panDirection == nil {
+ if abs(translation.x) >= abs(translation.y) {
+ panDirection = .left
+ } else {
+ panDirection = .up
+ }
+ }
- if abs(translation.x - recordButtonsContainerView.frame.width / 2.0) > self.bounds.width / 2.0 {
- cancelDrag()
+ if panDirection == .left {
+ secondaryRecordButton.transform = CGAffineTransform(translationX: min(translation.x, 0.0), y: 0.0)
+ slideToCancelContainerView.transform = CGAffineTransform(translationX: min(translation.x + cancelLabelToRecordButtonDistance, 0.0), y: 0.0)
+
+ if abs(translation.x - recordButtonsContainerView.frame.width / 2.0) > self.bounds.width / 2.0 {
+ delegate?.voiceMessageToolbarViewDidRequestRecordingCancel(self)
+ }
+ } else if panDirection == .up {
+ secondaryRecordButton.transform = CGAffineTransform(translationX: 0.0, y: min(0.0, translation.y))
+
+ let yTranslation = min(max(translation.y + lockChevronToRecordButtonDistance, -lockChevronToLockButtonDistance), 0.0)
+ lockChevron.transform = CGAffineTransform(translationX: 0.0, y: yTranslation)
+
+ let transitionPercentage = abs(yTranslation) / lockChevronToLockButtonDistance
+
+ lockChevron.alpha = 1.0 - transitionPercentage
+ secondaryRecordButton.alpha = 1.0 - transitionPercentage
+ primaryLockButton.alpha = 1.0 - transitionPercentage
+ lockContainerBackgroundView.alpha = 1.0 - transitionPercentage
+ secondaryLockButton.alpha = transitionPercentage
+
+ if transitionPercentage >= 1.0 {
+ self.delegate?.voiceMessageToolbarViewDidRequestLockedModeRecording(self)
+ }
+
+ } else {
+ secondaryRecordButton.transform = CGAffineTransform(translationX: min(0.0, translation.x), y: min(0.0, translation.y))
}
}
@@ -149,19 +234,42 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
}
}
- private func updateUIAnimated(_ animated: Bool) {
- UIView.animate(withDuration: (animated ? 0.25 : 0.0)) {
- switch self.state {
- case .idle:
- self.backgroundView.alpha = 0.0
- self.primaryRecordButton.alpha = 1.0
- self.secondaryRecordButton.alpha = 0.0
- self.recordingChromeContainerView.alpha = 0.0
- case .recording:
+ private func updateUIWithDetails(_ details: VoiceMessageToolbarViewDetails?, animated: Bool) {
+ guard let details = details else {
+ return
+ }
+
+ UIView.animate(withDuration: (animated ? 0.25 : 0.0), delay: 0.0, options: .beginFromCurrentState) {
+ switch details.state {
+ case .record:
self.backgroundView.alpha = 1.0
self.primaryRecordButton.alpha = 0.0
self.secondaryRecordButton.alpha = 1.0
self.recordingChromeContainerView.alpha = 1.0
+ self.lockContainerView.alpha = 1.0
+ self.lockContainerBackgroundView.alpha = 1.0
+ self.lockedModeContainerView.alpha = 0.0
+ self.recordingContainerView.alpha = 1.0
+ case .lockedModeRecord:
+ 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
+ default:
+ self.backgroundView.alpha = 0.0
+ self.primaryRecordButton.alpha = 1.0
+ self.secondaryRecordButton.alpha = 0.0
+ self.recordingChromeContainerView.alpha = 0.0
+ self.lockContainerView.alpha = 0.0
+ self.lockContainerBackgroundView.alpha = 1.0
+ self.primaryLockButton.alpha = 1.0
+ self.secondaryLockButton.alpha = 0.0
+ self.lockChevron.alpha = 1.0
+ self.lockedModeContainerView.alpha = 0.0
+ self.recordingContainerView.alpha = 1.0
}
guard let theme = self.currentTheme else {
@@ -176,18 +284,30 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
self.slideToCancelChevron.tintColor = theme.textSecondaryColor
self.elapsedTimeLabel.textColor = theme.textSecondaryColor
} completion: { _ in
- switch self.state {
+ switch details.state {
case .idle:
self.secondaryRecordButton.transform = .identity
self.slideToCancelContainerView.transform = .identity
+ self.lockChevron.transform = .identity
+ self.secondaryLockButton.transform = .identity
default:
break
}
}
}
+ private func updatePlaybackViewWithDetails(_ details: VoiceMessageToolbarViewDetails) {
+ var playbackViewDetails = VoiceMessagePlaybackViewDetails()
+ playbackViewDetails.recording = (details.state == .record || details.state == .lockedModeRecord)
+ playbackViewDetails.currentTime = details.elapsedTime
+ playbackViewDetails.samples = details.audioSamples
+ playbackViewDetails.playbackEnabled = true
+ playbackViewDetails.progress = 0.0
+ playbackView.configureWithDetails(playbackViewDetails)
+ }
+
private func startAnimatingRecordingIndicator() {
- if self.state != .recording {
+ if self.details?.state != .record {
return
}
@@ -202,4 +322,12 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
}
}
+
+ @IBAction private func onTrashButtonTap(_ sender: UIBarItem) {
+ delegate?.voiceMessageToolbarViewDidRequestRecordingCancel(self)
+ }
+
+ @IBAction private func onSendButtonTap(_ sender: UIBarItem) {
+ delegate?.voiceMessageToolbarViewDidRequestRecordingFinish(self)
+ }
}
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib
index 6218ad96a..fe548450b 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib
@@ -11,121 +11,236 @@
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift
index 39c159e04..8a7a65dff 100644
--- a/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift
+++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageWaveformView.swift
@@ -56,11 +56,6 @@ class VoiceMessageWaveformView: UIView {
updateBarViews()
}
- func addSample(_ sample: Float) {
- samples.append(sample)
- updateBarViews()
- }
-
// MARK: - Private
private func setupBarViews() {