First part of the voice broadcast recording feature

This commit is contained in:
Philippe Loriaux
2022-10-19 18:25:29 +02:00
parent 477f0790a6
commit 852586ef7b
28 changed files with 1036 additions and 0 deletions
+1
View File
@@ -93,6 +93,7 @@ final class RoomCoordinator: NSObject, RoomCoordinatorProtocol {
TimelinePollProvider.shared.session = parameters.session
VoiceBroadcastPlaybackProvider.shared.session = parameters.session
VoiceBroadcastRecorderProvider.shared.session = parameters.session
super.init()
}
+16
View File
@@ -3272,6 +3272,22 @@ static CGSize kThreadListBarButtonItemImageSize;
}
}
}
else if (bubbleData.tag == RoomBubbleCellDataTagVoiceBroadcastRecord)
{
if (bubbleData.isPaginationFirstBubble)
{
cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithPaginationTitle;
}
else if (bubbleData.shouldHideSenderInformation)
{
cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithoutSenderInfo;
}
else
{
cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder;
}
}
else if (roomBubbleCellData.getFirstBubbleComponentWithDisplay.event.isEmote)
{
if (bubbleData.isIncoming)
@@ -178,6 +178,11 @@ typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) {
RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo,
RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle,
// - Voice broadcast recorder
RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder,
RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithoutSenderInfo,
RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithPaginationTitle,
// - Others
RoomTimelineCellIdentifierEmpty,
RoomTimelineCellIdentifierSelectedSticker,
@@ -143,6 +143,13 @@
[tableView registerClass:VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
}
- (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView
{
// Outgoing
[tableView registerClass:VoiceBroadcastRecorderOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastRecorderOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
[tableView registerClass:VoiceBroadcastRecorderOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastRecorderOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
}
#pragma mark - Mapping
- (NSDictionary<NSNumber*, Class>*)incomingTextMessageCellsMapping
@@ -318,4 +325,14 @@
};
}
- (NSDictionary<NSNumber*, Class>*)voiceBroadcastRecorderCellsMapping
{
return @{
// Outgoing
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder) : VoiceBroadcastRecorderOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithoutSenderInfo) : VoiceBroadcastRecorderOutgoingWithoutSenderInfoBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithPaginationTitle) : VoiceBroadcastRecorderOutgoingWithPaginationTitleBubbleCell.class,
};
}
@end
@@ -0,0 +1,27 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class VoiceBroadcastRecorderOutgoingWithPaginationTitleBubbleCell: VoiceBroadcastRecorderOutgoingWithoutSenderInfoBubbleCell {
override func setupViews() {
super.setupViews()
roomCellContentView?.showPaginationTitle = true
}
}
@@ -0,0 +1,41 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class VoiceBroadcastRecorderOutgoingWithoutSenderInfoBubbleCell: VoiceBroadcastRecorderBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
roomCellContentView?.showSenderInfo = false
// TODO: VB update margins attributes
let leftMargin: CGFloat = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.left + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left
let rightMargin: CGFloat = BubbleRoomCellLayoutConstants.outgoingBubbleBackgroundMargins.right + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right
roomCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
roomCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
self.setupBubbleDecorations()
}
override func update(theme: Theme) {
super.update(theme: theme)
self.bubbleBackgroundColor = theme.roomCellOutgoingBubbleBackgroundColor
}
}
@@ -0,0 +1,113 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import UIKit
class VoiceBroadcastRecorderBubbleCell: VoiceBroadcastRecorderPlainCell {
// MARK: - Properties
var bubbleBackgroundColor: UIColor?
// MARK: - Overrides
override func render(_ cellData: MXKCellData!) {
super.render(cellData)
self.update(theme: ThemeService.shared().theme)
}
override func setupViews() {
super.setupViews()
self.setupBubbleBackgroundView()
}
override func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) {
super.addVoiceBroadcastView(voiceBroadcastView, on: contentView)
self.addBubbleBackgroundViewIfNeeded(for: voiceBroadcastView)
}
// MARK: - Private
private func addBubbleBackgroundViewIfNeeded(for voiceBroadcastView: UIView) {
guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else {
return
}
self.addBubbleBackgroundView( messageBubbleBackgroundView, to: voiceBroadcastView)
messageBubbleBackgroundView.backgroundColor = self.bubbleBackgroundColor
}
private func addBubbleBackgroundView(_ bubbleBackgroundView: RoomMessageBubbleBackgroundView,
to voiceBroadcastView: UIView) {
// TODO: VB update margins attributes
let topMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.top
let leftMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left
let rightMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right
let bottomMargin = BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.bottom
let topAnchor = voiceBroadcastView.topAnchor
let leadingAnchor = voiceBroadcastView.leadingAnchor
let trailingAnchor = voiceBroadcastView.trailingAnchor
let bottomAnchor = voiceBroadcastView.bottomAnchor
NSLayoutConstraint.activate([
bubbleBackgroundView.topAnchor.constraint(equalTo: topAnchor, constant: -topMargin),
bubbleBackgroundView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: -leftMargin),
bubbleBackgroundView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: rightMargin),
bubbleBackgroundView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: bottomMargin)
])
}
private func setupBubbleBackgroundView() {
let bubbleBackgroundView = RoomMessageBubbleBackgroundView()
self.roomCellContentView?.insertSubview(bubbleBackgroundView, at: 0)
}
// The extension property MXKRoomBubbleTableViewCell.messageBubbleBackgroundView is not working there even by doing recursion
private func getBubbleBackgroundView() -> RoomMessageBubbleBackgroundView? {
guard let contentView = self.roomCellContentView else {
return nil
}
let foundView = contentView.subviews.first { view in
return view is RoomMessageBubbleBackgroundView
}
return foundView as? RoomMessageBubbleBackgroundView
}
}
// MARK: - TimestampDisplayable
extension VoiceBroadcastRecorderBubbleCell: TimestampDisplayable {
func addTimestampView(_ timestampView: UIView) {
guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else {
return
}
messageBubbleBackgroundView.addTimestampView(timestampView)
}
func removeTimestampView() {
guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else {
return
}
messageBubbleBackgroundView.removeTimestampView()
}
}
@@ -0,0 +1,65 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class VoiceBroadcastRecorderPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable {
private var voiceBroadcastView: UIView?
private var event: MXEvent?
override func render(_ cellData: MXKCellData!) {
super.render(cellData)
guard let contentView = roomCellContentView?.innerContentView,
let bubbleData = cellData as? RoomBubbleCellData,
let event = bubbleData.events.last,
let voiceBroadcastContent = VoiceBroadcastInfo(fromJSON: event.content),
voiceBroadcastContent.state == VoiceBroadcastInfo.State.started.rawValue,
let view = VoiceBroadcastRecorderProvider.shared.buildVoiceBroadcastRecorderViewForEvent(event) else {
return
}
self.event = event
self.addVoiceBroadcastView(view, on: contentView)
}
override func setupViews() {
super.setupViews()
roomCellContentView?.backgroundColor = .clear
roomCellContentView?.showSenderInfo = true
roomCellContentView?.showPaginationTitle = false
}
// The normal flow for tapping on cell content views doesn't work for bubbles without attributed strings
override func onContentViewTap(_ sender: UITapGestureRecognizer) {
guard let event = self.event else {
return
}
delegate.cell(self, didRecognizeAction: kMXKRoomBubbleCellTapOnContentView, userInfo: [kMXKRoomBubbleCellEventKey: event])
}
func addVoiceBroadcastView(_ voiceBroadcastView: UIView, on contentView: UIView) {
self.voiceBroadcastView?.removeFromSuperview()
contentView.vc_addSubViewMatchingParent(voiceBroadcastView)
self.voiceBroadcastView = voiceBroadcastView
}
}
extension VoiceBroadcastRecorderPlainCell: RoomCellThreadSummaryDisplayable {}
@@ -0,0 +1,27 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class VoiceBroadcastRecorderWithPaginationTitlePlainCell: VoiceBroadcastRecorderPlainCell {
override func setupViews() {
super.setupViews()
roomCellContentView?.showPaginationTitle = true
}
}
@@ -0,0 +1,27 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
class VoiceBroadcastRecorderWithoutSenderInfoPlainCell: VoiceBroadcastRecorderPlainCell {
override func setupViews() {
super.setupViews()
roomCellContentView?.showSenderInfo = false
}
}
@@ -58,6 +58,8 @@ NS_ASSUME_NONNULL_BEGIN
- (NSDictionary<NSNumber*, Class>*)voiceBroadcastCellsMapping;
- (NSDictionary<NSNumber*, Class>*)voiceBroadcastRecorderCellsMapping;
@end
NS_ASSUME_NONNULL_END
@@ -115,6 +115,8 @@
[self registerVoiceBroadcastCellsForTableView:tableView];
[self registerVoiceBroadcastRecorderCellsForTableView:tableView];
[tableView registerClass:RoomEmptyBubbleCell.class forCellReuseIdentifier:RoomEmptyBubbleCell.defaultReuseIdentifier];
[tableView registerClass:RoomSelectedStickerBubbleCell.class forCellReuseIdentifier:RoomSelectedStickerBubbleCell.defaultReuseIdentifier];
@@ -279,6 +281,13 @@
[tableView registerClass:VoiceBroadcastWithPaginationTitlePlainCell.class forCellReuseIdentifier:VoiceBroadcastWithPaginationTitlePlainCell.defaultReuseIdentifier];
}
- (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView
{
[tableView registerClass:VoiceBroadcastRecorderPlainCell.class forCellReuseIdentifier:VoiceBroadcastRecorderPlainCell.defaultReuseIdentifier];
[tableView registerClass:VoiceBroadcastRecorderWithoutSenderInfoPlainCell.class forCellReuseIdentifier:VoiceBroadcastRecorderWithoutSenderInfoPlainCell.defaultReuseIdentifier];
[tableView registerClass:VoiceBroadcastRecorderWithPaginationTitlePlainCell.class forCellReuseIdentifier:VoiceBroadcastRecorderWithPaginationTitlePlainCell.defaultReuseIdentifier];
}
#pragma mark Cell class association
- (NSDictionary<NSNumber*, Class>*)buildCellClasses
@@ -339,6 +348,9 @@
NSDictionary *voiceBroadcastCellsMapping = [self voiceBroadcastCellsMapping];
[cellClasses addEntriesFromDictionary:voiceBroadcastCellsMapping];
NSDictionary *voiceBroadcastRecorderCellsMapping = [self voiceBroadcastRecorderCellsMapping];
[cellClasses addEntriesFromDictionary:voiceBroadcastRecorderCellsMapping];
NSDictionary *othersCells = @{
@(RoomTimelineCellIdentifierEmpty) : RoomEmptyBubbleCell.class,
@@ -576,5 +588,14 @@
};
}
- (NSDictionary<NSNumber*, Class>*)voiceBroadcastRecorderCellsMapping
{
return @{
// Outoing
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder) : VoiceBroadcastRecorderPlainCell.class,
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithoutSenderInfo) : VoiceBroadcastRecorderWithoutSenderInfoPlainCell.class,
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithPaginationTitle) : VoiceBroadcastRecorderWithPaginationTitlePlainCell.class
};
}
@end