Integrate new voice message design

This commit is contained in:
nielsandriesse 2020-10-01 09:25:17 +10:00
parent 6ff0834065
commit aa568aba7b
8 changed files with 49 additions and 48 deletions

View File

@ -571,6 +571,7 @@
C31D1DDD25217014005D4DA8 /* UserCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DDC25217014005D4DA8 /* UserCell.swift */; };
C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */; };
C31D1DE9252172D4005D4DA8 /* ContactUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */; };
C31F8117252546F200DD9FD9 /* file_example_MP3_2MG.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C31F8116252546F200DD9FD9 /* file_example_MP3_2MG.mp3 */; };
C329FEEC24F7277900B1C64C /* LightModeSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C329FEEB24F7277900B1C64C /* LightModeSheet.swift */; };
C329FEEF24F7743F00B1C64C /* UIViewController+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C329FEED24F7742E00B1C64C /* UIViewController+Utilities.swift */; };
C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; };
@ -1370,6 +1371,7 @@
C31D1DDC25217014005D4DA8 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = "<group>"; };
C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionVC.swift; sourceTree = "<group>"; };
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = "<group>"; };
C31F8116252546F200DD9FD9 /* file_example_MP3_2MG.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = file_example_MP3_2MG.mp3; sourceTree = "<group>"; };
C329FEEB24F7277900B1C64C /* LightModeSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightModeSheet.swift; sourceTree = "<group>"; };
C329FEED24F7742E00B1C64C /* UIViewController+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Utilities.swift"; sourceTree = "<group>"; };
C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Bold.ttf"; sourceTree = "<group>"; };
@ -2870,6 +2872,7 @@
C35E8AA42485C83B00ACB629 /* CSV */,
34330A581E7875FB00DF2FB9 /* Fonts */,
B633C4FD1A1D190B0059AC12 /* Images */,
C31F8116252546F200DD9FD9 /* file_example_MP3_2MG.mp3 */,
B66DBF4919D5BBC8006EA940 /* Images.xcassets */,
B67EBF5C19194AC60084CCFD /* Settings.bundle */,
B657DDC91911A40500F45B0C /* Signal.entitlements */,
@ -3263,6 +3266,7 @@
AD83FF411A73426500B5C81A /* audio_play_button_blue@2x.png in Resources */,
34C3C78D20409F320000134C /* Opening.m4r in Resources */,
FC5CDF3A1A3393DD00B47253 /* warning_white@2x.png in Resources */,
C31F8117252546F200DD9FD9 /* file_example_MP3_2MG.mp3 in Resources */,
B633C58D1A1D190B0059AC12 /* contact_default_feed.png in Resources */,
B10C9B621A7049EC00ECA2BF /* play_icon@2x.png in Resources */,
B633C5861A1D190B0059AC12 /* call@2x.png in Resources */,

Binary file not shown.

View File

