mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-23 01:52:44 +02:00
Voip all
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user