Skip to content

Commit

Permalink
#4090 - Added recording duration label and permissions checking.
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanceriu committed Jun 8, 2021
1 parent f0c9138 commit 604ee7e
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 52 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "voice_message_record_icon.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "voice_message_record_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "voice_message_record_icon@3x.png",
"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.
1 change: 1 addition & 0 deletions Riot/Generated/Images.swift
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ internal enum Asset {
internal static let voiceMessageCancelGradient = ImageAsset(name: "voice_message_cancel_gradient")
internal static let voiceMessageRecordButtonDefault = ImageAsset(name: "voice_message_record_button_default")
internal static let voiceMessageRecordButtonRecording = ImageAsset(name: "voice_message_record_button_recording")
internal static let voiceMessageRecordIcon = ImageAsset(name: "voice_message_record_icon")
internal static let addMemberFloatingAction = ImageAsset(name: "add_member_floating_action")
internal static let addParticipant = ImageAsset(name: "add_participant")
internal static let addParticipants = ImageAsset(name: "add_participants")
Expand Down
14 changes: 14 additions & 0 deletions Riot/Modules/Room/RoomViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -6163,6 +6163,20 @@ - (void)removeJitsiWidgetViewDidCompleteSliding:(RemoveJitsiWidgetView *)view

#pragma mark - VoiceMessageControllerDelegate

- (void)voiceMessageController:(VoiceMessageController *)voiceMessageController didRequestPermissionCheckWithCompletion:(void (^)(BOOL))completion
{
NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];

// FIXME: fix permission message
NSString * message = [NSString stringWithFormat:[NSBundle mxk_localizedStringForKey:@"microphone_access_not_granted_for_call"], appDisplayName];

[MXKTools checkAccessForMediaType:AVMediaTypeAudio
manualChangeMessage: message
showPopUpInViewController:self completionHandler:^(BOOL granted) {
completion(granted);
}];
}

