Skip to content

Commit

Permalink
#4096 - Added locked mode transition and animations, locked recording…
Browse files Browse the repository at this point in the history
… mode and real time waveform.
  • Loading branch information
stefanceriu committed Jun 22, 2021
1 parent 99eb2d1 commit 3130fd5
Show file tree
Hide file tree
Showing 21 changed files with 504 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "voice_message_lock_chevron.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "voice_message_lock_icon_locked.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "voice_message_lock_icon_unlocked.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "[email protected]",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions Riot/Generated/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
}

Expand Down
45 changes: 36 additions & 9 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}

if let sample = audioRecorder?.averagePowerForChannelNumber(0) {
audioSamples.append(sample)
audioSamples.remove(at: 0)
}

_voiceMessageToolbarView.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: audioRecorder.currentTime))
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ struct VoiceMessagePlaybackViewDetails {
var samples: [Float] = []
var playing: Bool = false
var playbackEnabled = false
var recording: Bool = false
}

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!
Expand Down Expand Up @@ -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

Expand Down
64 changes: 35 additions & 29 deletions Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackView.xib
Original file line number Diff line number Diff line change
Expand Up @@ -21,44 +21,48 @@
<constraint firstAttribute="height" priority="999" constant="44" id="RFF-Im-d7x"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eAi-HM-Wvj">
<rect key="frame" x="44" y="0.0" width="40" height="44"/>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="ZQ2-Ij-mYr">
<rect key="frame" x="8" y="0.0" width="411" height="44"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="voice_message_record_icon" translatesAutoresizingMaskIntoConstraints="NO" id="REB-gl-h0h">
<rect key="frame" x="0.0" y="17" width="10" height="10"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GL1-b8-dZK">
<rect key="frame" x="14" y="6" width="32" height="32"/>
<state key="normal" image="voice_message_play_button_light"/>
<connections>
<action selector="onPlayButtonTap" destination="cGR-49-HWB" eventType="touchUpInside" id="B5j-st-pUp"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eAi-HM-Wvj">
<rect key="frame" x="50" y="0.0" width="40" height="44"/>
<constraints>
<constraint firstAttribute="width" constant="40" id="iuv-MD-XYg"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Fl-yZ-dZB">
<rect key="frame" x="94" y="7" width="317" height="30"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<constraints>
<constraint firstAttribute="width" constant="40" id="iuv-MD-XYg"/>
<constraint firstItem="7Fl-yZ-dZB" firstAttribute="height" secondItem="ZQ2-Ij-mYr" secondAttribute="height" constant="-14" id="PiL-fv-hP1"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GL1-b8-dZK">
<rect key="frame" x="8" y="6" width="32" height="32"/>
<state key="normal" image="voice_message_play_button_light"/>
<connections>
<action selector="onPlayButtonTap" destination="cGR-49-HWB" eventType="touchUpInside" id="B5j-st-pUp"/>
</connections>
</button>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Fl-yZ-dZB">
<rect key="frame" x="88" y="7" width="331" height="30"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</stackView>
</subviews>
<viewLayoutGuide key="safeArea" id="Ugy-Dx-gcs"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="Ugy-Dx-gcs" firstAttribute="trailing" secondItem="LPc-i8-8UC" secondAttribute="trailing" id="2AH-VU-Kcc"/>
<constraint firstItem="7Fl-yZ-dZB" firstAttribute="top" secondItem="cGR-49-HWB" secondAttribute="top" constant="7" id="4wl-W5-P44"/>
<constraint firstItem="eAi-HM-Wvj" firstAttribute="leading" secondItem="GL1-b8-dZK" secondAttribute="trailing" constant="4" id="89L-2g-RhG"/>
<constraint firstAttribute="bottom" secondItem="ZQ2-Ij-mYr" secondAttribute="bottom" id="BSe-tM-f0V"/>
<constraint firstItem="LPc-i8-8UC" firstAttribute="leading" secondItem="Ugy-Dx-gcs" secondAttribute="leading" id="FnY-Ab-FVL"/>
<constraint firstAttribute="bottom" secondItem="eAi-HM-Wvj" secondAttribute="bottom" id="K3Q-rl-3zD"/>
<constraint firstItem="7Fl-yZ-dZB" firstAttribute="leading" secondItem="eAi-HM-Wvj" secondAttribute="trailing" constant="4" id="KUM-Pg-Ume"/>
<constraint firstItem="GL1-b8-dZK" firstAttribute="leading" secondItem="cGR-49-HWB" secondAttribute="leading" constant="8" id="RIG-A2-5bp"/>
<constraint firstItem="Ugy-Dx-gcs" firstAttribute="trailing" secondItem="7Fl-yZ-dZB" secondAttribute="trailing" constant="8" id="TRa-VB-QEc"/>
<constraint firstItem="GL1-b8-dZK" firstAttribute="centerY" secondItem="cGR-49-HWB" secondAttribute="centerY" id="TW1-ng-w6C"/>
<constraint firstAttribute="bottom" secondItem="7Fl-yZ-dZB" secondAttribute="bottom" constant="7" id="Zx4-eB-H2q"/>
<constraint firstItem="ZQ2-Ij-mYr" firstAttribute="top" secondItem="cGR-49-HWB" secondAttribute="top" id="KRu-5w-kGE"/>
<constraint firstAttribute="bottom" secondItem="LPc-i8-8UC" secondAttribute="bottom" id="apf-b1-yIb"/>
<constraint firstItem="eAi-HM-Wvj" firstAttribute="top" secondItem="cGR-49-HWB" secondAttribute="top" id="jxd-Mq-ASx"/>
<constraint firstAttribute="bottom" secondItem="GL1-b8-dZK" secondAttribute="bottom" constant="6" id="nzi-5c-92a"/>
<constraint firstItem="GL1-b8-dZK" firstAttribute="top" secondItem="cGR-49-HWB" secondAttribute="top" constant="6" id="zCt-aS-zv4"/>
<constraint firstItem="ZQ2-Ij-mYr" firstAttribute="leading" secondItem="cGR-49-HWB" secondAttribute="leading" constant="8" id="fDO-rh-Jbl"/>
<constraint firstAttribute="trailing" secondItem="ZQ2-Ij-mYr" secondAttribute="trailing" constant="8" id="fM3-nY-rDV"/>
<constraint firstItem="LPc-i8-8UC" firstAttribute="top" secondItem="cGR-49-HWB" secondAttribute="top" id="zl5-Sf-qSF"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
Expand All @@ -68,12 +72,14 @@
<outlet property="backgroundView" destination="LPc-i8-8UC" id="mfD-md-nTj"/>
<outlet property="elapsedTimeLabel" destination="eAi-HM-Wvj" id="z70-aJ-O90"/>
<outlet property="playButton" destination="GL1-b8-dZK" id="5u7-CG-d99"/>
<outlet property="recordingIcon" destination="REB-gl-h0h" id="uL1-nI-bhF"/>
<outlet property="waveformContainerView" destination="7Fl-yZ-dZB" id="f9u-wS-jvG"/>
</connections>
<point key="canvasLocation" x="-1742.753623188406" y="-299.33035714285711"/>
</view>
</objects>
<resources>
<image name="voice_message_play_button_light" width="32" height="32"/>
<image name="voice_message_record_icon" width="10" height="10"/>
</resources>
</document>
Loading

0 comments on commit 3130fd5

Please sign in to comment.