@ -2,17 +2,16 @@ import Accelerate
@objc(LKVoiceMessageView2)
final class VoiceMessageView2 : UIView {
private let audioFileURL: URL
private let player: AVAudioPlayer
private var duration: Double = 1
private let voiceMessage: TSAttachment
private var isAnimating = false
private var volumeSamples: [Float] = [] { didSet { updateShapeLayer() } }
private var volumeSamples: [Float] = [] { didSet { updateShapeLayers() } }
private var progress: CGFloat = 0
private var duration: CGFloat = 1 // Not initialized at 0 to avoid division by zero
// MARK: Components
private lazy var loader: UIView = {
let result = UIView()
result.backgroundColor = Colors.text.withAlphaComponent(0.2)
result.layer.cornerRadius = Values.messageBubbleCornerRadius
return result
}()
@ -29,34 +28,41 @@ final class VoiceMessageView2 : UIView {
}()
// MARK: Settings
private let margin = Values.smallSpacing
private let margin: CGFloat = 4
private let sampleSpacing: CGFloat = 1
// MARK: Initialization
init(audioFileURL: URL) {
self.audioFileURL = audioFileURL
player = try! AVAudioPlayer(contentsOf: audioFileURL)
@objc(initWithVoiceMessage:)
init(voiceMessage: TSAttachment) {
self.voiceMessage = voiceMessage
super.init(frame: CGRect.zero)
initialize()
}
override init(frame: CGRect) {
preconditionFailure("Use init(audioFileURL:) instead.")
preconditionFailure("Use init(voiceMessage:associatedWith:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(audioFileURL:) instead.")
preconditionFailure("Use init(voiceMessage:associatedWith:) instead.")
}
private func initialize() {
setUpViewHierarchy()
AudioUtilities.getVolumeSamples(for: audioFileURL).done(on: DispatchQueue.main) { [weak self] duration, volumeSamples in
guard let self = self else { return }
self.duration = duration
self.volumeSamples = volumeSamples
self.stopAnimating()
}.catch(on: DispatchQueue.main) { error in
print("[Loki] Couldn't sample audio file due to error: \(error).")
if voiceMessage.isDownloaded {
loader.alpha = 0
guard let url = (voiceMessage as? TSAttachmentStream)?.originalMediaURL else {
return print("[Loki] Couldn't get URL for voice message.")
}
AudioUtilities.getVolumeSamples(for: url).done(on: DispatchQueue.main) { [weak self] volumeSamples in
guard let self = self else { return }
self.volumeSamples = volumeSamples
self.stopAnimating()
}.catch(on: DispatchQueue.main) { error in
print("[Loki] Couldn't sample audio file due to error: \(error).")
}
} else {
showLoader()
}
}
@ -65,16 +71,11 @@ final class VoiceMessageView2 : UIView {
set(.height, to: 40)
addSubview(loader)
loader.pin(to: self)
backgroundColor = Colors.sentMessageBackground
layer.cornerRadius = Values.messageBubbleCornerRadius
layer.insertSublayer(backgroundShapeLayer, at: 0)
layer.insertSublayer(foregroundShapeLayer, at: 1)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(togglePlayback))
addGestureRecognizer(tapGestureRecognizer)
showLoader()
}
// MARK: User Interface
// MARK: UI & Updating
private func showLoader() {
isAnimating = true
loader.alpha = 1
@ -98,10 +99,17 @@ final class VoiceMessageView2 : UIView {
override func layoutSubviews() {
super.layoutSubviews()
updateShapeLayer()
updateShapeLayers()
}
private func updateShapeLayer() {
@objc(updateForProgress:duration:)
func update(for progress: CGFloat, duration: CGFloat) {
self.progress = progress
self.duration = duration
updateShapeLayers()
}
private func updateShapeLayers() {
guard !volumeSamples.isEmpty else { return }
let max = CGFloat(volumeSamples.max()!)
let min = CGFloat(volumeSamples.min()!)
@ -117,20 +125,11 @@ final class VoiceMessageView2 : UIView {
let y = margin + (h - sH) / 2
let subPath = UIBezierPath(roundedRect: CGRect(x: x, y: y, width: sW, height: sH), cornerRadius: sW / 2)
backgroundPath.append(subPath)
if player.currentTime / duration > Double(i) / Double(volumeSamples.count) { foregroundPath.append(subPath) }
if progress / duration > CGFloat(i) / CGFloat(volumeSamples.count) { foregroundPath.append(subPath) }
}
backgroundPath.close()
foregroundPath.close()
backgroundShapeLayer.path = backgroundPath.cgPath
foregroundShapeLayer.path = foregroundPath.cgPath
}
@objc private func togglePlayback() {
player.play()
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
guard let self = self else { return timer.invalidate() }
self.updateShapeLayer()
if !self.player.isPlaying { timer.invalidate() }
}
}
}

View File

@ -27,7 +27,7 @@ enum AudioUtilities {
}
}
static func getVolumeSamples(for audioFileURL: URL, targetSampleCount: Int = 32) -> Promise<(duration: Double, volumeSamples: [Float])> {
static func getVolumeSamples(for audioFileURL: URL, targetSampleCount: Int = 32) -> Promise<[Float]> {
return loadFile(audioFileURL).then { fileInfo in
AudioUtilities.parseSamples(from: fileInfo, with: targetSampleCount)
}
@ -59,7 +59,7 @@ enum AudioUtilities {
return promise
}
private static func parseSamples(from fileInfo: FileInfo, with targetSampleCount: Int) -> Promise<(duration: Double, volumeSamples: [Float])> {
private static func parseSamples(from fileInfo: FileInfo, with targetSampleCount: Int) -> Promise<[Float]> {
// Prepare the reader
guard let reader = try? AVAssetReader(asset: fileInfo.asset) else { return Promise(error: Error.parsingFailed) }
let range = 0..<fileInfo.sampleCount
@ -128,8 +128,7 @@ enum AudioUtilities {
}
guard reader.status == .completed else { return Promise(error: Error.parsingFailed) }
// Return
let duration = fileInfo.asset.duration.seconds
return Promise { $0.fulfill((duration, result)) }
return Promise { $0.fulfill(result) }
}
private static func processSamples(from sampleBuffer: inout Data, outputSamples: inout [Float], samplesToProcess: Int,

View File

@ -839,10 +839,9 @@ NS_ASSUME_NONNULL_BEGIN
OWSAssertDebug(attachment);
OWSAssertDebug([attachment isAudio]);
LKVoiceMessageView *voiceMessageView = [[LKVoiceMessageView alloc] initWithVoiceMessage:attachment viewItem:self.viewItem];
LKVoiceMessageView2 *voiceMessageView = [[LKVoiceMessageView2 alloc] initWithVoiceMessage:attachment];
self.viewItem.lastAudioMessageView = voiceMessageView;
[voiceMessageView update];
self.loadCellContentBlock = ^{
// Do nothing.
@ -1064,7 +1063,7 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
case OWSMessageCellType_Audio:
result = CGSizeMake(maxMessageWidth, [LKVoiceMessageView getHeightFor:self.viewItem]);
result = CGSizeMake(maxMessageWidth, 40.0f);
break;
case OWSMessageCellType_GenericAttachment: {
TSAttachment *attachment = (self.viewItem.attachmentStream ?: self.viewItem.attachmentPointer);

View File

@ -24,7 +24,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@class ContactShareViewModel;
@class ConversationViewCell;
@class DisplayableText;
@class LKVoiceMessageView;
@class LKVoiceMessageView2;
@class OWSLinkPreview;
@class OWSQuotedReplyModel;
@class OWSUnreadIndicator;
@ -99,7 +99,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
#pragma mark - Audio Playback
@property (nonatomic, weak) LKVoiceMessageView *lastAudioMessageView;
@property (nonatomic, weak) LKVoiceMessageView2 *lastAudioMessageView;
@property (nonatomic, readonly) CGFloat audioDurationSeconds;
@property (nonatomic, readonly) CGFloat audioProgressSeconds;

View File

@ -475,7 +475,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
{
_audioPlaybackState = audioPlaybackState;
[self.lastAudioMessageView update];
// No need to update the voice message view here
}
- (void)setAudioProgress:(CGFloat)progress duration:(CGFloat)duration
@ -484,7 +484,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
self.audioProgressSeconds = progress;
[self.lastAudioMessageView update];
[self.lastAudioMessageView updateForProgress:progress duration:duration];
}
#pragma mark - Displayable Text

View File

@ -146,7 +146,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.audioPlayer play];
[self.audioPlayerPoller invalidate];
self.audioPlayerPoller = [NSTimer weakScheduledTimerWithTimeInterval:.05f
self.audioPlayerPoller = [NSTimer weakScheduledTimerWithTimeInterval:.5f
target:self
selector:@selector(audioPlayerUpdated:)
userInfo:nil