- (void)voiceMessageController:(VoiceMessageController *)voiceMessageController didRequestSendForFileAtURL:(NSURL *)url completion:(void (^)(BOOL))completion
{
[self.roomDataSource sendAudioFile:url mimeType:@"audio/mp4" success:^(NSString *eventId) {
Expand Down
8 changes: 5 additions & 3 deletions Riot/Modules/Room/VoiceMessages/AudioRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ enum AudioRecorderError: Error {

class AudioRecorder: NSObject, AVAudioRecorderDelegate {

private(set) var isRecording: Bool = false
private(set) var currentTime: TimeInterval = 0
private var audioRecorder: AVAudioRecorder?

var url: URL? {
return audioRecorder?.url
}
private var audioRecorder: AVAudioRecorder?

var currentTime: TimeInterval {
return audioRecorder?.currentTime ?? 0
}

weak var delegate: AudioRecorderDelegate?

Expand Down
41 changes: 35 additions & 6 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
import Foundation

@objc public protocol VoiceMessageControllerDelegate: AnyObject {
func voiceMessageController(_ voiceMessageController: VoiceMessageController, didRequestPermissionCheckWithCompletion: @escaping (Bool) -> Void)
func voiceMessageController(_ voiceMessageController: VoiceMessageController, didRequestSendForFileAtURL url: URL, completion: @escaping (Bool) -> Void)
}

public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, AudioRecorderDelegate {

private let themeService: ThemeService
private let _voiceMessageToolbarView: VoiceMessageToolbarView
private let timeFormatter: DateFormatter
private var displayLink: CADisplayLink!

private var audioRecorder: AudioRecorder?

Expand All @@ -36,24 +39,37 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
@objc public init(themeService: ThemeService) {
_voiceMessageToolbarView = VoiceMessageToolbarView.instanceFromNib()
self.themeService = themeService
self.timeFormatter = DateFormatter()

super.init()

_voiceMessageToolbarView.delegate = self

timeFormatter.dateFormat = "m:ss"

displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLinkTick))
displayLink.isPaused = true
displayLink.add(to: .current, forMode: .common)

self._voiceMessageToolbarView.update(theme: self.themeService.theme)
NotificationCenter.default.addObserver(self, selector: #selector(handleThemeDidChange), name: .themeServiceDidChangeTheme, object: nil)
}

// MARK: - VoiceMessageToolbarViewDelegate

func voiceMessageToolbarViewDidRequestRecordingStart(_ toolbarView: VoiceMessageToolbarView) {
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString)

audioRecorder = AudioRecorder()
audioRecorder?.delegate = self
audioRecorder?.recordWithOuputURL(temporaryFileURL)
delegate?.voiceMessageController(self, didRequestPermissionCheckWithCompletion: { [weak self] success in
guard let self = self, success != false else {
return
}

let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(ProcessInfo().globallyUniqueString)

self.audioRecorder = AudioRecorder()
self.audioRecorder?.delegate = self
self.audioRecorder?.recordWithOuputURL(temporaryFileURL)
})
}

func voiceMessageToolbarViewDidRequestRecordingFinish(_ toolbarView: VoiceMessageToolbarView) {
Expand All @@ -65,28 +81,33 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
}

delegate?.voiceMessageController(self, didRequestSendForFileAtURL: url) { [weak self] success in
UINotificationFeedbackGenerator().notificationOccurred( (success ? .success : .error))
self?.deleteRecordingAtURL(url)
}
}

func voiceMessageToolbarViewDidRequestRecordingCancel(_ toolbarView: VoiceMessageToolbarView) {
audioRecorder?.stopRecording()
deleteRecordingAtURL(audioRecorder?.url)
UINotificationFeedbackGenerator().notificationOccurred(.error)
}

// MARK: - AudioRecorderDelegate

func audioRecorderDidStartRecording(_ audioRecorder: AudioRecorder) {
_voiceMessageToolbarView.state = .recording
self.displayLink.isPaused = false
}

func audioRecorderDidFinishRecording(_ audioRecorder: AudioRecorder) {
_voiceMessageToolbarView.state = .idle
displayLink.isPaused = true
}

func audioRecorder(_ audioRecorder: AudioRecorder, didFailWithError: Error) {
MXLog.error("Failed recording voice message.")
_voiceMessageToolbarView.state = .idle
displayLink.isPaused = true
}

// MARK: - Private
Expand All @@ -106,4 +127,12 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate,
@objc private func handleThemeDidChange() {
self._voiceMessageToolbarView.update(theme: self.themeService.theme)
}

@objc private func handleDisplayLinkTick() {
guard let audioRecorder = audioRecorder else {
return
}

_voiceMessageToolbarView.elapsedTime = timeFormatter.string(from: Date(timeIntervalSinceReferenceDate: audioRecorder.currentTime))
}
}
29 changes: 19 additions & 10 deletions Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,21 @@ enum VoiceMessageToolbarViewState {
}

class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDelegate {

weak var delegate: VoiceMessageToolbarViewDelegate?

@IBOutlet private var backgroundView: UIView!

@IBOutlet private var recordButtonsContainerView: UIView!
@IBOutlet private var primaryRecordButton: UIButton!
@IBOutlet private var secondaryRecordButton: UIButton!

@IBOutlet private var recordingChromeContainerView: UIView!

@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!

private var cancelLabelToRecordButtonDistance: CGFloat = 0.0

private var currentTheme: Theme? {
Expand All @@ -50,6 +51,8 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
}
}

weak var delegate: VoiceMessageToolbarViewDelegate?

var state: VoiceMessageToolbarViewState = .idle {
didSet {
switch state {
Expand All @@ -63,6 +66,12 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
updateUIAnimated(true)
}
}

var elapsedTime: String? {
didSet {
elapsedTimeLabel.text = elapsedTime
}
}

@objc static func instanceFromNib() -> VoiceMessageToolbarView {
let nib = UINib(nibName: "VoiceMessageToolbarView", bundle: nil)
Expand Down Expand Up @@ -142,30 +151,30 @@ class VoiceMessageToolbarView: PassthroughView, Themable, UIGestureRecognizerDel
UIView.animate(withDuration: (animated ? 0.25 : 0.0)) {
switch self.state {
case .idle:
self.slideToCancelContainerView.alpha = 0.0
self.backgroundView.alpha = 0.0
self.slideToCancelGradient.alpha = 0.0
self.recordButtonsContainerView.transform = .identity
self.slideToCancelContainerView.transform = .identity
self.primaryRecordButton.alpha = 1.0
self.secondaryRecordButton.alpha = 0.0
self.recordingChromeContainerView.alpha = 0.0
self.recordButtonsContainerView.transform = .identity
self.slideToCancelContainerView.transform = .identity
case .recording:
self.slideToCancelContainerView.alpha = 1.0
self.backgroundView.alpha = 1.0
self.slideToCancelGradient.alpha = 1.0
self.primaryRecordButton.alpha = 0.0
self.secondaryRecordButton.alpha = 1.0
self.recordingChromeContainerView.alpha = 1.0
}

guard let theme = self.currentTheme else {
return
}

self.backgroundView.backgroundColor = theme.backgroundColor
self.slideToCancelGradient.tintColor = theme.backgroundColor

self.primaryRecordButton.tintColor = theme.textSecondaryColor
self.slideToCancelLabel.textColor = theme.textSecondaryColor
self.slideToCancelChevron.tintColor = theme.textSecondaryColor
self.slideToCancelGradient.tintColor = theme.backgroundColor
self.elapsedTimeLabel.textColor = theme.textSecondaryColor
}
}
}
Loading

0 comments on commit 604ee7e

Please sign in to comment.