diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 43e16caf4..99a333157 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -892,6 +892,9 @@ EC711BB124A63B58008F830C /* SecureBackupBannerPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711BA624A63B58008F830C /* SecureBackupBannerPreferences.swift */; }; EC711BB324A63B58008F830C /* SecureBackupBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC711BA824A63B58008F830C /* SecureBackupBannerCell.xib */; }; EC711BB624A63C11008F830C /* AuthenticatedSessionViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711BB524A63C11008F830C /* AuthenticatedSessionViewControllerFactory.swift */; }; + EC757B1B25B5B63C00DF5787 /* CallBubbleCellBaseContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC757B1A25B5B63C00DF5787 /* CallBubbleCellBaseContentView.swift */; }; + EC757B1D25B5B64800DF5787 /* CallBubbleCellBaseContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC757B1C25B5B64800DF5787 /* CallBubbleCellBaseContentView.xib */; }; + EC757B1F25B7741D00DF5787 /* RoomBubbleCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC757B1E25B7741D00DF5787 /* RoomBubbleCellData.swift */; }; EC7749C925A47E2600AB1295 /* CallVCEnterPipOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7749C825A47E2600AB1295 /* CallVCEnterPipOperation.swift */; }; EC7749CB25A48E7500AB1295 /* CallVCExitPipOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7749CA25A48E7500AB1295 /* CallVCExitPipOperation.swift */; }; EC7749CD25A48F6100AB1295 /* PiPAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7749CC25A48F6100AB1295 /* PiPAnimator.swift */; }; @@ -925,6 +928,9 @@ EC85D752247C0F52002C44C9 /* UNUserNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */; }; EC85D754247C0F5B002C44C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D753247C0F5B002C44C9 /* Constants.swift */; }; EC85D755247C0F84002C44C9 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC85D753247C0F5B002C44C9 /* Constants.swift */; }; + EC8A542A25B1CDB5004E0802 /* RoomBaseCallBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A542825B1CDB5004E0802 /* RoomBaseCallBubbleCell.swift */; }; + EC8A542B25B1CDB5004E0802 /* RoomBaseCallBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC8A542925B1CDB5004E0802 /* RoomBaseCallBubbleCell.xib */; }; + EC8A542F25B1CE5C004E0802 /* RoomDirectCallStatusBubbleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8A542E25B1CE5C004E0802 /* RoomDirectCallStatusBubbleCell.swift */; }; EC9A3EC524E1616900A8CFAE /* PushNotificationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9A3EC424E1616900A8CFAE /* PushNotificationStore.swift */; }; EC9A3EC624E1632C00A8CFAE /* PushNotificationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9A3EC424E1616900A8CFAE /* PushNotificationStore.swift */; }; EC9A3EC724E1634100A8CFAE /* KeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1CA87124C823E700DE9EBF /* KeyValueStore.swift */; }; @@ -2172,6 +2178,9 @@ EC711BA624A63B58008F830C /* SecureBackupBannerPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureBackupBannerPreferences.swift; sourceTree = ""; }; EC711BA824A63B58008F830C /* SecureBackupBannerCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SecureBackupBannerCell.xib; sourceTree = ""; }; EC711BB524A63C11008F830C /* AuthenticatedSessionViewControllerFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticatedSessionViewControllerFactory.swift; sourceTree = ""; }; + EC757B1A25B5B63C00DF5787 /* CallBubbleCellBaseContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallBubbleCellBaseContentView.swift; sourceTree = ""; }; + EC757B1C25B5B64800DF5787 /* CallBubbleCellBaseContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CallBubbleCellBaseContentView.xib; sourceTree = ""; }; + EC757B1E25B7741D00DF5787 /* RoomBubbleCellData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomBubbleCellData.swift; sourceTree = ""; }; EC7749C825A47E2600AB1295 /* CallVCEnterPipOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallVCEnterPipOperation.swift; sourceTree = ""; }; EC7749CA25A48E7500AB1295 /* CallVCExitPipOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallVCExitPipOperation.swift; sourceTree = ""; }; EC7749CC25A48F6100AB1295 /* PiPAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiPAnimator.swift; sourceTree = ""; }; @@ -2208,6 +2217,9 @@ EC85D74E2477E614002C44C9 /* RiotNSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RiotNSE.entitlements; sourceTree = ""; }; EC85D750247C0E8F002C44C9 /* UNUserNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNUserNotificationCenter.swift; sourceTree = ""; }; EC85D753247C0F5B002C44C9 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + EC8A542825B1CDB5004E0802 /* RoomBaseCallBubbleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomBaseCallBubbleCell.swift; sourceTree = ""; }; + EC8A542925B1CDB5004E0802 /* RoomBaseCallBubbleCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RoomBaseCallBubbleCell.xib; sourceTree = ""; }; + EC8A542E25B1CE5C004E0802 /* RoomDirectCallStatusBubbleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoomDirectCallStatusBubbleCell.swift; sourceTree = ""; }; EC9A3EC424E1616900A8CFAE /* PushNotificationStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationStore.swift; sourceTree = ""; }; EC9E9B052575AF5C0007C0A0 /* CallVCPresentOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCPresentOperation.swift; sourceTree = ""; }; EC9E9B072575AF730007C0A0 /* CallVCDismissOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVCDismissOperation.swift; sourceTree = ""; }; @@ -4406,6 +4418,7 @@ B1B5583F20EF768E00210D55 /* BubbleCells */ = { isa = PBXGroup; children = ( + EC8A542625B1CBBD004E0802 /* Call */, ECFBD5E6250F97FC00DD5F5A /* RoomCreationCollapsedBubbleCell.h */, ECFBD5E9250F97FC00DD5F5A /* RoomCreationCollapsedBubbleCell.m */, ECFBD5E4250F97FC00DD5F5A /* RoomCreationCollapsedBubbleCell.xib */, @@ -5323,6 +5336,26 @@ path = RiotNSE; sourceTree = ""; }; + EC8A542625B1CBBD004E0802 /* Call */ = { + isa = PBXGroup; + children = ( + EC8A542825B1CDB5004E0802 /* RoomBaseCallBubbleCell.swift */, + EC8A542925B1CDB5004E0802 /* RoomBaseCallBubbleCell.xib */, + EC757B1A25B5B63C00DF5787 /* CallBubbleCellBaseContentView.swift */, + EC757B1C25B5B64800DF5787 /* CallBubbleCellBaseContentView.xib */, + EC8A542725B1CD27004E0802 /* Direct */, + ); + path = Call; + sourceTree = ""; + }; + EC8A542725B1CD27004E0802 /* Direct */ = { + isa = PBXGroup; + children = ( + EC8A542E25B1CE5C004E0802 /* RoomDirectCallStatusBubbleCell.swift */, + ); + path = Direct; + sourceTree = ""; + }; EC9E9B042575AF460007C0A0 /* Operations */ = { isa = PBXGroup; children = ( @@ -5625,6 +5658,7 @@ ECF57A4F250913E4004BBF9D /* MXKTableViewCellWithLabelAndSwitch.swift */, ECF57A5625091ECC004BBF9D /* MXKTableViewCellWithTextView.swift */, B1A15BD525A526A200BDCA36 /* URLComponents.swift */, + EC757B1E25B7741D00DF5787 /* RoomBubbleCellData.swift */, ); path = Categories; sourceTree = ""; @@ -6029,6 +6063,7 @@ F083BDE81E7009ED00A9B29C /* message.caf in Resources */, B1107ECA2200B09F0038014B /* KeyBackupRecoverSuccessViewController.storyboard in Resources */, B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */, + EC8A542B25B1CDB5004E0802 /* RoomBaseCallBubbleCell.xib in Resources */, B1550FCA2420E8F500CE097B /* QRCodeReaderViewController.storyboard in Resources */, B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */, F083BE011E7009ED00A9B29C /* third_party_licenses.html in Resources */, @@ -6102,6 +6137,7 @@ B1B557AD20EF5A6D00210D55 /* DeviceView.xib in Resources */, B1B5573920EE6C4D00210D55 /* GroupRoomsViewController.xib in Resources */, B1B5597620EFB02A00210D55 /* InviteRecentTableViewCell.xib in Resources */, + EC757B1D25B5B64800DF5787 /* CallBubbleCellBaseContentView.xib in Resources */, B1B558FB20EF768F00210D55 /* RoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, EC711B8C24A63B37008F830C /* SecretsRecoveryWithPassphraseViewController.storyboard in Resources */, EC85D7192477DCD7002C44C9 /* KeyVerificationScanConfirmationViewController.storyboard in Resources */, @@ -6883,6 +6919,7 @@ EC9E9B102575B33E0007C0A0 /* CallServiceDelegate.swift in Sources */, B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */, B12D7A0223E2462200FACEDC /* UserVerificationStartViewAction.swift in Sources */, + EC757B1B25B5B63C00DF5787 /* CallBubbleCellBaseContentView.swift in Sources */, EC85D7372477DD97002C44C9 /* LocalContactsSectionHeaderContainerView.m in Sources */, B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */, B1A15BD425A4BAA800BDCA36 /* CustomSchemeURLParser.swift in Sources */, @@ -6944,6 +6981,7 @@ ECF57A85250A64F0004BBF9D /* PlaceholderedTextView.swift in Sources */, B1B4E9C424D47207004D5C33 /* BubbleReactionsViewModelBuilder.swift in Sources */, EC85D72A2477DCF2002C44C9 /* KeyVerificationManuallyVerifyViewModel.swift in Sources */, + EC8A542F25B1CE5C004E0802 /* RoomDirectCallStatusBubbleCell.swift in Sources */, B12742BC258C472800731DA6 /* LegacySSOAuthentificationSession.swift in Sources */, B1BEE74A23E093260003A4CB /* UserVerificationSessionStatusCoordinatorType.swift in Sources */, 3232ABA4225730E100AD6A5C /* DeviceVerificationStartViewAction.swift in Sources */, @@ -6966,6 +7004,7 @@ B16C029025ABC428008CA7B1 /* CustomRoundedButton.swift in Sources */, EC711B8324A63B37008F830C /* SecretsSetupRecoveryPassphraseCoordinator.swift in Sources */, B125FE23231D5E4300B72806 /* SettingsDiscoveryViewState.swift in Sources */, + EC757B1F25B7741D00DF5787 /* RoomBubbleCellData.swift in Sources */, B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */, B1BEE74723E093260003A4CB /* UserVerificationSessionStatusViewModelType.swift in Sources */, @@ -7010,6 +7049,7 @@ ECB5D98F255420F8000AD89C /* Keychain.swift in Sources */, B1B557BF20EF5B4500210D55 /* DisabledRoomInputToolbarView.m in Sources */, B1B5578620EF564900210D55 /* GroupTableViewCellWithSwitch.m in Sources */, + EC8A542A25B1CDB5004E0802 /* RoomBaseCallBubbleCell.swift in Sources */, B1098BE821ECFE52000DDA48 /* Coordinator.swift in Sources */, B11291EC238D704C0077B478 /* FloatingPoint.swift in Sources */, B1B557E920EF60F500210D55 /* MessagesSearchResultTextMsgBubbleCell.m in Sources */, diff --git a/Riot/Categories/RoomBubbleCellData.swift b/Riot/Categories/RoomBubbleCellData.swift new file mode 100644 index 000000000..3b556bdc0 --- /dev/null +++ b/Riot/Categories/RoomBubbleCellData.swift @@ -0,0 +1,50 @@ +// +// Copyright 2020 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 + +extension RoomBubbleCellData { + + /// Gathers all collapsable events in both directions (previous and next) + /// - Returns: Array of events containing collapsable events in both sides. + func allLinkedEvents() -> [MXEvent] { + var result: [MXEvent] = [] + + // add prev linked events + var prevBubbleData = prevCollapsableCellData + while prevBubbleData != nil { + // swiftlint:disable force_unwrapping + result.append(contentsOf: prevBubbleData!.events) + // swiftlint:enable force_unwrapping + prevBubbleData = prevBubbleData?.prevCollapsableCellData + } + + // add self events + result.append(contentsOf: events) + + // add next linked events + var nextBubbleData = nextCollapsableCellData + while nextBubbleData != nil { + // swiftlint:disable force_unwrapping + result.append(contentsOf: nextBubbleData!.events) + // swiftlint:enable force_unwrapping + nextBubbleData = nextBubbleData?.nextCollapsableCellData + } + + return result + } + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift new file mode 100644 index 000000000..670f153de --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.swift @@ -0,0 +1,122 @@ +// +// Copyright 2020 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 +import Reusable + +class CallBubbleCellBaseContentView: UIView { + + @IBOutlet private weak var paginationTitleView: UIView! + @IBOutlet private weak var paginationLabel: UILabel! + @IBOutlet private weak var paginationSeparatorView: UIView! + + @IBOutlet private weak var bgView: UIView! + @IBOutlet private weak var avatarImageView: MXKImageView! + @IBOutlet private weak var callerNameLabel: UILabel! + @IBOutlet private weak var callIconView: UIImageView! + @IBOutlet private weak var callTypeLabel: UILabel! + + @IBOutlet weak var bubbleOverlayContainer: UIView! + + @IBOutlet weak var bottomContainerView: UIView! + + private var _theme: Theme? + private var theme: Theme { + return _theme ?? ThemeService.shared().theme + } + + func render(_ cellData: MXKCellData) { + guard let bubbleCellData = cellData as? RoomBubbleCellData else { + return + } + + if bubbleCellData.isPaginationFirstBubble { + paginationTitleView.isHidden = false + paginationLabel.text = bubbleCellData.eventFormatter.dateString(from: bubbleCellData.date, withTime: false)?.uppercased() + } else { + paginationTitleView.isHidden = true + } + + avatarImageView.enableInMemoryCache = true + + if bubbleCellData.senderId == bubbleCellData.mxSession.myUserId { + // event sent by my user, no means in displaying our own avatar and display name + if let directUserId = bubbleCellData.mxSession.directUserId(inRoom: bubbleCellData.roomId) { + let user = bubbleCellData.mxSession.user(withUserId: directUserId) + + let placeholder = AvatarGenerator.generateAvatar(forMatrixItem: directUserId, + withDisplayName: user?.displayname) + + avatarImageView.setImageURI(user?.avatarUrl, + withType: nil, + andImageOrientation: .up, + toFitViewSize: avatarImageView.frame.size, + with: MXThumbnailingMethodCrop, + previewImage: placeholder, + mediaManager: bubbleCellData.mxSession.mediaManager) + avatarImageView.defaultBackgroundColor = .clear + + callerNameLabel.text = user?.displayname + } + } else { + avatarImageView.setImageURI(bubbleCellData.senderAvatarUrl, + withType: nil, + andImageOrientation: .up, + toFitViewSize: avatarImageView.frame.size, + with: MXThumbnailingMethodCrop, + previewImage: bubbleCellData.senderAvatarPlaceholder, + mediaManager: bubbleCellData.mxSession.mediaManager) + avatarImageView.defaultBackgroundColor = .clear + + callerNameLabel.text = bubbleCellData.senderDisplayName + } + + let events = bubbleCellData.allLinkedEvents() + + guard let event = events.first(where: { $0.eventType == .callInvite }) else { + return + } + + let callInviteEventContent = MXCallInviteEventContent(fromJSON: event.content) + let isVideoCall = callInviteEventContent?.isVideoCall() ?? false + callIconView.image = isVideoCall ? Asset.Images.callVideoIcon.image.vc_tintedImage(usingColor: theme.textSecondaryColor) : Asset.Images.voiceCallHangonIcon.image.vc_tintedImage(usingColor: theme.textSecondaryColor) + callTypeLabel.text = isVideoCall ? VectorL10n.eventFormatterCallVideo : VectorL10n.eventFormatterCallVoice + } + +} + +extension CallBubbleCellBaseContentView: NibLoadable { + +} + +extension CallBubbleCellBaseContentView: Themable { + + func update(theme: Theme) { + _theme = theme + + paginationLabel.textColor = theme.tintColor + paginationSeparatorView.backgroundColor = theme.tintColor + + bgView.backgroundColor = theme.headerBackgroundColor + callIconView.tintColor = theme.textSecondaryColor + callTypeLabel.textColor = theme.textSecondaryColor + + if let bottomContainerView = bottomContainerView as? Themable { + bottomContainerView.update(theme: theme) + } + } + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib new file mode 100644 index 000000000..e99e798aa --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Call/CallBubbleCellBaseContentView.xib @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/Direct/RoomDirectCallStatusBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Call/Direct/RoomDirectCallStatusBubbleCell.swift new file mode 100644 index 000000000..23b534e3a --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Call/Direct/RoomDirectCallStatusBubbleCell.swift @@ -0,0 +1,203 @@ +// +// Copyright 2020 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 RoomDirectCallStatusBubbleCell: RoomBaseCallBubbleCell { + + private enum Constants { + static let statusTextFontSize: CGFloat = 14 + static let statusTextInsets: UIEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 12, right: 8) + // swiftlint:disable force_unwrapping + static let statusCallBackURL: URL = URL(string: "element://call")! + // swiftlint:enable force_unwrapping + } + + private lazy var statusTextView: UITextView = { + let textView = UITextView() + textView.font = .systemFont(ofSize: Constants.statusTextFontSize) + textView.backgroundColor = .clear + textView.textColor = ThemeService.shared().theme.noticeSecondaryColor + textView.linkTextAttributes = [ + .font: UIFont.systemFont(ofSize: Constants.statusTextFontSize), + .foregroundColor: ThemeService.shared().theme.tintColor + ] + textView.textAlignment = .center + textView.contentInset = .zero + textView.isEditable = false + textView.isSelectable = false + textView.isScrollEnabled = false + textView.scrollsToTop = false + textView.textContainerInset = Constants.statusTextInsets + textView.textContainer.lineFragmentPadding = 0 + textView.delegate = self + return textView + }() + + override var bottomContentView: UIView? { + return statusTextView + } + + override func update(theme: Theme) { + super.update(theme: theme) + statusTextView.textColor = theme.noticeSecondaryColor + statusTextView.linkTextAttributes = [ + .font: UIFont.systemFont(ofSize: Constants.statusTextFontSize), + .foregroundColor: theme.tintColor + ] + } + + private func configure(withCall call: MXCall) { + switch call.state { + case .connected, + .fledgling, + .waitLocalMedia, + .createOffer, + .inviteSent, + .createAnswer, + .connecting, + .onHold, + .remotelyOnHold: + statusTextView.text = VectorL10n.eventFormatterCallYouCurrentlyIn + case .ringing: + // TODO: Waiting for design decision here + statusTextView.text = nil + case .ended: + switch call.endReason { + case .unknown, + .hangup, + .hangupElsewhere, + .remoteHangup, + .missed, + .answeredElseWhere: + statusTextView.text = VectorL10n.eventFormatterCallHasEnded + case .busy: + configureForRejectedCall(call: call) + @unknown default: + statusTextView.text = VectorL10n.eventFormatterCallHasEnded + } + case .inviteExpired, + .answeredElseWhere: + statusTextView.text = VectorL10n.eventFormatterCallHasEnded + @unknown default: + statusTextView.text = VectorL10n.eventFormatterCallHasEnded + } + } + + private func configureForRejectedCall(withEvent event: MXEvent? = nil, call: MXCall? = nil, bubbleCellData: RoomBubbleCellData? = nil) { + + let isMyReject: Bool + + if let call = call, call.isIncoming { + isMyReject = true + } else if let event = event, let bubbleCellData = bubbleCellData, event.sender == bubbleCellData.mxSession.myUserId { + isMyReject = true + } else { + isMyReject = false + } + + if isMyReject { + + let centerParagraphStyle = NSMutableParagraphStyle() + centerParagraphStyle.alignment = .center + + let mutableAttrString = NSMutableAttributedString(string: VectorL10n.eventFormatterCallYouDeclined + " " + VectorL10n.eventFormatterCallBack, attributes: [ + .font: UIFont.systemFont(ofSize: Constants.statusTextFontSize), + .foregroundColor: ThemeService.shared().theme.noticeSecondaryColor, + .paragraphStyle: centerParagraphStyle + ]) + + let range = mutableAttrString.mutableString.range(of: VectorL10n.eventFormatterCallBack) + if range.location != NSNotFound { + mutableAttrString.addAttribute(.link, value: Constants.statusCallBackURL, range: range) + } + + statusTextView.attributedText = mutableAttrString + statusTextView.isSelectable = true + } else { + statusTextView.text = VectorL10n.eventFormatterCallHasEnded + } + } + + // MARK: - MXKCellRendering + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + guard let bubbleCellData = cellData as? RoomBubbleCellData else { + return + } + + let events = bubbleCellData.allLinkedEvents() + + // getting a random event for call id is enough + guard let randomEvent = bubbleCellData.events.randomElement() else { + return + } + + guard let callEventContent = MXCallEventContent(fromJSON: randomEvent.content) else { return } + let callId = callEventContent.callId + guard let call = bubbleCellData.mxSession.callManager.call(withCallId: callId) else { + + // check events include a reject event + if let rejectEvent = events.first(where: { $0.eventType == .callReject }) { + configureForRejectedCall(withEvent: rejectEvent, bubbleCellData: bubbleCellData) + return + } + + // there is no reject event, we can just say this call has ended + statusTextView.text = VectorL10n.eventFormatterCallHasEnded + return + } + + configure(withCall: call) + } + + override func prepareForReuse() { + statusTextView.isSelectable = false + statusTextView.text = nil + statusTextView.attributedText = nil + + super.prepareForReuse() + } + +} + +// MARK: - UITextViewDelegate + +extension RoomDirectCallStatusBubbleCell { + + override func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool { + if URL == Constants.statusCallBackURL && interaction == .invokeDefaultAction { + let userInfo: [AnyHashable: Any]? + + guard let bubbleCellData = bubbleData as? RoomBubbleCellData else { + return false + } + let events = bubbleCellData.allLinkedEvents() + if let callInviteEvent = events.first(where: { $0.eventType == .callInvite }) { + userInfo = [kMXKRoomBubbleCellEventKey: callInviteEvent] + } else { + userInfo = nil + } + + self.delegate?.cell(self, didRecognizeAction: kMXKRoomBubbleCellCallBackButtonPressed, userInfo: userInfo) + return true + } + return false + } + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/RoomBaseCallBubbleCell.swift b/Riot/Modules/Room/Views/BubbleCells/Call/RoomBaseCallBubbleCell.swift new file mode 100644 index 000000000..fed709feb --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Call/RoomBaseCallBubbleCell.swift @@ -0,0 +1,113 @@ +// +// Copyright 2020 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 +import Reusable + +class RoomBaseCallBubbleCell: MXKRoomBubbleTableViewCell { + + fileprivate lazy var innerContentView: CallBubbleCellBaseContentView = { + return CallBubbleCellBaseContentView.loadFromNib() + }() + + override required init!(style: UITableViewCell.CellStyle, reuseIdentifier: String!) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + setupViews() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupViews() + } + + override func setupViews() { + super.setupViews() + + self.contentView.vc_removeAllSubviews() + self.contentView.vc_addSubViewMatchingParent(innerContentView) + + updateBottomContentView() + } + + // Properties to override + private(set) var bottomContentView: UIView? + + func updateBottomContentView() { + innerContentView.bottomContainerView.vc_removeAllSubviews() + + guard let bottomContentView = bottomContentView else { return } + innerContentView.bottomContainerView.vc_addSubViewMatchingParent(bottomContentView) + } + + class func createSizingView() -> RoomBaseCallBubbleCell { + return self.init(style: .default, reuseIdentifier: self.defaultReuseIdentifier()) + } + + // MARK: - Overrides + + override var bubbleOverlayContainer: UIView! { + get { + guard let overlayContainer = innerContentView.bubbleOverlayContainer else { + fatalError("[RoomBaseCallBubbleCell] bubbleOverlayContainer should not be used before set") + } + return overlayContainer + } + set { + super.bubbleOverlayContainer = newValue + } + } + + // MARK: - MXKCellRendering + + override func render(_ cellData: MXKCellData!) { + super.render(cellData) + + update(theme: ThemeService.shared().theme) + + guard let cellData = cellData else { + return + } + + innerContentView.render(cellData) + } + + override class func height(for cellData: MXKCellData!, withMaximumWidth maxWidth: CGFloat) -> CGFloat { + guard let cellData = cellData else { + return 0 + } + + let fittingSize = CGSize(width: maxWidth, height: UIView.layoutFittingCompressedSize.height) + guard let cell = self.init(style: .default, reuseIdentifier: self.defaultReuseIdentifier()) else { + return 0 + } + cell.render(cellData) + + return cell.contentView.systemLayoutSizeFitting(fittingSize).height + } + +} + +extension RoomBaseCallBubbleCell: Themable { + + func update(theme: Theme) { + innerContentView.update(theme: theme) + } + +} + +extension RoomBaseCallBubbleCell: NibLoadable, Reusable { + +} diff --git a/Riot/Modules/Room/Views/BubbleCells/Call/RoomBaseCallBubbleCell.xib b/Riot/Modules/Room/Views/BubbleCells/Call/RoomBaseCallBubbleCell.xib new file mode 100644 index 000000000..b7b585fca --- /dev/null +++ b/Riot/Modules/Room/Views/BubbleCells/Call/RoomBaseCallBubbleCell.xib @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + +