This commit is contained in:
ismailgulek
2021-02-03 14:01:29 +03:00
parent 7a231fead7
commit 0593e18d07
26 changed files with 983 additions and 29 deletions
@@ -1961,6 +1961,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Register the session to the widgets manager
[[WidgetManager sharedManager] addMatrixSession:mxSession];
// register the session to the call service
[_callService addMatrixSession:mxSession];
[mxSessionArray addObject:mxSession];
// Do the one time check on device id
@@ -1974,6 +1977,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni
// Update home data sources
[_masterTabBarController removeMatrixSession:mxSession];
// remove session from the call service
[_callService removeMatrixSession:mxSession];
// Update the widgets manager
[[WidgetManager sharedManager] removeMatrixSession:mxSession];
@@ -26,7 +26,8 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag)
RoomBubbleCellDataTagKeyVerificationNoDisplay,
RoomBubbleCellDataTagKeyVerificationRequestIncomingApproval,
RoomBubbleCellDataTagKeyVerificationRequest,
RoomBubbleCellDataTagKeyVerificationConclusion
RoomBubbleCellDataTagKeyVerificationConclusion,
RoomBubbleCellDataTagCall
};
/**
@@ -125,6 +125,18 @@ static NSAttributedString *timestampVerticalWhitespace = nil;
// Membership events can be collapsed together
self.collapsable = YES;
// Collapse them by default
self.collapsed = YES;
}
break;
case MXEventTypeCallInvite:
case MXEventTypeCallReject:
{
self.tag = RoomBubbleCellDataTagCall;
// Call events can be collapsed together
self.collapsable = YES;
// Collapse them by default
self.collapsed = YES;
}
@@ -233,6 +245,17 @@ static NSAttributedString *timestampVerticalWhitespace = nil;
{
return YES;
}
else if (self.tag == RoomBubbleCellDataTagCall && cellData.tag == RoomBubbleCellDataTagCall)
{
// Check if the same call
MXEvent * event1 = self.events.firstObject;
MXCallEventContent *eventContent1 = [MXCallEventContent modelFromJSON:event1.content];
MXEvent * event2 = cellData.events.firstObject;
MXCallEventContent *eventContent2 = [MXCallEventContent modelFromJSON:event2.content];
return [eventContent1.callId isEqualToString:eventContent2.callId];
}
if (self.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor || cellData.tag == RoomBubbleCellDataTagRoomCreateWithPredecessor)
{
@@ -711,6 +734,9 @@ static NSAttributedString *timestampVerticalWhitespace = nil;
// One single bubble per membership event
shouldAddEvent = NO;
break;
case RoomBubbleCellDataTagCall:
shouldAddEvent = NO;
break;
case RoomBubbleCellDataTagRoomCreateConfiguration:
shouldAddEvent = NO;
break;
@@ -755,6 +781,10 @@ static NSAttributedString *timestampVerticalWhitespace = nil;
case MXEventTypeRoomJoinRules:
shouldAddEvent = NO;
break;
case MXEventTypeCallInvite:
case MXEventTypeCallReject:
shouldAddEvent = NO;
break;
default:
break;
}
+5
View File
@@ -27,6 +27,11 @@
#import "UIViewController+RiotSearch.h"
/**
Notification string used to indicate call tile tapped in a room. Notification object will be the `RoomBubbleCellData` object.
*/
extern NSString *const kRoomCallTileTapped;
@interface RoomViewController : MXKRoomViewController
// The preview header
+24
View File
@@ -123,6 +123,8 @@
#import "Riot-Swift.h"
NSString *const kRoomCallTileTapped = @"RoomCallTileTapped";
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, UIScrollViewAccessibilityDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate,
ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate,
@@ -363,6 +365,9 @@
[self.bubblesTableView registerClass:RoomCreationCollapsedBubbleCell.class forCellReuseIdentifier:RoomCreationCollapsedBubbleCell.defaultReuseIdentifier];
[self.bubblesTableView registerClass:RoomCreationWithPaginationCollapsedBubbleCell.class forCellReuseIdentifier:RoomCreationWithPaginationCollapsedBubbleCell.defaultReuseIdentifier];
// call cells
[self.bubblesTableView registerClass:RoomDirectCallStatusBubbleCell.class forCellReuseIdentifier:RoomDirectCallStatusBubbleCell.defaultReuseIdentifier];
[self vc_removeBackTitle];
// Replace the default input toolbar view.
@@ -2025,6 +2030,10 @@
{
cellViewClass = bubbleData.isPaginationFirstBubble ? RoomCreationWithPaginationCollapsedBubbleCell.class : RoomCreationCollapsedBubbleCell.class;
}
else if (bubbleData.tag == RoomBubbleCellDataTagCall)
{
cellViewClass = RoomDirectCallStatusBubbleCell.class;
}
else if (bubbleData.isIncoming)
{
if (bubbleData.isAttachmentWithThumbnail)
@@ -2195,6 +2204,14 @@
}
}
}
else if (bubbleData.tag == RoomBubbleCellDataTagCall)
{
if ([bubbleData isKindOfClass:[RoomBubbleCellData class]])
{
// post notification `RoomCallTileTapped`
[[NSNotificationCenter defaultCenter] postNotificationName:kRoomCallTileTapped object:bubbleData];
}
}
else
{
// Show contextual menu on single tap if bubble is not collapsed
@@ -2327,6 +2344,13 @@
[self showReactionHistoryForEventId:tappedEventId animated:YES];
}
}
else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellCallBackButtonPressed])
{
MXEvent *callInviteEvent = userInfo[kMXKRoomBubbleCellEventKey];
MXCallInviteEventContent *eventContent = [MXCallInviteEventContent modelFromJSON:callInviteEvent.content];
[self roomInputToolbarView:self.inputToolbarView placeCallWithVideo2:eventContent.isVideoCall];
}
else
{
// Keep default implementation for other actions
@@ -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)
}
}
}
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB" customClass="CallBubbleCellBaseContentView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="414" height="189"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="pdr-Jo-LHQ">
<rect key="frame" x="54" y="0.0" width="344" height="183"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="H6S-AE-DTm" userLabel="Pagination Title View">
<rect key="frame" x="0.0" y="0.0" width="344" height="24"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GQH-kh-LXA" userLabel="Pagination Label">
<rect key="frame" x="0.0" y="0.0" width="334" height="18"/>
<constraints>
<constraint firstAttribute="height" constant="18" id="T80-uB-O9X"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<color key="textColor" red="0.66666666669999997" green="0.66666666669999997" blue="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="YdR-PH-eGM" userLabel="Pagination Separator View">
<rect key="frame" x="0.0" y="23" width="344" height="1"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<accessibility key="accessibilityConfiguration" identifier="PaginationTitleView"/>
<constraints>
<constraint firstItem="GQH-kh-LXA" firstAttribute="leading" secondItem="H6S-AE-DTm" secondAttribute="leading" id="5Sg-i3-2k4"/>
<constraint firstAttribute="trailing" secondItem="YdR-PH-eGM" secondAttribute="trailing" id="7GM-7V-73q"/>
<constraint firstItem="GQH-kh-LXA" firstAttribute="top" secondItem="H6S-AE-DTm" secondAttribute="top" id="Adq-g9-KrE"/>
<constraint firstAttribute="bottom" secondItem="YdR-PH-eGM" secondAttribute="bottom" id="V8F-CX-Tyd"/>
<constraint firstItem="YdR-PH-eGM" firstAttribute="leading" secondItem="H6S-AE-DTm" secondAttribute="leading" id="akV-i8-jvw"/>
<constraint firstAttribute="height" constant="24" id="bIm-sL-gvZ"/>
<constraint firstAttribute="trailing" secondItem="GQH-kh-LXA" secondAttribute="trailing" constant="10" id="uxE-lK-q0K"/>
<constraint firstItem="YdR-PH-eGM" firstAttribute="top" secondItem="GQH-kh-LXA" secondAttribute="bottom" constant="5" id="w1Z-CT-v0T"/>
</constraints>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="155-SF-bbg">
<rect key="frame" x="0.0" y="30" width="344" height="153"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="rRX-FX-vnm">
<rect key="frame" x="0.0" y="0.0" width="344" height="153"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2TD-ng-0tw">
<rect key="frame" x="52" y="0.0" width="240" height="16"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="mBC-kR-MIe"/>
</constraints>
</view>
<view clipsSubviews="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uox-ay-bcL" customClass="MXKImageView">
<rect key="frame" x="152" y="16" width="40" height="40"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="bZm-SK-dlV"/>
<constraint firstAttribute="width" constant="40" id="kXN-4l-8f2"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="20"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Caller Name" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ONW-WU-t2g">
<rect key="frame" x="8" y="56" width="328" height="27"/>
<constraints>
<constraint firstAttribute="height" constant="27" id="iNJ-kn-6dl"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="kdm-vj-rsY">
<rect key="frame" x="136" y="83" width="72" height="20"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="voice_call_hangon_icon" translatesAutoresizingMaskIntoConstraints="NO" id="iQL-Bn-D9b">
<rect key="frame" x="0.0" y="2" width="16" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="16" id="9F5-f1-Iws"/>
<constraint firstAttribute="height" constant="16" id="OdD-1h-kkV"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Voice call" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="alq-3S-yHg">
<rect key="frame" x="18" y="3" width="54" height="14.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="height" constant="20" id="xI1-oH-QU6"/>
</constraints>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ce0-Kh-hec">
<rect key="frame" x="0.0" y="103" width="344" height="50"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<constraints>
<constraint firstItem="ONW-WU-t2g" firstAttribute="leading" secondItem="rRX-FX-vnm" secondAttribute="leading" constant="8" id="3l8-ws-bGs"/>
<constraint firstAttribute="trailing" secondItem="ONW-WU-t2g" secondAttribute="trailing" constant="8" id="ZOz-xJ-Bx2"/>
<constraint firstAttribute="trailing" secondItem="ce0-Kh-hec" secondAttribute="trailing" id="ixk-2w-Lcy"/>
<constraint firstItem="ce0-Kh-hec" firstAttribute="leading" secondItem="rRX-FX-vnm" secondAttribute="leading" id="l3J-mv-ebk"/>
</constraints>
</stackView>
</subviews>
<color key="backgroundColor" red="0.95294117649999999" green="0.97254901959999995" blue="0.99215686270000003" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="rRX-FX-vnm" firstAttribute="top" secondItem="155-SF-bbg" secondAttribute="top" id="45L-cH-SuQ"/>
<constraint firstItem="rRX-FX-vnm" firstAttribute="leading" secondItem="155-SF-bbg" secondAttribute="leading" id="50U-vN-RJD"/>
<constraint firstAttribute="trailing" secondItem="rRX-FX-vnm" secondAttribute="trailing" id="5QQ-uv-5Pb"/>
<constraint firstAttribute="bottom" secondItem="rRX-FX-vnm" secondAttribute="bottom" id="Dlh-3F-Elp"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="8"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
</stackView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="8Fb-Sw-AW5">
<rect key="frame" x="0.0" y="0.0" width="414" height="189"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="8Fb-Sw-AW5" secondAttribute="bottom" id="1bC-yu-IPf"/>
<constraint firstItem="8Fb-Sw-AW5" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="3j3-0h-Bsx"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="pdr-Jo-LHQ" secondAttribute="trailing" constant="16" id="7xn-GF-gTt"/>
<constraint firstItem="pdr-Jo-LHQ" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="54" id="96R-ah-got"/>
<constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="8Fb-Sw-AW5" secondAttribute="trailing" id="SEd-5d-C8r"/>
<constraint firstAttribute="bottom" secondItem="pdr-Jo-LHQ" secondAttribute="bottom" constant="6" id="TYc-7V-K67"/>
<constraint firstItem="8Fb-Sw-AW5" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="Ywq-vx-otD"/>
<constraint firstItem="pdr-Jo-LHQ" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="fgy-XO-coX"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="avatarImageView" destination="Uox-ay-bcL" id="G9f-eA-sMU"/>
<outlet property="bgView" destination="155-SF-bbg" id="bTR-Re-lnw"/>
<outlet property="bottomContainerView" destination="ce0-Kh-hec" id="Bab-JS-9Th"/>
<outlet property="bubbleOverlayContainer" destination="8Fb-Sw-AW5" id="E46-Pt-XXj"/>
<outlet property="callIconView" destination="iQL-Bn-D9b" id="Cih-PG-EM2"/>
<outlet property="callTypeLabel" destination="alq-3S-yHg" id="lgA-Dw-PH2"/>
<outlet property="callerNameLabel" destination="ONW-WU-t2g" id="0Vj-Xn-CLD"/>
<outlet property="paginationLabel" destination="GQH-kh-LXA" id="Mww-Ps-yv1"/>
<outlet property="paginationSeparatorView" destination="YdR-PH-eGM" id="aK7-LF-awm"/>
<outlet property="paginationTitleView" destination="H6S-AE-DTm" id="yep-a0-QYq"/>
</connections>
<point key="canvasLocation" x="44.927536231884062" y="-144.97767857142856"/>
</view>
</objects>
<resources>
<image name="voice_call_hangon_icon" width="24" height="24"/>
</resources>
</document>
@@ -0,0 +1,207 @@
//
// 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:
if call.isIncoming {
// should not be here
statusTextView.text = nil
} else {
statusTextView.text = VectorL10n.eventFormatterCallYouCurrentlyIn
}
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
}
}
@@ -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 {
}
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="160" id="KGk-i7-Jjw" customClass="RoomBaseCallBubbleCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="160"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="320" height="160"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<point key="canvasLocation" x="214.49275362318843" y="56.919642857142854"/>
</tableViewCell>
</objects>
</document>