diff --git a/Rocket.Chat.xcodeproj/project.pbxproj b/Rocket.Chat.xcodeproj/project.pbxproj index 09a1cbd3d2..5b090abb37 100644 --- a/Rocket.Chat.xcodeproj/project.pbxproj +++ b/Rocket.Chat.xcodeproj/project.pbxproj @@ -297,8 +297,8 @@ 419D78831FBDB7E0005FC7A2 /* InfoRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419D78821FBDB7E0005FC7A2 /* InfoRequestHandler.swift */; }; 419D78871FBDCF6C005FC7A2 /* InfoRequestHandlerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419D78861FBDCF6C005FC7A2 /* InfoRequestHandlerSpec.swift */; }; 419EB5AF215E3C0A00E591BF /* AudioMessageChatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419EB5AE215E3C0A00E591BF /* AudioMessageChatItem.swift */; }; - 419EB5B1215E3C1400E591BF /* AudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419EB5B0215E3C1400E591BF /* AudioMessageCell.swift */; }; - 419EB5B3215E3C2200E591BF /* AudioMessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 419EB5B2215E3C2200E591BF /* AudioMessageCell.xib */; }; + 419EB5B1215E3C1400E591BF /* AudioCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419EB5B0215E3C1400E591BF /* AudioCell.swift */; }; + 419EB5B3215E3C2200E591BF /* AudioCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 419EB5B2215E3C2200E591BF /* AudioCell.xib */; }; 419EB5B5215E52BC00E591BF /* VideoMessageChatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419EB5B4215E52BC00E591BF /* VideoMessageChatItem.swift */; }; 419EB5B7215E52EA00E591BF /* VideoMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 419EB5B6215E52EA00E591BF /* VideoMessageCell.swift */; }; 419EB5B9215E52F300E591BF /* VideoMessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 419EB5B8215E52F300E591BF /* VideoMessageCell.xib */; }; @@ -732,6 +732,10 @@ 9914183B2166AF63007D2AA2 /* MessageURLCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9914183A2166AF63007D2AA2 /* MessageURLCell.xib */; }; 9914183D2166C626007D2AA2 /* MessageURLCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9914183C2166C626007D2AA2 /* MessageURLCell.swift */; }; 9914183F2166CFA4007D2AA2 /* MessageURLChatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9914183E2166CFA4007D2AA2 /* MessageURLChatItem.swift */; }; + 991AB00621718D0F0097AE4C /* MessageHeaderChatItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 991AB00521718D0F0097AE4C /* MessageHeaderChatItem.swift */; }; + 991AB00821744E640097AE4C /* BaseAudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 991AB00721744E640097AE4C /* BaseAudioMessageCell.swift */; }; + 991AB00A217458790097AE4C /* AudioMessageCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 991AB009217458790097AE4C /* AudioMessageCell.xib */; }; + 991AB00C217459690097AE4C /* AudioMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 991AB00B217459690097AE4C /* AudioMessageCell.swift */; }; 9921BFAC2075AF5F00BB027A /* PublicSettingsRequestSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9921BFAB2075AF5F00BB027A /* PublicSettingsRequestSpec.swift */; }; 9928225F204DDC8C005D2067 /* EditProfileViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9928225E204DDC8C005D2067 /* EditProfileViewModel.swift */; }; 992B5AB6209A14B5009C8123 /* AudioFileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 992B5AB5209A14B5009C8123 /* AudioFileViewController.swift */; }; @@ -751,6 +755,7 @@ 994DA2B020653FB600083FB8 /* WebBrowserManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994DA2AF20653FB600083FB8 /* WebBrowserManagerSpec.swift */; }; 994DA2B32065486D00083FB8 /* WebBrowserViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994DA2B22065486D00083FB8 /* WebBrowserViewModelSpec.swift */; }; 994EB8042050DD5D0011A9CE /* NewPasswordTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994EB8032050DD5D0011A9CE /* NewPasswordTableViewController.swift */; }; + 99549ED2216FE4F50037D6A5 /* MessageHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99549ED1216FE4F50037D6A5 /* MessageHeaderCell.swift */; }; 995F710B20C7822A00B7535F /* AuthTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995F710A20C7822A00B7535F /* AuthTableViewController.swift */; }; 995F710E20C7837300B7535F /* AuthSeparatorTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995F710D20C7837300B7535F /* AuthSeparatorTableViewCell.swift */; }; 995F711020C7842100B7535F /* LoginServiceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995F710F20C7842100B7535F /* LoginServiceTableViewCell.swift */; }; @@ -1190,8 +1195,8 @@ 419D78821FBDB7E0005FC7A2 /* InfoRequestHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoRequestHandler.swift; sourceTree = ""; }; 419D78861FBDCF6C005FC7A2 /* InfoRequestHandlerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoRequestHandlerSpec.swift; sourceTree = ""; }; 419EB5AE215E3C0A00E591BF /* AudioMessageChatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMessageChatItem.swift; sourceTree = ""; }; - 419EB5B0215E3C1400E591BF /* AudioMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMessageCell.swift; sourceTree = ""; }; - 419EB5B2215E3C2200E591BF /* AudioMessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AudioMessageCell.xib; sourceTree = ""; }; + 419EB5B0215E3C1400E591BF /* AudioCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioCell.swift; sourceTree = ""; }; + 419EB5B2215E3C2200E591BF /* AudioCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AudioCell.xib; sourceTree = ""; }; 419EB5B4215E52BC00E591BF /* VideoMessageChatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoMessageChatItem.swift; sourceTree = ""; }; 419EB5B6215E52EA00E591BF /* VideoMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoMessageCell.swift; sourceTree = ""; }; 419EB5B8215E52F300E591BF /* VideoMessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = VideoMessageCell.xib; sourceTree = ""; }; @@ -1619,6 +1624,10 @@ 9914183A2166AF63007D2AA2 /* MessageURLCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MessageURLCell.xib; sourceTree = ""; }; 9914183C2166C626007D2AA2 /* MessageURLCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageURLCell.swift; sourceTree = ""; }; 9914183E2166CFA4007D2AA2 /* MessageURLChatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageURLChatItem.swift; sourceTree = ""; }; + 991AB00521718D0F0097AE4C /* MessageHeaderChatItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHeaderChatItem.swift; sourceTree = ""; }; + 991AB00721744E640097AE4C /* BaseAudioMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseAudioMessageCell.swift; sourceTree = ""; }; + 991AB009217458790097AE4C /* AudioMessageCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AudioMessageCell.xib; sourceTree = ""; }; + 991AB00B217459690097AE4C /* AudioMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioMessageCell.swift; sourceTree = ""; }; 9921BFAB2075AF5F00BB027A /* PublicSettingsRequestSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicSettingsRequestSpec.swift; sourceTree = ""; }; 9928225E204DDC8C005D2067 /* EditProfileViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileViewModel.swift; sourceTree = ""; }; 992B5AB5209A14B5009C8123 /* AudioFileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioFileViewController.swift; sourceTree = ""; }; @@ -1638,6 +1647,7 @@ 994DA2AF20653FB600083FB8 /* WebBrowserManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebBrowserManagerSpec.swift; sourceTree = ""; }; 994DA2B22065486D00083FB8 /* WebBrowserViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebBrowserViewModelSpec.swift; sourceTree = ""; }; 994EB8032050DD5D0011A9CE /* NewPasswordTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPasswordTableViewController.swift; sourceTree = ""; }; + 99549ED1216FE4F50037D6A5 /* MessageHeaderCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHeaderCell.swift; sourceTree = ""; }; 995F710A20C7822A00B7535F /* AuthTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTableViewController.swift; sourceTree = ""; }; 995F710D20C7837300B7535F /* AuthSeparatorTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSeparatorTableViewCell.swift; sourceTree = ""; }; 995F710F20C7842100B7535F /* LoginServiceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginServiceTableViewCell.swift; sourceTree = ""; }; @@ -3705,14 +3715,18 @@ 996735CB21581CEC0049BB63 /* Cells */ = { isa = PBXGroup; children = ( + 99549ED1216FE4F50037D6A5 /* MessageHeaderCell.swift */, 996735CC21581D150049BB63 /* BasicMessageCell.xib */, 996735CE21582E790049BB63 /* BasicMessageCell.swift */, 41BF4D71215B4CC800588B35 /* DateSeparatorCell.xib */, 41BF4D6F215B4CBD00588B35 /* DateSeparatorCell.swift */, 996E741C215DFBDB00842818 /* SequentialMessageCell.xib */, 996E741E215DFBF400842818 /* SequentialMessageCell.swift */, - 419EB5B2215E3C2200E591BF /* AudioMessageCell.xib */, - 419EB5B0215E3C1400E591BF /* AudioMessageCell.swift */, + 991AB00721744E640097AE4C /* BaseAudioMessageCell.swift */, + 991AB009217458790097AE4C /* AudioMessageCell.xib */, + 991AB00B217459690097AE4C /* AudioMessageCell.swift */, + 419EB5B2215E3C2200E591BF /* AudioCell.xib */, + 419EB5B0215E3C1400E591BF /* AudioCell.swift */, 419EB5B8215E52F300E591BF /* VideoMessageCell.xib */, 419EB5B6215E52EA00E591BF /* VideoMessageCell.swift */, 419EB5BE215E6A0C00E591BF /* ReactionsCell.xib */, @@ -3736,6 +3750,7 @@ 996735D021585C600049BB63 /* ChatItems */ = { isa = PBXGroup; children = ( + 991AB00521718D0F0097AE4C /* MessageHeaderChatItem.swift */, 996735D121585CA70049BB63 /* BasicMessageChatItem.swift */, 41BF4D6D215B4CA100588B35 /* DateSeparatorChatItem.swift */, 996E7420215DFE0700842818 /* SequentialMessageChatItem.swift */, @@ -4178,6 +4193,7 @@ 806465EC1FED19B9001F27DB /* EmojiView.xib in Resources */, 41833F4E1DEF16B600E54655 /* Keys.plist in Resources */, 4124FCC91F7A6BF9008ED4C3 /* ChannelInfoActionCell.xib in Resources */, + 991AB00A217458790097AE4C /* AudioMessageCell.xib in Resources */, 995F711C20C7978C00B7535F /* LoginServiceTableViewCell.xib in Resources */, 41DF76ED1D2C50720028DBF8 /* LaunchScreen.storyboard in Resources */, 140A95E1202F526C003FD564 /* Drawing.storyboard in Resources */, @@ -4218,7 +4234,7 @@ 14F8A287202E659000175FDC /* White-40@2x.png in Resources */, 41DCB8261DDC828200E1197F /* SubscriptionSearchMoreView.xib in Resources */, 14F8A288202E659000175FDC /* White-60@3x.png in Resources */, - 419EB5B3215E3C2200E591BF /* AudioMessageCell.xib in Resources */, + 419EB5B3215E3C2200E591BF /* AudioCell.xib in Resources */, 333032A02073940800A9514D /* RCEmojiKit.strings in Resources */, 41CD52D520BEFA3B00336892 /* New Room.storyboard in Resources */, 14F8A25E202E64B200175FDC /* BnW-76@2x.png in Resources */, @@ -4577,6 +4593,7 @@ 8062E33520A5ECF50044F407 /* APIRequestOption.swift in Sources */, 99B15BCD20FD4589005A528F /* DatabaseManagerAuthSettings.swift in Sources */, 807C7C0620751ED2006B600E /* SpotlightClient.swift in Sources */, + 99549ED2216FE4F50037D6A5 /* MessageHeaderCell.swift in Sources */, 8039441120AF1334002F317A /* RoomKickRequest.swift in Sources */, 33383509207926DE006E1D0A /* TransparentToTouchesWindow.swift in Sources */, 4174CB1F1D2DB3350086DAC8 /* StringExtensions.swift in Sources */, @@ -4683,12 +4700,13 @@ 1496A87C20FA481B005C2E14 /* SaveNotificationRequest.swift in Sources */, 9938A08720D3141C00714AB4 /* CustomFieldTableViewCell.swift in Sources */, 41BAE3E71D71B26C00C2445A /* URLExtension.swift in Sources */, + 991AB00821744E640097AE4C /* BaseAudioMessageCell.swift in Sources */, 4174CB0D1D2D994A0086DAC8 /* ConnectServerViewController.swift in Sources */, 4199A9891DABCC570035F820 /* Emojione.swift in Sources */, 4116CBE720B599E6007E7163 /* StatusViewModel.swift in Sources */, 418C74431FA3813F00499577 /* CompoundPickerViewDelegate.swift in Sources */, 80235D1B1F74070100A56CA5 /* RoomMembersRequest.swift in Sources */, - 419EB5B1215E3C1400E591BF /* AudioMessageCell.swift in Sources */, + 419EB5B1215E3C1400E591BF /* AudioCell.swift in Sources */, 1496A86F20FA4644005C2E14 /* NotificationsSwitchCell.swift in Sources */, 3330329A20738E1500A9514D /* SubscriptionManagerRooms.swift in Sources */, 1496A87020FA4644005C2E14 /* NotificationsPreferencesViewModel.swift in Sources */, @@ -4763,6 +4781,7 @@ 80AE2542203E61AF00DC2867 /* ChatMessageUnreadSeparator.swift in Sources */, 8013F86D1FD6B59A00EE1A4E /* APIClient.swift in Sources */, 41FC9E08209B3A6300FED485 /* NSAttributedStringExtensions.swift in Sources */, + 991AB00621718D0F0097AE4C /* MessageHeaderChatItem.swift in Sources */, 414D99161EA0E7CB0020F7E9 /* SignupTableViewController.swift in Sources */, 41E9F025215D4C6B00B2C9E7 /* TestingCoordinator.swift in Sources */, 80D41DF6208FC57100034D1F /* PreferencesViewController.swift in Sources */, @@ -4881,6 +4900,7 @@ 807371A61F96A4FF00D53ADF /* LoginServiceModelHandler.swift in Sources */, 80D41DFD2092378400034D1F /* StarMessageRequest.swift in Sources */, 33D08E2A20BD5F24008D03EF /* TopTransparentViewController.swift in Sources */, + 991AB00C217459690097AE4C /* AudioMessageCell.swift in Sources */, 993B7B40215F0B730067B962 /* FileMessageChatItem.swift in Sources */, 41C45AEF1DFAD42800D9969C /* ChatDataController.swift in Sources */, 801DF8151FD7172500302CC8 /* SubscriptionUserView.swift in Sources */, diff --git a/Rocket.Chat/Controllers/Chat/ChatSections/MessageSection.swift b/Rocket.Chat/Controllers/Chat/ChatSections/MessageSection.swift index b0b9877b41..4f822b2148 100644 --- a/Rocket.Chat/Controllers/Chat/ChatSections/MessageSection.swift +++ b/Rocket.Chat/Controllers/Chat/ChatSections/MessageSection.swift @@ -39,6 +39,7 @@ final class MessageSection: ChatSection { // on the inverse order. What we want to show in the top // needs to go last. var cells: [AnyChatItem] = [] + var shouldAppendMessageHeader = true if !object.message.reactions.isEmpty { cells.append(ReactionsChatItem( @@ -50,10 +51,25 @@ final class MessageSection: ChatSection { for attachment in object.message.attachments { switch attachment.type { case .audio: - cells.append(AudioMessageChatItem( - identifier: attachment.identifier, - audioURL: attachment.fullAudioURL - ).wrapped) + if object.message.text.isEmpty { + cells.append(AudioMessageChatItem( + identifier: attachment.identifier, + audioURL: attachment.fullAudioURL, + hasText: false, + user: user, + message: object.message + ).wrapped) + + shouldAppendMessageHeader = false + } else { + cells.append(AudioMessageChatItem( + identifier: attachment.identifier, + audioURL: attachment.fullAudioURL, + hasText: true, + user: nil, + message: nil + ).wrapped) + } case .video: cells.append(VideoMessageChatItem( identifier: attachment.identifier, @@ -94,12 +110,12 @@ final class MessageSection: ChatSection { ).wrapped) } - if !object.isSequential { + if !object.isSequential && shouldAppendMessageHeader { cells.append(BasicMessageChatItem( user: user, message: object.message ).wrapped) - } else { + } else if object.isSequential { cells.append(SequentialMessageChatItem( user: user, message: object.message diff --git a/Rocket.Chat/Controllers/Chat/MessagesViewController.swift b/Rocket.Chat/Controllers/Chat/MessagesViewController.swift index a4f7f14833..b0458bbf31 100644 --- a/Rocket.Chat/Controllers/Chat/MessagesViewController.swift +++ b/Rocket.Chat/Controllers/Chat/MessagesViewController.swift @@ -54,6 +54,7 @@ final class MessagesViewController: RocketChatViewController { collectionView.register(BasicMessageCell.nib, forCellWithReuseIdentifier: BasicMessageCell.identifier) collectionView.register(SequentialMessageCell.nib, forCellWithReuseIdentifier: SequentialMessageCell.identifier) collectionView.register(DateSeparatorCell.nib, forCellWithReuseIdentifier: DateSeparatorCell.identifier) + collectionView.register(AudioCell.nib, forCellWithReuseIdentifier: AudioCell.identifier) collectionView.register(AudioMessageCell.nib, forCellWithReuseIdentifier: AudioMessageCell.identifier) collectionView.register(VideoMessageCell.nib, forCellWithReuseIdentifier: VideoMessageCell.identifier) collectionView.register(ReactionsCell.nib, forCellWithReuseIdentifier: ReactionsCell.identifier) diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/AudioCell.swift b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioCell.swift new file mode 100644 index 0000000000..4ef3ac99fa --- /dev/null +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioCell.swift @@ -0,0 +1,116 @@ +// +// AudioMessageCell.swift +// Rocket.Chat +// +// Created by Rafael Streit on 28/09/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import Foundation +import AVFoundation +import RocketChatViewController + +final class AudioCell: BaseAudioMessageCell, SizingCell { + static let identifier = String(describing: AudioCell.self) + + static let sizingCell: UICollectionViewCell & ChatCell = { + guard let cell = AudioCell.instantiateFromNib() else { + return AudioCell() + } + + return cell + }() + + @IBOutlet weak var viewPlayerBackground: UIView! { + didSet { + viewPlayerBackground.layer.cornerRadius = 4 + } + } + + @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! + @IBOutlet weak var buttonPlay: UIButton! + + @IBOutlet weak var slider: UISlider! { + didSet { + slider.value = 0 + slider.setThumbImage(UIImage(named: "Player Progress Button"), for: .normal) + } + } + + @IBOutlet weak var labelAudioTime: UILabel! { + didSet { + labelAudioTime.font = labelAudioTime.font.bold() + } + } + + override var playing: Bool { + didSet { + updatePlayingState(with: buttonPlay) + } + } + + override var loading: Bool { + didSet { + updateLoadingState(with: activityIndicatorView, and: labelAudioTime) + } + } + + deinit { + updateTimer?.invalidate() + } + + override func awakeFromNib() { + super.awakeFromNib() + + updateTimer = setupPlayerTimer(with: slider, and: labelAudioTime) + } + + override func configure() { + updateAudio() + } + + override func prepareForReuse() { + super.prepareForReuse() + + slider.value = 0 + labelAudioTime.text = "--:--" + playing = false + loading = false + } + + // MARK: IBAction + + @IBAction func didStartSlidingSlider(_ sender: UISlider) { + startSlidingSlider(sender) + } + + @IBAction func didFinishSlidingSlider(_ sender: UISlider) { + finishSlidingSlider(sender) + } + + @IBAction func didPressPlayButton(_ sender: UIButton) { + pressPlayButton(sender) + } + +} + +// MARK: Theming + +extension AudioCell { + override func applyTheme() { + super.applyTheme() + + let theme = self.theme ?? .light + viewPlayerBackground.backgroundColor = theme.auxiliaryBackground + labelAudioTime.textColor = theme.auxiliaryText + updatePlayingState(with: buttonPlay) + } +} + +// MARK: AVAudioPlayerDelegate + +extension AudioCell { + override func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + slider.value = 0.0 + } +} diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/AudioCell.xib b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioCell.xib new file mode 100644 index 0000000000..611d4ff207 --- /dev/null +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioCell.xib @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.swift b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.swift index 5c74aab3c6..e2c0fa7c03 100644 --- a/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.swift +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.swift @@ -2,15 +2,16 @@ // AudioMessageCell.swift // Rocket.Chat // -// Created by Rafael Streit on 28/09/18. +// Created by Filipe Alvarenga on 15/10/18. // Copyright © 2018 Rocket.Chat. All rights reserved. // +import UIKit import Foundation import AVFoundation import RocketChatViewController -final class AudioMessageCell: UICollectionViewCell, ChatCell, SizingCell { +final class AudioMessageCell: BaseAudioMessageCell, SizingCell { static let identifier = String(describing: AudioMessageCell.self) static let sizingCell: UICollectionViewCell & ChatCell = { @@ -21,23 +22,11 @@ final class AudioMessageCell: UICollectionViewCell, ChatCell, SizingCell { return cell }() - var updateTimer: Timer? - - var playing = false { - didSet { - updatePlayingState() - } - } - - var loading = false { + @IBOutlet weak var avatarContainerView: UIView! { didSet { - updateLoadingState() - } - } - - private var player: AVAudioPlayer? { - didSet { - player?.delegate = self + avatarContainerView.layer.cornerRadius = 4 + avatarView.frame = avatarContainerView.bounds + avatarContainerView.addSubview(avatarView) } } @@ -47,6 +36,8 @@ final class AudioMessageCell: UICollectionViewCell, ChatCell, SizingCell { } } + @IBOutlet weak var username: UILabel! + @IBOutlet weak var date: UILabel! @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! @IBOutlet weak var buttonPlay: UIButton! @@ -63,90 +54,31 @@ final class AudioMessageCell: UICollectionViewCell, ChatCell, SizingCell { } } - var adjustedHorizontalInsets: CGFloat = 0 - var viewModel: AnyChatItem? - - deinit { - updateTimer?.invalidate() - } - - override func awakeFromNib() { - super.awakeFromNib() - - updateTimer = Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { [weak self] _ in - guard let self = self else { return } - guard let player = self.player else { return } - - self.slider.maximumValue = Float(player.duration) - - if self.playing { - self.slider.value = Float(player.currentTime) - } - - let displayTime = self.playing ? Int(player.currentTime) : Int(player.duration) - self.labelAudioTime.text = String(format: "%02d:%02d", (displayTime/60) % 60, displayTime % 60) - } - } - - func configure() { - guard let viewModel = viewModel?.base as? AudioMessageChatItem else { - return + override var playing: Bool { + didSet { + updatePlayingState(with: buttonPlay) } - - updateAudio(viewModel: viewModel) } - func updateLoadingState() { - if loading { - activityIndicatorView.startAnimating() - labelAudioTime.isHidden = true - } else { - activityIndicatorView.stopAnimating() - labelAudioTime.isHidden = false + override var loading: Bool { + didSet { + updateLoadingState(with: activityIndicatorView, and: labelAudioTime) } } - func updatePlayingState() { - let theme = self.theme ?? Theme.light - - if playing { - let image = UIImage(named: "Player Pause")?.imageWithTint(theme.hyperlink) - buttonPlay.setImage(image, for: .normal) - player?.play() - } else { - let image = UIImage(named: "Player Play")?.imageWithTint(theme.hyperlink) - buttonPlay.setImage(image, for: .normal) - player?.stop() - } + deinit { + updateTimer?.invalidate() } - func updateAudio(viewModel: AudioMessageChatItem) { - guard !playing, !loading else { return } - guard let url = viewModel.audioURL, let localURL = viewModel.localAudioURL else { - Log.debug("[WARNING]: Audio without audio URL - \(viewModel.differenceIdentifier)") - return - } - - loading = true - - func updatePlayer() throws { - let data = try Data(contentsOf: localURL) - player = try AVAudioPlayer(data: data) - player?.prepareToPlay() + override func awakeFromNib() { + super.awakeFromNib() - loading = false - } + updateTimer = setupPlayerTimer(with: slider, and: labelAudioTime) + } - if DownloadManager.fileExists(localURL) { - try? updatePlayer() - } else { - // Download file and cache it to be used later - DownloadManager.download(url: url, to: localURL) { - DispatchQueue.main.async { - try? updatePlayer() - } - } - } + override func configure() { + configure(with: avatarView, date: date, and: username) + updateAudio() } override func prepareForReuse() { @@ -161,16 +93,15 @@ final class AudioMessageCell: UICollectionViewCell, ChatCell, SizingCell { // MARK: IBAction @IBAction func didStartSlidingSlider(_ sender: UISlider) { - playing = false + startSlidingSlider(sender) } @IBAction func didFinishSlidingSlider(_ sender: UISlider) { - player?.currentTime = Double(sender.value) - playing = true + finishSlidingSlider(sender) } @IBAction func didPressPlayButton(_ sender: UIButton) { - playing = !playing + pressPlayButton(sender) } } @@ -178,25 +109,20 @@ final class AudioMessageCell: UICollectionViewCell, ChatCell, SizingCell { // MARK: Theming extension AudioMessageCell { - override func applyTheme() { super.applyTheme() let theme = self.theme ?? .light viewPlayerBackground.backgroundColor = theme.auxiliaryBackground labelAudioTime.textColor = theme.auxiliaryText - updatePlayingState() + updatePlayingState(with: buttonPlay) } - } // MARK: AVAudioPlayerDelegate -extension AudioMessageCell: AVAudioPlayerDelegate { - - func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { - playing = false +extension AudioMessageCell { + override func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { slider.value = 0.0 } - } diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.xib b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.xib index 6faedf1ef7..bd001f00b8 100644 --- a/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.xib +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/AudioMessageCell.xib @@ -11,39 +11,63 @@ - - + + + - + - - + + + + + + + + + + + + - - + - - - + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - + + + + + + + + + + + - - + + - - - - - + + + + + + + + - + - diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/BaseAudioMessageCell.swift b/Rocket.Chat/Views/Chat/New Chat/Cells/BaseAudioMessageCell.swift new file mode 100644 index 0000000000..d2063a46df --- /dev/null +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/BaseAudioMessageCell.swift @@ -0,0 +1,115 @@ +// +// BaseAudioMessageCell.swift +// Rocket.Chat +// +// Created by Filipe Alvarenga on 15/10/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit +import Foundation +import AVFoundation +import RocketChatViewController + +class BaseAudioMessageCell: MessageHeaderCell { + var updateTimer: Timer? + var playing = false + var loading = false + + private var player: AVAudioPlayer? { + didSet { + player?.delegate = self + } + } + + func setupPlayerTimer(with slider: UISlider, and audioTimeLabel: UILabel) -> Timer { + return Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { [weak self] _ in + guard let self = self else { return } + guard let player = self.player else { return } + + slider.maximumValue = Float(player.duration) + + if self.playing { + slider.value = Float(player.currentTime) + } + + let displayTime = self.playing ? Int(player.currentTime) : Int(player.duration) + audioTimeLabel.text = String(format: "%02d:%02d", (displayTime/60) % 60, displayTime % 60) + } + } + + func updateLoadingState(with activityIndicator: UIActivityIndicatorView, and audioTimeLabel: UILabel) { + if loading { + activityIndicator.startAnimating() + audioTimeLabel.isHidden = true + } else { + activityIndicator.stopAnimating() + audioTimeLabel.isHidden = false + } + } + + func updatePlayingState(with buttonPlay: UIButton) { + let theme = self.theme ?? Theme.light + + if playing { + let image = UIImage(named: "Player Pause")?.imageWithTint(theme.hyperlink) + buttonPlay.setImage(image, for: .normal) + player?.play() + } else { + let image = UIImage(named: "Player Play")?.imageWithTint(theme.hyperlink) + buttonPlay.setImage(image, for: .normal) + player?.stop() + } + } + + func updateAudio() { + guard !playing, !loading else { return } + guard let viewModel = viewModel?.base as? AudioMessageChatItem else { return } + guard let url = viewModel.audioURL, let localURL = viewModel.localAudioURL else { + Log.debug("[WARNING]: Audio without audio URL - \(viewModel.differenceIdentifier)") + return + } + + loading = true + + func updatePlayer() throws { + let data = try Data(contentsOf: localURL) + player = try AVAudioPlayer(data: data) + player?.prepareToPlay() + + loading = false + } + + if DownloadManager.fileExists(localURL) { + try? updatePlayer() + } else { + // Download file and cache it to be used later + DownloadManager.download(url: url, to: localURL) { + DispatchQueue.main.async { + try? updatePlayer() + } + } + } + } + + func startSlidingSlider(_ sender: UISlider) { + playing = false + } + + func finishSlidingSlider(_ sender: UISlider) { + player?.currentTime = Double(sender.value) + playing = true + } + + func pressPlayButton(_ sender: UIButton) { + playing = !playing + } +} + +// MARK: AVAudioPlayerDelegate + +extension BaseAudioMessageCell: AVAudioPlayerDelegate { + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + playing = false + } +} diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/BasicMessageCell.swift b/Rocket.Chat/Views/Chat/New Chat/Cells/BasicMessageCell.swift index 278d1810b4..fb986f9e69 100644 --- a/Rocket.Chat/Views/Chat/New Chat/Cells/BasicMessageCell.swift +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/BasicMessageCell.swift @@ -22,15 +22,7 @@ final class BasicMessageCell: UICollectionViewCell, ChatCell, SizingCell { return cell }() - @IBOutlet weak var avatarContainerView: UIView! { - didSet { - avatarContainerView.layer.cornerRadius = 4 - avatarView.frame = avatarContainerView.bounds - avatarContainerView.addSubview(avatarView) - } - } - - let avatarView: AvatarView = { + lazy var avatarView: AvatarView = { let avatarView = AvatarView() avatarView.layer.cornerRadius = 4 @@ -39,6 +31,14 @@ final class BasicMessageCell: UICollectionViewCell, ChatCell, SizingCell { return avatarView }() + @IBOutlet weak var avatarContainerView: UIView! { + didSet { + avatarContainerView.layer.cornerRadius = 4 + avatarView.frame = avatarContainerView.bounds + avatarContainerView.addSubview(avatarView) + } + } + @IBOutlet weak var username: UILabel! @IBOutlet weak var date: UILabel! @IBOutlet weak var text: RCTextView! @@ -66,8 +66,8 @@ final class BasicMessageCell: UICollectionViewCell, ChatCell, SizingCell { } } - var viewModel: AnyChatItem? var adjustedHorizontalInsets: CGFloat = 0 + var viewModel: AnyChatItem? var initialTextHeightConstant: CGFloat = 0 override func awakeFromNib() { @@ -84,15 +84,7 @@ final class BasicMessageCell: UICollectionViewCell, ChatCell, SizingCell { let createdAt = viewModel.message.createdAt date.text = RCDateFormatter.time(createdAt) - username.text = viewModel.user.username - updateText() - } - - func updateText(force: Bool = false) { - guard let viewModel = viewModel?.base as? BasicMessageChatItem else { - return - } avatarView.emoji = viewModel.message.emoji avatarView.user = viewModel.message.user?.managedObject @@ -101,6 +93,14 @@ final class BasicMessageCell: UICollectionViewCell, ChatCell, SizingCell { avatarView.avatarURL = URL(string: avatar) } + updateText() + } + + func updateText(force: Bool = false) { + guard let viewModel = viewModel?.base as? BasicMessageChatItem else { + return + } + if let message = force ? MessageTextCacheManager.shared.update(for: viewModel.message.managedObject, with: theme) : MessageTextCacheManager.shared.message(for: viewModel.message.managedObject, with: theme) { if viewModel.message.temporary { message.setFontColor(MessageTextFontAttributes.systemFontColor(for: theme)) diff --git a/Rocket.Chat/Views/Chat/New Chat/Cells/MessageHeaderCell.swift b/Rocket.Chat/Views/Chat/New Chat/Cells/MessageHeaderCell.swift new file mode 100644 index 0000000000..486e408e92 --- /dev/null +++ b/Rocket.Chat/Views/Chat/New Chat/Cells/MessageHeaderCell.swift @@ -0,0 +1,44 @@ +// +// MessageHeaderCell.swift +// Rocket.Chat +// +// Created by Filipe Alvarenga on 11/10/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import UIKit +import RocketChatViewController + +class MessageHeaderCell: UICollectionViewCell, ChatCell { + var adjustedHorizontalInsets: CGFloat = 0 + var viewModel: AnyChatItem? + + lazy var avatarView: AvatarView = { + let avatarView = AvatarView() + + avatarView.layer.cornerRadius = 4 + avatarView.layer.masksToBounds = true + + return avatarView + }() + + func configure() {} + + func configure(with avatarView: AvatarView, date: UILabel, and username: UILabel) { + guard + let viewModel = viewModel?.base as? MessageHeaderChatItem, + let user = viewModel.user + else { + return + } + + date.text = viewModel.dateFormatted + username.text = user.username + avatarView.emoji = viewModel.emoji + avatarView.user = user.managedObject + + if let avatar = viewModel.avatar { + avatarView.avatarURL = URL(string: avatar) + } + } +} diff --git a/Rocket.Chat/Views/Chat/New Chat/ChatItems/AudioMessageChatItem.swift b/Rocket.Chat/Views/Chat/New Chat/ChatItems/AudioMessageChatItem.swift index 01d1f3132c..a393afc03a 100644 --- a/Rocket.Chat/Views/Chat/New Chat/ChatItems/AudioMessageChatItem.swift +++ b/Rocket.Chat/Views/Chat/New Chat/ChatItems/AudioMessageChatItem.swift @@ -10,13 +10,21 @@ import Foundation import DifferenceKit import RocketChatViewController -struct AudioMessageChatItem: ChatItem, Differentiable { +class AudioMessageChatItem: MessageHeaderChatItem, ChatItem, Differentiable { var relatedReuseIdentifier: String { - return AudioMessageCell.identifier + return hasText ? AudioCell.identifier : AudioMessageCell.identifier } - var identifier: String - var audioURL: URL? + let identifier: String + let audioURL: URL? + let hasText: Bool + + init(identifier: String, audioURL: URL?, hasText: Bool, user: UnmanagedUser?, message: UnmanagedMessage?) { + self.identifier = identifier + self.audioURL = audioURL + self.hasText = hasText + super.init(user: user, avatar: message?.avatar, emoji: message?.emoji, date: message?.createdAt) + } var localAudioURL: URL? { return DownloadManager.localFileURLFor(identifier) diff --git a/Rocket.Chat/Views/Chat/New Chat/ChatItems/MessageHeaderChatItem.swift b/Rocket.Chat/Views/Chat/New Chat/ChatItems/MessageHeaderChatItem.swift new file mode 100644 index 0000000000..8402280ce5 --- /dev/null +++ b/Rocket.Chat/Views/Chat/New Chat/ChatItems/MessageHeaderChatItem.swift @@ -0,0 +1,30 @@ +// +// MessageHeaderChatItem.swift +// Rocket.Chat +// +// Created by Filipe Alvarenga on 12/10/18. +// Copyright © 2018 Rocket.Chat. All rights reserved. +// + +import Foundation + +class MessageHeaderChatItem { + var user: UnmanagedUser? + var avatar: String? + var emoji: String? + var date: Date? + var dateFormatted: String { + guard let date = date else { + return "" + } + + return RCDateFormatter.time(date) + } + + init(user: UnmanagedUser?, avatar: String?, emoji: String?, date: Date?) { + self.user = user + self.avatar = avatar + self.emoji = emoji + self.date = date + } +}