Revert "Video conference and new Call Views"

This reverts commit 608577034d.
This commit is contained in:
Christophe Deschamps 2022-01-04 12:27:14 +01:00
parent e23a4a7951
commit 306162228e
198 changed files with 8070 additions and 7792 deletions

52
Classes/AudioHelper.m Normal file
View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "AudioHelper.h"
@implementation AudioHelper
+ (NSArray *)bluetoothRoutes {
return @[AVAudioSessionPortBluetoothHFP, AVAudioSessionPortCarAudio, AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE ];
}
+ (AVAudioSessionPortDescription *)bluetoothAudioDevice {
return [AudioHelper audioDeviceFromTypes:[AudioHelper bluetoothRoutes]];
}
+ (AVAudioSessionPortDescription *)builtinAudioDevice {
NSArray *builtinRoutes = @[ AVAudioSessionPortBuiltInMic ];
return [AudioHelper audioDeviceFromTypes:builtinRoutes];
}
+ (AVAudioSessionPortDescription *)speakerAudioDevice {
NSArray *builtinRoutes = @[ AVAudioSessionPortBuiltInSpeaker ];
return [AudioHelper audioDeviceFromTypes:builtinRoutes];
}
+ (AVAudioSessionPortDescription *)audioDeviceFromTypes:(NSArray *)types {
NSArray *routes = [[AVAudioSession sharedInstance] availableInputs];
for (AVAudioSessionPortDescription *route in routes) {
if ([types containsObject:route.portType]) {
return route;
}
}
return nil;
}
@end

View file

@ -0,0 +1,317 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="landscape" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CallIncomingView">
<connections>
<outlet property="addressLabel" destination="78f-eb-xdx" id="Qjw-7G-oqG"/>
<outlet property="avatarImage" destination="19" id="20"/>
<outlet property="earlyMediaView" destination="lUj-2n-Afx" id="cHv-3Y-lIQ"/>
<outlet property="landscapeView" destination="r7T-Et-xrQ" id="rA1-2K-fUf"/>
<outlet property="nameLabel" destination="hjQ-4P-bKP" id="Elh-o8-zM9"/>
<outlet property="portraitView" destination="25" id="6Gy-ZX-kTl"/>
<outlet property="tabBar" destination="4" id="9gd-FT-jaI"/>
<outlet property="tabVideoBar" destination="vIQ-QP-ooa" id="JZq-9l-pOy"/>
<outlet property="view" destination="25" id="26"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="6e0-76-KvA" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="25">
<rect key="frame" x="0.0" y="42" width="667" height="333"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="3" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="19" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="155" y="147" width="355" height="107"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<button opaque="NO" tag="2" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Cro-ww-VIC" userLabel="headerBar">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" button="YES" notEnabled="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="boldSystem" pointSize="27"/>
<state key="normal" title="INCOMING CALL" backgroundImage="color_F.png">
<color key="titleColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
<view tag="6" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4" userLabel="tabBar">
<rect key="frame" x="0.0" y="270" width="667" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<button opaque="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6" userLabel="declineButton">
<rect key="frame" x="0.0" y="0.0" width="334" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="16"/>
</connections>
</button>
<button opaque="NO" tag="8" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7" userLabel="acceptButton">
<rect key="frame" x="333" y="0.0" width="334" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_audio_start_default.png" backgroundImage="color_A.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_audio_start_disabled.png"/>
<state key="highlighted" backgroundImage="color_L.png"/>
<connections>
<action selector="onAcceptClick:" destination="-1" eventType="touchUpInside" id="15"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view hidden="YES" tag="9" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vIQ-QP-ooa" userLabel="tabVideoBar">
<rect key="frame" x="0.0" y="270" width="667" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<button opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KnH-hj-g47" userLabel="declineButton">
<rect key="frame" x="0.0" y="0.0" width="222" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="Nrs-UR-Hb9"/>
</connections>
</button>
<button opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wYo-ty-Rwk" userLabel="acceptAudioOnlyButton">
<rect key="frame" x="222" y="0.0" width="223" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_audio_start_default.png" backgroundImage="color_A.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_audio_start_disabled.png"/>
<state key="highlighted" backgroundImage="color_L.png"/>
<connections>
<action selector="onAcceptAudioOnlyClick:" destination="-1" eventType="touchUpInside" id="N9h-i1-ejZ"/>
</connections>
</button>
<button opaque="NO" tag="12" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tX0-eE-di5" userLabel="acceptButton">
<rect key="frame" x="445" y="0.0" width="222" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_video_start_default.png" backgroundImage="color_A.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_video_start_disabled.png"/>
<state key="selected" image="call_video_start_disabled.png"/>
<state key="highlighted" backgroundImage="color_L.png"/>
<connections>
<action selector="onAcceptClick:" destination="-1" eventType="touchUpInside" id="XvK-9T-J2j"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lUj-2n-Afx" userLabel="earlyMediaView">
<rect key="frame" x="0.0" y="35" width="667" height="265"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" tag="5" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="john.doe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" translatesAutoresizingMaskIntoConstraints="NO" id="78f-eb-xdx" userLabel="addressLabel">
<rect key="frame" x="0.0" y="111" width="667" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<color key="textColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="4" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="19" translatesAutoresizingMaskIntoConstraints="NO" id="hjQ-4P-bKP" userLabel="nameLabel">
<rect key="frame" x="0.0" y="79" width="667" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="-141.59999999999999" y="42.728635682158924"/>
</view>
<view contentMode="scaleToFill" id="Znq-C0-ZAc" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r7T-Et-xrQ">
<rect key="frame" x="0.0" y="42" width="667" height="333"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" tag="2" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="E9b-qt-GBq" userLabel="headerBar">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" button="YES" notEnabled="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="boldSystem" pointSize="27"/>
<state key="normal" title="INCOMING CALL" backgroundImage="color_F.png">
<color key="titleColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
<view tag="6" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Tc-7G-eqT" userLabel="tabBar">
<rect key="frame" x="0.0" y="270" width="667" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<button opaque="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="qpN-iY-3Ao" userLabel="declineButton">
<rect key="frame" x="0.0" y="0.0" width="334" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="JKz-8y-c9T"/>
</connections>
</button>
<button opaque="NO" tag="8" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8dX-3c-mba" userLabel="acceptButton">
<rect key="frame" x="333" y="0.0" width="334" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_audio_start_default.png" backgroundImage="color_A.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_audio_start_disabled.png"/>
<state key="highlighted" backgroundImage="color_L.png"/>
<connections>
<action selector="onAcceptClick:" destination="-1" eventType="touchUpInside" id="17v-kE-yOu"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view hidden="YES" tag="9" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PPE-Fd-wDf" userLabel="tabVideoBar">
<rect key="frame" x="0.0" y="270" width="667" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<button opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="i8b-dr-IYG" userLabel="declineButton">
<rect key="frame" x="0.0" y="0.0" width="221" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="mjN-BB-4ph"/>
</connections>
</button>
<button opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QYg-9G-We0" userLabel="acceptAudioOnlyButton">
<rect key="frame" x="221" y="0.0" width="224" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_audio_start_default.png" backgroundImage="color_A.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_audio_start_disabled.png"/>
<state key="highlighted" backgroundImage="color_L.png"/>
<connections>
<action selector="onAcceptAudioOnlyClick:" destination="-1" eventType="touchUpInside" id="veh-c0-GOe"/>
</connections>
</button>
<button opaque="NO" tag="12" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="sbd-NW-OZx" userLabel="acceptButton">
<rect key="frame" x="445" y="0.0" width="222" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_video_start_default.png" backgroundImage="color_A.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_video_start_disabled.png"/>
<state key="selected" image="call_video_start_disabled.png"/>
<state key="highlighted" backgroundImage="color_L.png"/>
<connections>
<action selector="onAcceptClick:" destination="-1" eventType="touchUpInside" id="RLl-y6-yhs"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<imageView userInteractionEnabled="NO" tag="3" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="Q0C-CO-AYR" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="110" y="74" width="180" height="180"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="F11-a0-ZL6" userLabel="earlyMediaView">
<rect key="frame" x="45" y="73" width="250" height="187"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" tag="5" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="john.doe@sip.linphone.org" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" translatesAutoresizingMaskIntoConstraints="NO" id="tsb-6p-cAk" userLabel="addressLabel">
<rect key="frame" x="297" y="166" width="370" height="33"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<color key="textColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="4" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="19" translatesAutoresizingMaskIntoConstraints="NO" id="WsB-At-ejv" userLabel="nameLabel">
<rect key="frame" x="297" y="121" width="370" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="762.39999999999998" y="-88.605697151424295"/>
</view>
</objects>
<resources>
<image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="call_audio_start_default.png" width="58.400001525878906" height="58.400001525878906"/>
<image name="call_audio_start_disabled.png" width="58.400001525878906" height="58.400001525878906"/>
<image name="call_hangup_default.png" width="67.199996948242188" height="58.400001525878906"/>
<image name="call_hangup_disabled.png" width="67.199996948242188" height="58.400001525878906"/>
<image name="call_video_start_default.png" width="64" height="58.400001525878906"/>
<image name="call_video_start_disabled.png" width="64" height="58.400001525878906"/>
<image name="color_A.png" width="2" height="2"/>
<image name="color_D.png" width="2" height="2"/>
<image name="color_F.png" width="2" height="2"/>
<image name="color_I.png" width="2" height="2"/>
<image name="color_L.png" width="2" height="2"/>
</resources>
</document>

View file

@ -0,0 +1,623 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CallOutgoingView">
<connections>
<outlet property="addressLabel" destination="2fa-Ag-3GW" id="2Oe-UW-rPC"/>
<outlet property="avatarImage" destination="bNo-O5-DWh" id="eqo-0s-UoN"/>
<outlet property="declineButton" destination="AaM-cH-pvW" id="nET-cl-Kfb"/>
<outlet property="declineButton_earlyMedia" destination="4AS-bf-34N" id="SQm-um-bgq"/>
<outlet property="eightButton" destination="t0H-I1-ukQ" id="vOh-0j-erj"/>
<outlet property="fiveButton" destination="iU3-pL-jRO" id="1wx-4h-Gvl"/>
<outlet property="fourButton" destination="Xfq-Ej-VHd" id="aAF-q4-Thm"/>
<outlet property="hashButton" destination="yiW-6Q-8Gn" id="SvV-4h-CQq"/>
<outlet property="landscapeView" destination="Czn-ec-dh8" id="ZIk-2g-9Qk"/>
<outlet property="microButton" destination="26e-Pj-2Oh" id="jXh-0n-yg7"/>
<outlet property="nameLabel" destination="d5s-yP-8VE" id="0VY-HP-ovD"/>
<outlet property="nineButton" destination="H98-w7-FtJ" id="Y29-sY-bs7"/>
<outlet property="numpadButton" destination="1Ya-Ok-b84" id="R3l-8u-Bcl"/>
<outlet property="numpadView" destination="5UV-gd-B5P" id="bOc-Em-4g1"/>
<outlet property="oneButton" destination="YNy-y3-Vfb" id="mib-jj-IWo"/>
<outlet property="portraitView" destination="25" id="26I-da-00C"/>
<outlet property="routesBluetoothButton" destination="SH1-xD-Agw" id="ifX-Dy-Tcb"/>
<outlet property="routesButton" destination="29K-Sd-aHF" id="MVM-Mb-OWE"/>
<outlet property="routesEarpieceButton" destination="zs4-Zy-FrP" id="TOt-D3-635"/>
<outlet property="routesSpeakerButton" destination="oKz-6p-EAF" id="yIx-qF-Gd7"/>
<outlet property="routesView" destination="iyf-nk-ORJ" id="LBJ-Rm-VUX"/>
<outlet property="sevenButton" destination="yAW-CE-SCK" id="Zhm-Oo-U7v"/>
<outlet property="sixButton" destination="c3c-wj-OO9" id="Dmk-J0-8Mc"/>
<outlet property="speakerButton" destination="G7m-Av-QlR" id="UHW-L2-NDM"/>
<outlet property="starButton" destination="MdJ-SK-bIH" id="Ri3-EC-1oq"/>
<outlet property="threeButton" destination="gXh-2W-waU" id="Neg-lD-uwc"/>
<outlet property="twoButton" destination="wrx-1J-g34" id="e17-3F-hcV"/>
<outlet property="view" destination="25" id="26"/>
<outlet property="zeroButton" destination="49E-lz-Dx1" id="Car-8R-zs1"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="HpM-if-114" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="25">
<rect key="frame" x="0.0" y="42" width="414" height="854"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" tag="2" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NFl-sb-0TV" userLabel="headerBar">
<rect key="frame" x="0.0" y="0.0" width="414" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" button="YES" notEnabled="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="boldSystem" pointSize="27"/>
<state key="normal" title="OUTGOING CALL" backgroundImage="color_F.png">
<color key="titleColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
<view hidden="YES" tag="37" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iyf-nk-ORJ" userLabel="routesView">
<rect key="frame" x="311" y="593" width="104" height="198"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="38" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="xEp-Iw-uII" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="104" height="198"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<button opaque="NO" tag="39" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SH1-xD-Agw" userLabel="routesBluetoothButton" customClass="UIBluetoothButton">
<rect key="frame" x="0.0" y="0.0" width="104" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Bluetooth"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<inset key="titleEdgeInsets" minX="0.0" minY="28" maxX="0.0" maxY="0.0"/>
<state key="normal" image="route_bluetooth_default.png">
<color key="titleColor" red="0.19582480192184448" green="0.21824681758880615" blue="0.24259182810783386" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="route_bluetooth_disabled.png">
<color key="titleColor" red="0.58667385578155518" green="0.64151597023010254" blue="0.69586050510406494" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="route_bluetooth_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png">
<color key="titleColor" red="0.76000285148620605" green="0.21515019237995148" blue="0.12376989424228668" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onRoutesBluetoothClick:" destination="-1" eventType="touchUpInside" id="0bq-3n-xii"/>
</connections>
</button>
<button opaque="NO" tag="40" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="zs4-Zy-FrP" userLabel="routesEarpieceButton">
<rect key="frame" x="0.0" y="66" width="104" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Receiver"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<inset key="titleEdgeInsets" minX="0.0" minY="28" maxX="0.0" maxY="0.0"/>
<state key="normal" image="route_earpiece_default.png">
<color key="titleColor" red="0.19582480192184448" green="0.21824681758880615" blue="0.24259182810783386" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="route_earpiece_disabled.png">
<color key="titleColor" red="0.58667385578155518" green="0.64151597023010254" blue="0.69586050510406494" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="route_earpiece_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png">
<color key="titleColor" red="0.76000285148620605" green="0.21515019237995148" blue="0.12376989424228668" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onRoutesEarpieceClick:" destination="-1" eventType="touchUpInside" id="YRr-Kn-GgV"/>
</connections>
</button>
<button opaque="NO" tag="41" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oKz-6p-EAF" userLabel="routesSpeakerButton" customClass="UISpeakerButton">
<rect key="frame" x="0.0" y="132" width="104" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Speaker"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<inset key="titleEdgeInsets" minX="0.0" minY="28" maxX="0.0" maxY="0.0"/>
<state key="normal" image="route_speaker_default.png">
<color key="titleColor" red="0.19582480192184448" green="0.21824681758880615" blue="0.24259182810783386" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="route_speaker_disabled.png">
<color key="titleColor" red="0.58667385578155518" green="0.64151597023010254" blue="0.69586050510406494" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="route_speaker_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png">
<color key="titleColor" red="0.76000285148620605" green="0.21515019237995148" blue="0.12376989424228668" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="onRoutesSpeakerClick:" destination="-1" eventType="touchUpInside" id="g4L-6P-Iqw"/>
</connections>
</button>
</subviews>
</view>
<view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8Qi-Cq-3XH" userLabel="tabBar">
<rect key="frame" x="0.0" y="791" width="414" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="vyh-Us-8kj" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="414" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</imageView>
<button opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AaM-cH-pvW" userLabel="declineButton">
<rect key="frame" x="0.0" y="0.0" width="207" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="Ebl-hM-8F9"/>
</connections>
</button>
<button opaque="NO" tag="3057" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="1Ya-Ok-b84" userLabel="numpadButton" customClass="UIToggleButton">
<rect key="frame" x="0.0" y="0.0" width="94" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/>
<state key="normal" image="footer_dialer_default.png" backgroundImage="color_C.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="footer_dialer_disabled.png"/>
<state key="selected" image="dialer_alt_back.png"/>
<state key="highlighted" backgroundImage="color_A.png"/>
<connections>
<action selector="onNumpadClick:" destination="-1" eventType="touchUpInside" id="EeE-il-gTD"/>
</connections>
</button>
<button opaque="NO" tag="3011" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4AS-bf-34N" userLabel="declineButton_earlyMedia">
<rect key="frame" x="93" y="0.0" width="114" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="alt-Ma-tLc"/>
</connections>
</button>
<button opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="26e-Pj-2Oh" userLabel="microButton" customClass="UIMutedMicroButton">
<rect key="frame" x="207" y="0.0" width="104" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="micro_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="micro_disabled.png"/>
<state key="selected" image="micro_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
</button>
<button hidden="YES" opaque="NO" tag="54" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="29K-Sd-aHF" userLabel="routesButton" customClass="UIToggleButton">
<rect key="frame" x="311" y="0.0" width="104" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Route"/>
<inset key="titleEdgeInsets" minX="0.0" minY="38" maxX="0.0" maxY="0.0"/>
<state key="normal" image="routes_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="routes_disabled.png"/>
<state key="selected" image="routes_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
<connections>
<action selector="onRoutesClick:" destination="-1" eventType="touchUpInside" id="hXX-8a-7M4"/>
</connections>
</button>
<button opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="G7m-Av-QlR" userLabel="speakerButton" customClass="UISpeakerButton">
<rect key="frame" x="311" y="0.0" width="104" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="speaker_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="speaker_disabled.png"/>
<state key="selected" image="speaker_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
</button>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<label opaque="NO" userInteractionEnabled="NO" tag="5" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="john.doe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="2fa-Ag-3GW" userLabel="addressLabel">
<rect key="frame" x="4" y="281" width="405" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<color key="textColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="4" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="d5s-yP-8VE" userLabel="nameLabel">
<rect key="frame" x="0.0" y="207" width="414" height="71"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" tag="6" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="bNo-O5-DWh" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="96" y="374" width="220" height="273"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<view hidden="YES" tag="3024" contentMode="scaleToFill" id="5UV-gd-B5P" userLabel="numpadView">
<rect key="frame" x="0.0" y="65" width="414" height="726"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3025" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="YNy-y3-Vfb" userLabel="1" customClass="UIDigitButton">
<rect key="frame" x="-1" y="1" width="134" height="169"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.40000001000000002" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" label="1"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_1_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="numpad_1_over.png"/>
<state key="highlighted" image="numpad_1_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3026" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wrx-1J-g34" userLabel="2" customClass="UIDigitButton">
<rect key="frame" x="138" y="1" width="134" height="169"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="2"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_2_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_2_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3027" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gXh-2W-waU" userLabel="3" customClass="UIDigitButton">
<rect key="frame" x="277" y="1" width="134" height="169"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="3"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_3_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_3_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3028" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Xfq-Ej-VHd" userLabel="4" customClass="UIDigitButton">
<rect key="frame" x="-1" y="186" width="134" height="166"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="4"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_4_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_4_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3029" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iU3-pL-jRO" userLabel="5" customClass="UIDigitButton">
<rect key="frame" x="138" y="186" width="134" height="166"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="5"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_5_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_5_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3030" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c3c-wj-OO9" userLabel="6" customClass="UIDigitButton">
<rect key="frame" x="277" y="186" width="134" height="166"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="6"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_6_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_6_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3031" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAW-CE-SCK" userLabel="7" customClass="UIDigitButton">
<rect key="frame" x="-1" y="360" width="134" height="166"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="7"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_7_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_7_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3032" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="t0H-I1-ukQ" userLabel="8" customClass="UIDigitButton">
<rect key="frame" x="138" y="360" width="134" height="166"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="8"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_8_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_8_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3033" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H98-w7-FtJ" userLabel="9" customClass="UIDigitButton">
<rect key="frame" x="277" y="360" width="134" height="166"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="9"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_9_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_9_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3034" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MdJ-SK-bIH" userLabel="*" customClass="UIDigitButton">
<rect key="frame" x="-1" y="534" width="134" height="183"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Star"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_star_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_star_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3035" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="49E-lz-Dx1" userLabel="0" customClass="UIDigitButton">
<rect key="frame" x="138" y="534" width="134" height="183"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="0"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_0_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_0_over.png" backgroundImage="numpad_over_background.png"/>
</button>
<button opaque="NO" clearsContextBeforeDrawing="NO" tag="3036" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yiW-6Q-8Gn" userLabel="#" customClass="UIDigitButton">
<rect key="frame" x="277" y="534" width="134" height="183"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Hash"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<state key="normal" image="numpad_hash_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="highlighted" image="numpad_hash_over.png" backgroundImage="numpad_over_background.png"/>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="42.028985507246382" y="111.16071428571428"/>
</view>
<view contentMode="scaleToFill" id="YAs-im-wvR" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Czn-ec-dh8">
<rect key="frame" x="0.0" y="42" width="414" height="854"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" tag="2" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oAv-Cz-FaR" userLabel="headerBar">
<rect key="frame" x="0.0" y="0.0" width="414" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" button="YES" notEnabled="YES"/>
</accessibility>
<fontDescription key="fontDescription" type="boldSystem" pointSize="27"/>
<state key="normal" title="OUTGOING CALL" backgroundImage="color_F.png">
<color key="titleColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
<label opaque="NO" userInteractionEnabled="NO" tag="5" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="john.doe@sip.linphone.org" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" translatesAutoresizingMaskIntoConstraints="NO" id="Fj8-Pu-ShI" userLabel="addressLabel">
<rect key="frame" x="185" y="426" width="228" height="84"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/>
<color key="textColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" tag="4" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="19" translatesAutoresizingMaskIntoConstraints="NO" id="ubQ-ZN-AhT" userLabel="nameLabel">
<rect key="frame" x="185" y="310" width="229" height="95"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" tag="6" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="1ZH-n6-QZ0" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="68" y="74" width="112" height="701"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<view hidden="YES" tag="37" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="EaW-SR-bqv" userLabel="routesView">
<rect key="frame" x="311" y="593" width="104" height="198"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="38" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="Tvo-Jg-0h8" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="104" height="198"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<button opaque="NO" tag="39" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N7s-YH-dJQ" userLabel="routesBluetoothButton" customClass="UIBluetoothButton">
<rect key="frame" x="0.0" y="0.0" width="104" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Bluetooth"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<inset key="titleEdgeInsets" minX="0.0" minY="28" maxX="0.0" maxY="0.0"/>
<state key="normal" image="route_bluetooth_default.png">
<color key="titleColor" red="0.19582480192184448" green="0.21824681758880615" blue="0.24259182810783386" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="route_bluetooth_disabled.png">
<color key="titleColor" red="0.58667385578155518" green="0.64151597023010254" blue="0.69586050510406494" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="route_bluetooth_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png">
<color key="titleColor" red="0.76000285148620605" green="0.21515019237995148" blue="0.12376989424228668" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
<button opaque="NO" tag="40" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bg7-Cv-tyO" userLabel="routesEarpieceButton">
<rect key="frame" x="0.0" y="66" width="104" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Receiver"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<inset key="titleEdgeInsets" minX="0.0" minY="28" maxX="0.0" maxY="0.0"/>
<state key="normal" image="route_earpiece_default.png">
<color key="titleColor" red="0.19582480192184448" green="0.21824681758880615" blue="0.24259182810783386" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="route_earpiece_disabled.png">
<color key="titleColor" red="0.58667385578155518" green="0.64151597023010254" blue="0.69586050510406494" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="route_earpiece_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png">
<color key="titleColor" red="0.76000285148620605" green="0.21515019237995148" blue="0.12376989424228668" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
<button opaque="NO" tag="41" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yd8-ed-g8u" userLabel="routesSpeakerButton" customClass="UISpeakerButton">
<rect key="frame" x="0.0" y="132" width="104" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Speaker"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<inset key="titleEdgeInsets" minX="0.0" minY="28" maxX="0.0" maxY="0.0"/>
<state key="normal" image="route_speaker_default.png">
<color key="titleColor" red="0.19582480192184448" green="0.21824681758880615" blue="0.24259182810783386" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="route_speaker_disabled.png">
<color key="titleColor" red="0.58667385578155518" green="0.64151597023010254" blue="0.69586050510406494" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="route_speaker_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png">
<color key="titleColor" red="0.76000285148620605" green="0.21515019237995148" blue="0.12376989424228668" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
</button>
</subviews>
</view>
<view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vJ1-A8-eFV" userLabel="tabBar">
<rect key="frame" x="0.0" y="791" width="414" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="eYb-yI-yVB" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="414" height="63"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
</imageView>
<button opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jfG-HJ-FPI" userLabel="declineButton">
<rect key="frame" x="0.0" y="0.0" width="207" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Decline"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="call_hangup_default.png" backgroundImage="color_D.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="call_hangup_disabled.png"/>
<state key="highlighted" backgroundImage="color_I.png"/>
<connections>
<action selector="onDeclineClick:" destination="-1" eventType="touchUpInside" id="voJ-Cd-XHg"/>
</connections>
</button>
<button opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="jLg-1u-ulZ" userLabel="microButton" customClass="UIMutedMicroButton">
<rect key="frame" x="207" y="0.0" width="104" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="micro_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="micro_disabled.png"/>
<state key="selected" image="micro_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
</button>
<button hidden="YES" opaque="NO" tag="54" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="61B-DK-jZ6" userLabel="routesButton" customClass="UIToggleButton">
<rect key="frame" x="311" y="0.0" width="104" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Route"/>
<inset key="titleEdgeInsets" minX="0.0" minY="38" maxX="0.0" maxY="0.0"/>
<state key="normal" image="routes_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="routes_disabled.png"/>
<state key="selected" image="routes_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
</button>
<button opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5CY-aN-NLX" userLabel="speakerButton" customClass="UISpeakerButton">
<rect key="frame" x="311" y="0.0" width="104" height="63"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Accept"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<state key="normal" image="speaker_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="speaker_disabled.png"/>
<state key="selected" image="speaker_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
</button>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="502.17391304347831" y="39.84375"/>
</view>
</objects>
<resources>
<image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="call_hangup_default.png" width="67.199996948242188" height="58.400001525878906"/>
<image name="call_hangup_disabled.png" width="67.199996948242188" height="58.400001525878906"/>
<image name="color_A.png" width="2" height="2"/>
<image name="color_C.png" width="2" height="2"/>
<image name="color_D.png" width="2" height="2"/>
<image name="color_E.png" width="2" height="2"/>
<image name="color_F.png" width="2" height="2"/>
<image name="color_I.png" width="2" height="2"/>
<image name="dialer_alt_back.png" width="42.400001525878906" height="42.400001525878906"/>
<image name="footer_dialer_default.png" width="44" height="44"/>
<image name="footer_dialer_disabled.png" width="44" height="44"/>
<image name="micro_default.png" width="47.200000762939453" height="60"/>
<image name="micro_disabled.png" width="47.200000762939453" height="60"/>
<image name="micro_selected.png" width="47.200000762939453" height="60"/>
<image name="numpad_0_default.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_0_over.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_1_default.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_1_over.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_2_default.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_2_over.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_3_default.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_3_over.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_4_default.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_4_over.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_5_default.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_5_over.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_6_default.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_6_over.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_7_default.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_7_over.png" width="84.800003051757812" height="77.599998474121094"/>
<image name="numpad_8_default.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_8_over.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_9_default.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_9_over.png" width="84.800003051757812" height="79.199996948242188"/>
<image name="numpad_hash_default.png" width="84.800003051757812" height="78.400001525878906"/>
<image name="numpad_hash_over.png" width="84.800003051757812" height="78.400001525878906"/>
<image name="numpad_over_background.png" width="2" height="2"/>
<image name="numpad_star_default.png" width="84.800003051757812" height="81.599998474121094"/>
<image name="numpad_star_over.png" width="84.800003051757812" height="81.599998474121094"/>
<image name="route_bluetooth_default.png" width="26.399999618530273" height="41.599998474121094"/>
<image name="route_bluetooth_disabled.png" width="26.399999618530273" height="41.599998474121094"/>
<image name="route_bluetooth_selected.png" width="26.399999618530273" height="41.599998474121094"/>
<image name="route_earpiece_default.png" width="37.599998474121094" height="38.400001525878906"/>
<image name="route_earpiece_disabled.png" width="37.599998474121094" height="38.400001525878906"/>
<image name="route_earpiece_selected.png" width="38.400001525878906" height="38.400001525878906"/>
<image name="route_speaker_default.png" width="44" height="41.599998474121094"/>
<image name="route_speaker_disabled.png" width="44" height="41.599998474121094"/>
<image name="route_speaker_selected.png" width="44" height="41.599998474121094"/>
<image name="routes_default.png" width="60" height="40.799999237060547"/>
<image name="routes_disabled.png" width="60" height="40.799999237060547"/>
<image name="routes_selected.png" width="60" height="40.799999237060547"/>
<image name="speaker_default.png" width="44" height="41.599998474121094"/>
<image name="speaker_disabled.png" width="44" height="41.599998474121094"/>
<image name="speaker_selected.png" width="44" height="41.599998474121094"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,16 +17,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
extension Optional {
var logable: Any {
switch self {
case .none:
return "<nil>|⭕️"
case let .some(value):
return value
}
}
}
@interface CallConferenceTableView : UITableViewController
- (void)update;
@end

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "CallConferenceTableView.h"
#import "UICallConferenceCell.h"
#import "LinphoneManager.h"
#import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation CallConferenceTableView
#pragma mark - UI change
- (void)update {
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource Functions
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *kCellId = NSStringFromClass(UICallConferenceCell.class);
UICallConferenceCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
if (cell == nil) {
cell = [[UICallConferenceCell alloc] initWithIdentifier:kCellId];
}
LinphoneConference *c = [CallManager.instance getConference];
if (c != nil) {
[cell setParticipant:bctbx_list_nth_data(linphone_conference_get_participant_list(c),(int)indexPath.row)];
}
cell.contentView.userInteractionEnabled = NO;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [CallManager.instance getConference] ? linphone_conference_get_participant_count([CallManager.instance getConference]) : 0;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1e-5;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 1e-5;
}
@end

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
#import "UICompositeView.h"
#import "TPMultiLayoutViewController.h"
#import "UIRoundedImageView.h"
#include "LinphoneManager.h"
@protocol IncomingCallViewDelegate <NSObject>
- (void)incomingCallAccepted:(LinphoneCall *)call evenWithVideo:(BOOL)video;
- (void)incomingCallDeclined:(LinphoneCall *)call;
- (void)incomingCallAborted:(LinphoneCall *)call;
@end
@interface CallIncomingView : TPMultiLayoutViewController <UICompositeViewDelegate> {
}
@property(nonatomic) Boolean earlyMedia;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(nonatomic, strong) IBOutlet UILabel *addressLabel;
@property(nonatomic, strong) IBOutlet UIRoundedImageView *avatarImage;
@property(nonatomic, assign) LinphoneCall *call;
@property(nonatomic, strong) id<IncomingCallViewDelegate> delegate;
@property(weak, nonatomic) IBOutlet UIView *tabVideoBar;
@property(weak, nonatomic) IBOutlet UIView *tabBar;
@property (weak, nonatomic) IBOutlet UIView *earlyMediaView;
- (IBAction)onAcceptClick:(id)event;
- (IBAction)onDeclineClick:(id)event;
- (IBAction)onAcceptAudioOnlyClick:(id)sender;
@end

150
Classes/CallIncomingView.m Normal file
View file

@ -0,0 +1,150 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "CallIncomingView.h"
#import "LinphoneManager.h"
#import "FastAddressBook.h"
#import "PhoneMainView.h"
#import "Utils.h"
@implementation CallIncomingView
#pragma mark - ViewController Functions
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(callUpdateEvent:)
name:kLinphoneCallUpdate
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCallUpdate object:nil];
_call = NULL;
}
#pragma mark - UICompositeViewDelegate Functions
static UICompositeViewDescription *compositeDescription = nil;
+ (UICompositeViewDescription *)compositeViewDescription {
if (compositeDescription == nil) {
compositeDescription = [[UICompositeViewDescription alloc] init:self.class
statusBar:StatusBarView.class
tabBar:nil
sideMenu:CallSideMenuView.class
fullscreen:false
isLeftFragment:YES
fragmentWith:nil];
compositeDescription.darkBackground = true;
}
return compositeDescription;
}
- (UICompositeViewDescription *)compositeViewDescription {
return self.class.compositeViewDescription;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
if (_earlyMedia && [LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"] && linphone_core_get_calls_nb(LC) < 2) {
_earlyMediaView.hidden = NO;
linphone_core_set_native_video_window_id(LC, (__bridge void *)(_earlyMediaView));
}
if (_call) {
[self update];
}
}
#pragma mark - Event Functions
- (void)callUpdateEvent:(NSNotification *)notif {
LinphoneCall *acall = [[notif.userInfo objectForKey:@"call"] pointerValue];
LinphoneCallState astate = [[notif.userInfo objectForKey:@"state"] intValue];
[self callUpdate:acall state:astate];
}
- (void)callUpdate:(LinphoneCall *)acall state:(LinphoneCallState)astate {
if (_call == acall && (astate == LinphoneCallEnd || astate == LinphoneCallError)) {
[_delegate incomingCallAborted:_call];
} else if ([LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"]) {
LinphoneCallState state = linphone_call_get_state(_call);
if (state == LinphoneCallIncomingReceived) {
LOGI(@"Auto answering call");
[self onAcceptClick:nil];
}
}
}
#pragma mark -
- (void)update {
const LinphoneAddress *addr = linphone_call_get_remote_address(_call);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr withAddressLabel:_addressLabel];
char *uri = linphone_address_as_string_uri_only(addr);
ms_free(uri);
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:YES withRoundedRadius:YES];
_tabBar.hidden = linphone_call_params_video_enabled(linphone_call_get_remote_params(_call));
_tabVideoBar.hidden = !_tabBar.hidden;
}
#pragma mark - Property Functions
static void hideSpinner(LinphoneCall *call, void *user_data) {
CallIncomingView *thiz = (__bridge CallIncomingView *)user_data;
thiz.earlyMedia = TRUE;
thiz.earlyMediaView.hidden = NO;
linphone_core_set_native_video_window_id(LC, (__bridge void *)(thiz.earlyMediaView));
}
- (void)setCall:(LinphoneCall *)call {
_call = call;
_earlyMedia = FALSE;
if ([LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"] && linphone_core_get_calls_nb(LC) < 2) {
linphone_call_accept_early_media(_call);
// linphone_call_params_get_used_video_codec return 0 if no video stream enabled
if (linphone_call_params_get_used_video_codec(linphone_call_get_current_params(_call))) {
linphone_call_set_next_video_frame_decoded_callback(call, hideSpinner, (__bridge void *)(self));
}
} else {
_earlyMediaView.hidden = YES;
}
[self update];
[self callUpdate:_call state:linphone_call_get_state(call)];
}
#pragma mark - Action Functions
- (IBAction)onAcceptClick:(id)event {
[_delegate incomingCallAccepted:_call evenWithVideo:YES];
}
- (IBAction)onDeclineClick:(id)event {
[_delegate incomingCallDeclined:_call];
}
- (IBAction)onAcceptAudioOnlyClick:(id)sender {
[_delegate incomingCallAccepted:_call evenWithVideo:NO];
}
@end

View file

@ -37,13 +37,15 @@ import AVFoundation
static var theCallManager: CallManager?
let providerDelegate: ProviderDelegate! // to support callkit
let callController: CXCallController! // to support callkit
var core: Core?
var lc: Core?
@objc var speakerBeforePause : Bool = false
@objc var nextCallIsTransfer: Bool = false
var referedFromCall: String?
var referedToCall: String?
var endCallkit: Bool = false
var globalState : GlobalState = .Off
var actionsToPerformOnceWhenCoreIsOn : [(()->Void)] = []
var conference: Conference?
@ -64,8 +66,8 @@ import AVFoundation
@objc func setCore(core: OpaquePointer) {
self.core = Core.getSwiftObject(cObject: core)
self.core?.addDelegate(delegate: self)
lc = Core.getSwiftObject(cObject: core)
lc?.addDelegate(delegate: self)
}
@objc static func getAppData(call: OpaquePointer) -> CallAppData? {
@ -105,7 +107,7 @@ import AVFoundation
if (callId == nil) {
return nil
}
let calls = core?.calls
let calls = lc?.calls
if let callTmp = calls?.first(where: { $0.callLog?.callId == callId }) {
return callTmp
}
@ -113,8 +115,8 @@ import AVFoundation
}
@objc func stopLinphoneCore() {
if (core?.callsNb == 0) {
core?.stopAsync()
if (lc?.callsNb == 0) {
lc?.stopAsync()
}
}
@ -138,6 +140,60 @@ import AVFoundation
return false
}
@objc func changeRouteToSpeaker() {
for device in lc!.audioDevices {
if (device.type == AudioDeviceType.Speaker) {
lc!.outputAudioDevice = device
break
}
}
UIDevice.current.isProximityMonitoringEnabled = false
}
@objc func changeRouteToBluetooth() {
for device in lc!.audioDevices {
if (device.type == AudioDeviceType.Bluetooth || device.type == AudioDeviceType.BluetoothA2DP) {
lc!.outputAudioDevice = device
break
}
}
UIDevice.current.isProximityMonitoringEnabled = (lc!.callsNb > 0)
}
@objc func changeRouteToDefault() {
lc!.outputAudioDevice = lc!.defaultOutputAudioDevice
}
@objc func isBluetoothAvailable() -> Bool {
for device in lc!.audioDevices {
if (device.type == AudioDeviceType.Bluetooth || device.type == AudioDeviceType.BluetoothA2DP) {
return true;
}
}
return false;
}
@objc func isSpeakerEnabled() -> Bool {
if let outputDevice = lc!.outputAudioDevice {
return outputDevice.type == AudioDeviceType.Speaker
}
return false
}
@objc func isBluetoothEnabled() -> Bool {
if let outputDevice = lc!.outputAudioDevice {
return (outputDevice.type == AudioDeviceType.Bluetooth || outputDevice.type == AudioDeviceType.BluetoothA2DP)
}
return false
}
@objc func isReceiverEnabled() -> Bool {
if let outputDevice = lc!.outputAudioDevice {
return outputDevice.type == AudioDeviceType.Microphone
}
return false
}
func requestTransaction(_ transaction: CXTransaction, action: String) {
callController.request(transaction) { error in
@ -183,7 +239,7 @@ import AVFoundation
func acceptCall(call: Call, hasVideo:Bool) {
do {
let callParams = try core!.createCallParams(call: call)
let callParams = try lc!.createCallParams(call: call)
callParams.videoEnabled = hasVideo
if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (AppManager.network() == .network_2g)
@ -214,7 +270,7 @@ import AVFoundation
}
let sAddr = Address.getSwiftObject(cObject: addr!)
if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && core?.conference?.isIn != true) {
if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && !isInConference()) {
let uuid = UUID()
let name = FastAddressBook.displayName(for: addr) ?? "unknow"
let handle = CXHandle(type: .generic, value: sAddr.asStringUriOnly())
@ -235,7 +291,7 @@ import AVFoundation
func doCall(addr: Address, isSas: Bool) throws {
let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try CallManager.instance().core!.createCallParams(call: nil)
let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil)
if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
lcallParams.lowBandwidthEnabled = true
@ -250,7 +306,7 @@ import AVFoundation
}
if (CallManager.instance().nextCallIsTransfer) {
let call = CallManager.instance().core!.currentCall
let call = CallManager.instance().lc!.currentCall
try call?.transferTo(referTo: addr)
CallManager.instance().nextCallIsTransfer = false
} else {
@ -261,7 +317,7 @@ import AVFoundation
if (isSas) {
lcallParams.mediaEncryption = .ZRTP
}
let call = CallManager.instance().core!.inviteAddressWithParams(addr: addr, params: lcallParams)
let call = CallManager.instance().lc!.inviteAddressWithParams(addr: addr, params: lcallParams)
if (call != nil) {
// The LinphoneCallAppData object should be set on call creation with callback
// - (void)onCall:StateChanged:withMessage:. If not, we are in big trouble and expect it to crash
@ -278,6 +334,32 @@ import AVFoundation
}
}
@objc func groupCall() {
if (CallManager.callKitEnabled()) {
let calls = lc?.calls
if (calls == nil || calls!.isEmpty) {
return
}
let firstCall = calls!.first?.callLog?.callId ?? ""
let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
if (currentUuid == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
return
}
let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
let transcation = CXTransaction(action: groupAction)
requestTransaction(transcation, action: "groupCall")
setResumeCalls()
} else {
try? lc?.addAllToConference()
}
}
@objc func removeAllCallInfos() {
providerDelegate.callInfos.removeAll()
providerDelegate.uuids.removeAll()
@ -337,7 +419,7 @@ import AVFoundation
}
@objc func setHeldOtherCalls(exceptCallid: String) {
for call in CallManager.instance().core!.calls {
for call in CallManager.instance().lc!.calls {
if (call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote) {
setHeld(call: call, hold: true)
}
@ -345,7 +427,7 @@ import AVFoundation
}
func setResumeCalls() {
for call in CallManager.instance().core!.calls {
for call in CallManager.instance().lc!.calls {
if (call.state == .Paused || call.state == .Pausing || call.state == .PausedByRemote) {
setHeld(call: call, hold: false)
}
@ -362,7 +444,7 @@ import AVFoundation
@objc func acceptVideo(call: OpaquePointer, confirm: Bool) {
let sCall = Call.getSwiftObject(cObject: call)
let params = try? core?.createCallParams(call: sCall)
let params = try? lc?.createCallParams(call: sCall)
params?.videoEnabled = confirm
try? sCall.acceptUpdate(params: params)
}
@ -383,7 +465,7 @@ import AVFoundation
for call in CallManager.instance().providerDelegate.uuids {
let callId = CallManager.instance().providerDelegate.callInfos[call.value]?.callId
if (callId != nil) {
let call = CallManager.instance().core?.getCallByCallid(callId: callId!)
let call = CallManager.instance().lc?.getCallByCallid(callId: callId!)
if (call != nil && call?.state != .PushIncomingReceived) {
// sometimes (for example) due to network, registration failed, in this case, keep the call
continue
@ -397,6 +479,12 @@ import AVFoundation
CallManager.instance().endCallkit = false
}
}
func onConferenceStateChanged(core: Core, conference: Conference, state: Conference.State) {
if (state == .Terminated) {
CallManager.instance().conference = nil
}
}
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
let callLog = call.callLog
@ -448,6 +536,11 @@ import AVFoundation
}
}
}
if (CallManager.instance().speakerBeforePause) {
CallManager.instance().speakerBeforePause = false
CallManager.instance().changeRouteToSpeaker()
}
break
case .OutgoingInit,
.OutgoingProgress,
@ -476,6 +569,14 @@ import AVFoundation
displayName = contactName
}
UIDevice.current.isProximityMonitoringEnabled = false
if (CallManager.instance().lc!.callsNb == 0) {
CallManager.instance().changeRouteToDefault()
// disable this because I don't find anygood reason for it: _bluetoothAvailable = FALSE;
// furthermore it introduces a bug when calling multiple times since route may not be
// reconfigured between cause leading to bluetooth being disabled while it should not
//CallManager.instance().bluetoothEnabled = false
}
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
@ -534,6 +635,12 @@ import AVFoundation
break
}
if (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning) {
let check = call.currentParams?.videoEnabled
if ((call.currentParams?.videoEnabled ?? false) && CallManager.instance().isReceiverEnabled()) {
CallManager.instance().changeRouteToSpeaker()
}
}
}
// post Notification kLinphoneCallUpdate
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [
@ -546,13 +653,13 @@ import AVFoundation
// Audio messages
@objc func activateAudioSession() {
core?.activateAudioSession(actived: true)
lc?.activateAudioSession(actived: true)
}
@objc func getSpeakerSoundCard() -> String? {
var speakerCard: String? = nil
var earpieceCard: String? = nil
core?.audioDevices.forEach { device in
lc?.audioDevices.forEach { device in
if (device.hasCapability(capability: .CapabilityPlay)) {
if (device.type == .Speaker) {
speakerCard = device.id
@ -564,46 +671,116 @@ import AVFoundation
return speakerCard != nil ? speakerCard : earpieceCard
}
// Local Conference
// Conference
@objc func hostConference() -> Bool {
return conference != nil
}
@objc func startLocalConference() {
if (CallManager.callKitEnabled()) {
let calls = core?.calls
if (calls == nil || calls!.isEmpty) {
func addAllToConference() {
if (conference == nil) {
guard let cp = try?lc?.createConferenceParams() else {
Log.directLog(BCTBX_LOG_ERROR, text: "Unable to create conference parameters")
return
}
let firstCall = calls!.first?.callLog?.callId ?? ""
let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
if (currentUuid == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
return
if let currentCall = lc?.currentCall, let currentParams = currentCall.currentParams {
cp.videoEnabled = currentParams.videoEnabled
}
conference = try?lc?.createConferenceWithParams(params: cp)
}
lc?.calls.forEach { call in
if (call.conference == nil || call.conference?.participantCount == 1) {
try?conference?.addParticipant(call: call)
}
let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
let transcation = CXTransaction(action: groupAction)
requestTransaction(transcation, action: "groupCall")
setResumeCalls()
} else {
addAllToLocalConference()
}
}
func addAllToLocalConference() {
do {
if let core = core, let params = try? core.createConferenceParams() {
params.videoEnabled = false // We disable video for local conferencing (cf Android)
let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params)
try conference?.addParticipants(calls: core.calls)
}
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)")
@objc func getConference() -> OpaquePointer? {
guard let core = lc else {
return nil
}
return (core.conference != nil) ? core.conference?.getCobject : (core.currentCall?.conference != nil) ? core.currentCall!.conference!.getCobject : nil
}
func getConference() -> Conference? {
guard let core = lc else {
return nil
}
return (core.conference != nil) ? core.conference : (core.currentCall?.conference != nil) ? core.currentCall!.conference : nil
}
@objc func isInConference() -> Bool {
return isInConferenceAsHost()||isInConferenceAsGuest()
}
@objc func isInConferenceAsGuest() -> Bool {
guard let core = lc else {
return false
}
return !isInConferenceAsHost() && core.currentCall != nil && core.currentCall?.conference != nil && (core.currentCall?.conference!.participantCount)! > 1
}
@objc func isInConferenceAsHost() -> Bool {
guard let core = lc else {
return false
}
return core.conference?.isIn == true
}
@objc func hasConferenceAsGuest() -> Bool {
guard let core = lc else {
return false
}
if (core.callsNb<=1) {
return false
}
var found = false
core.calls.forEach {
let c = $0.conference
if (c != nil && c!.participantCount > 1 && hostConference()) {
found = true
return
}
}
return found
}
@objc func getCallFor(participant : OpaquePointer) -> OpaquePointer? {
let p = Participant.getSwiftObject(cObject: participant)
guard let core = lc else {
return nil
}
var call:Call? = nil
core.calls.forEach { (callIt) in
let c = callIt.conference
c?.participantList.forEach { (p2) in
if (p2.address?.asStringUriOnly() == p.address?.asStringUriOnly()) {
call = callIt
return
}
}
}
return call?.getCobject
}
@objc func inVideoConf() -> Bool {
guard let core = lc else {
return false
}
let result = isInConference() && (getConference()?.currentParams?.isVideoEnabled == true || core.currentCall?.currentParams?.videoEnabled == true)
NSLog("cdes \(result) \(core.currentCall?.currentParams?.videoEnabled)")
return result
}
@objc func inAudioConf() -> Bool {
guard let core = lc else {
return false
}
return core.conference?.isIn == true && core.conference != nil && core.currentCall?.conference?.currentParams?.isVideoEnabled == false
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
#import "UICompositeView.h"
#import "TPMultiLayoutViewController.h"
#include "linphone/linphonecore.h"
#import "UIRoundedImageView.h"
#import "UIDigitButton.h"
@interface CallOutgoingView : TPMultiLayoutViewController <UICompositeViewDelegate> {
}
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(weak, nonatomic) IBOutlet UISpeakerButton *speakerButton;
@property(weak, nonatomic) IBOutlet UILabel *addressLabel;
@property(weak, nonatomic) IBOutlet UIToggleButton *routesButton;
@property(weak, nonatomic) IBOutlet UIView *routesView;
@property(weak, nonatomic) IBOutlet UIBluetoothButton *routesBluetoothButton;
@property(weak, nonatomic) IBOutlet UIButton *routesEarpieceButton;
@property(weak, nonatomic) IBOutlet UISpeakerButton *routesSpeakerButton;
@property(weak, nonatomic) IBOutlet UIMutedMicroButton *microButton;
@property (weak, nonatomic) IBOutlet UIButton *declineButton;
@property (weak, nonatomic) IBOutlet UIToggleButton *numpadButton;
@property (strong, nonatomic) IBOutlet UIView *numpadView;
@property(nonatomic, strong) IBOutlet UIDigitButton *oneButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *twoButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *threeButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *fourButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *fiveButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *sixButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *sevenButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *eightButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *nineButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *starButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *zeroButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *hashButton;
@property (weak, nonatomic) IBOutlet UIButton *declineButton_earlyMedia;
- (IBAction)onDeclineClick:(id)sender;
@end

267
Classes/CallOutgoingView.m Normal file
View file

@ -0,0 +1,267 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "CallOutgoingView.h"
#import "PhoneMainView.h"
@implementation CallOutgoingView
#pragma mark - UICompositeViewDelegate Functions
static UICompositeViewDescription *compositeDescription = nil;
+ (UICompositeViewDescription *)compositeViewDescription {
if (compositeDescription == nil) {
compositeDescription = [[UICompositeViewDescription alloc] init:self.class
statusBar:StatusBarView.class
tabBar:nil
sideMenu:CallSideMenuView.class
fullscreen:false
isLeftFragment:NO
fragmentWith:nil];
compositeDescription.darkBackground = true;
}
return compositeDescription;
}
- (UICompositeViewDescription *)compositeViewDescription {
return self.class.compositeViewDescription;
}
- (void)viewDidLoad {
_routesEarpieceButton.enabled = !IPAD;
[_zeroButton setDigit:'0'];
[_zeroButton setDtmf:true];
[_oneButton setDigit:'1'];
[_oneButton setDtmf:true];
[_twoButton setDigit:'2'];
[_twoButton setDtmf:true];
[_threeButton setDigit:'3'];
[_threeButton setDtmf:true];
[_fourButton setDigit:'4'];
[_fourButton setDtmf:true];
[_fiveButton setDigit:'5'];
[_fiveButton setDtmf:true];
[_sixButton setDigit:'6'];
[_sixButton setDtmf:true];
[_sevenButton setDigit:'7'];
[_sevenButton setDtmf:true];
[_eightButton setDigit:'8'];
[_eightButton setDtmf:true];
[_nineButton setDigit:'9'];
[_nineButton setDtmf:true];
[_starButton setDigit:'*'];
[_starButton setDtmf:true];
[_hashButton setDigit:'#'];
[_hashButton setDtmf:true];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(bluetoothAvailabilityUpdateEvent:)
name:kLinphoneBluetoothAvailabilityUpdate
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(callUpdateEvent:)
name:kLinphoneCallUpdate
object:nil];
LinphoneCall *call = linphone_core_get_current_call(LC);
if (!call) {
return;
}
const LinphoneAddress *addr = linphone_call_get_remote_address(call);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr withAddressLabel:_addressLabel];
char *uri = linphone_address_as_string_uri_only(addr);
ms_free(uri);
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
[self hideSpeaker:LinphoneManager.instance.bluetoothAvailable];
[_speakerButton update];
[_microButton update];
[_routesButton update];
[self hidePad:TRUE animated:FALSE];
[self callUpdate:call state:linphone_call_get_state(call) animated:true];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// if there is no call (for whatever reason), we must wait viewDidAppear method
// before popping current view, because UICompositeView cannot handle view change
// directly in viewWillAppear (this would lead to crash in deallocated memory - easily
// reproductible on iPad mini).
if (!linphone_core_get_current_call(LC)) {
[PhoneMainView.instance popCurrentView];
}
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self];
}
- (IBAction)onRoutesBluetoothClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[CallManager.instance changeRouteToBluetooth];
}
- (IBAction)onRoutesEarpieceClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[CallManager.instance changeRouteToDefault];
}
- (IBAction)onRoutesSpeakerClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[CallManager.instance changeRouteToSpeaker];
}
- (IBAction)onRoutesClick:(id)sender {
if ([_routesView isHidden]) {
[self hideRoutes:FALSE animated:ANIMATED];
} else {
[self hideRoutes:TRUE animated:ANIMATED];
}
}
- (IBAction)onNumpadClick:(id)sender {
if ([_numpadView isHidden]) {
[self hidePad:FALSE animated:ANIMATED];
} else {
[self hidePad:TRUE animated:ANIMATED];
}
}
- (IBAction)onDeclineClick:(id)sender {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
[CallManager.instance terminateCallWithCall:call];
}
}
- (void)hidePad:(BOOL)hidden animated:(BOOL)animated {
if (hidden) {
[_numpadButton setOff];
} else {
[_numpadButton setOn];
}
if (hidden != _numpadView.hidden) {
if (animated) {
[self hideAnimation:hidden forView:_numpadView completion:nil];
} else {
[_numpadView setHidden:hidden];
}
}
}
- (void)hideRoutes:(BOOL)hidden animated:(BOOL)animated {
if (hidden) {
[_routesButton setOff];
} else {
[_routesButton setOn];
}
_routesBluetoothButton.selected = [CallManager.instance isBluetoothEnabled];
_routesSpeakerButton.selected = [CallManager.instance isSpeakerEnabled];
_routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected;
if (hidden != _routesView.hidden) {
[_routesView setHidden:hidden];
}
}
- (void)hideSpeaker:(BOOL)hidden {
_speakerButton.hidden = hidden;
_routesButton.hidden = !hidden;
}
#pragma mark - Event Functions
- (void)bluetoothAvailabilityUpdateEvent:(NSNotification *)notif {
bool available = [[notif.userInfo objectForKey:@"available"] intValue];
dispatch_async(dispatch_get_main_queue(), ^{
[self hideSpeaker:available];
});
}
- (void)callUpdateEvent:(NSNotification *)notif {
LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue];
LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue];
[self callUpdate:call state:state animated:TRUE];
}
- (void)callUpdate:(LinphoneCall *)call state:(LinphoneCallState)state animated:(BOOL)animated {
_declineButton_earlyMedia.hidden = linphone_call_get_state(call) != LinphoneCallStateOutgoingEarlyMedia;
_declineButton.hidden = !_declineButton_earlyMedia.hidden;
_numpadButton.hidden = _declineButton_earlyMedia.hidden;
}
#pragma mark - Animation
- (void)hideAnimation:(BOOL)hidden forView:(UIView *)target completion:(void (^)(BOOL finished))completion {
if (hidden) {
int original_y = target.frame.origin.y;
CGRect newFrame = target.frame;
newFrame.origin.y = self.view.frame.size.height;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
target.frame = newFrame;
}
completion:^(BOOL finished) {
CGRect originFrame = target.frame;
originFrame.origin.y = original_y;
target.hidden = YES;
target.frame = originFrame;
if (completion)
completion(finished);
}];
} else {
CGRect frame = target.frame;
int original_y = frame.origin.y;
frame.origin.y = self.view.frame.size.height;
target.frame = frame;
frame.origin.y = original_y;
target.hidden = NO;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
target.frame = frame;
}
completion:^(BOOL finished) {
target.frame = frame; // in case application did not finish
if (completion)
completion(finished);
}];
}
}
@end

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,11 +17,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
#import <UIKit/UIKit.h>
extension UIImageView {
func tint(_ color:UIColor) {
self.image = self.image?.withRenderingMode(.alwaysTemplate)
tintColor = color
}
}
@interface CallPausedTableView : UITableViewController
- (void)update;
@end

View file

@ -0,0 +1,104 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "CallPausedTableView.h"
#import "UICallPausedCell.h"
#import "LinphoneManager.h"
#import "Utils.h"
@implementation CallPausedTableView
#pragma mark - UI change
- (void)update {
[self.tableView reloadData];
CGRect newOrigin = self.tableView.frame;
newOrigin.size.height =
[self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] *
[self tableView:self.tableView numberOfRowsInSection:0];
newOrigin.origin.y += self.tableView.frame.size.height - newOrigin.size.height;
self.tableView.frame = newOrigin;
}
#pragma mark - UITableViewDataSource Functions
- (LinphoneCall *)conferenceCallForRow:(NSInteger)row {
const MSList *calls = linphone_core_get_calls(LC);
int i = -1;
while (calls) {
if (linphone_call_get_state(calls->data) == LinphoneCallPaused) {
i++;
if (i == row)
break;
}
calls = calls->next;
}
// we should reach this only when we are querying for conference
return (calls ? calls->data : NULL);
}
#pragma mark - UITableViewDataSource Functions
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *kCellId = NSStringFromClass(UICallPausedCell.class);
UICallPausedCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
if (cell == nil) {
cell = [[UICallPausedCell alloc] initWithIdentifier:kCellId];
}
[cell setCall:[self conferenceCallForRow:indexPath.row]];
cell.contentView.userInteractionEnabled = false;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
const MSList *calls = linphone_core_get_calls(LC);
int count = 0;
int conference_in_pause = 0;
int call_in_conference = 0;
while (calls) {
LinphoneCall *call = calls->data;
BOOL callInConference = linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
if (linphone_call_get_state(call) == LinphoneCallPaused) {
count++;
}
if(callInConference) {
call_in_conference++;
if (!linphone_core_is_in_conference(LC)) {
conference_in_pause = 1;
}
}
calls = calls->next;
}
if(call_in_conference == 1) {
conference_in_pause = 0;
}
return count + conference_in_pause;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1e-5;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 1e-5;
}
@end

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,13 +17,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import SnapKit
import UIKit
#import <UIKit/UIKit.h>
extension UIViewController {
func VIEW<T>( _ desc: UICompositeViewDescription) -> T{
return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T
}
}
#import "SideMenuTableView.h"
#import "PhoneMainView.h"
@interface CallSideMenuView : UIViewController
@property(weak, nonatomic) IBOutlet UILabel *statsLabel;
- (IBAction)onLateralSwipe:(id)sender;
@end

230
Classes/CallSideMenuView.m Normal file
View file

@ -0,0 +1,230 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "CallSideMenuView.h"
#import "LinphoneManager.h"
#import "PhoneMainView.h"
@implementation CallSideMenuView {
NSTimer *updateTimer;
}
#pragma mark - ViewController Functions
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (updateTimer != nil) {
[updateTimer invalidate];
}
updateTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(updateStats:)
userInfo:nil
repeats:YES];
[self updateStats:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (updateTimer != nil) {
[updateTimer invalidate];
updateTimer = nil;
}
}
- (IBAction)onLateralSwipe:(id)sender {
[PhoneMainView.instance.mainViewController hideSideMenu:YES];
}
+ (NSString *)iceToString:(LinphoneIceState)state {
switch (state) {
case LinphoneIceStateNotActivated:
return NSLocalizedString(@"Not activated", @"ICE has not been activated for this call");
break;
case LinphoneIceStateFailed:
return NSLocalizedString(@"Failed", @"ICE processing has failed");
break;
case LinphoneIceStateInProgress:
return NSLocalizedString(@"In progress", @"ICE process is in progress");
break;
case LinphoneIceStateHostConnection:
return NSLocalizedString(@"Direct connection",
@"ICE has established a direct connection to the remote host");
break;
case LinphoneIceStateReflexiveConnection:
return NSLocalizedString(
@"NAT(s) connection",
@"ICE has established a connection to the remote host through one or several NATs");
break;
case LinphoneIceStateRelayConnection:
return NSLocalizedString(@"Relay connection", @"ICE has established a connection through a relay");
break;
}
}
+ (NSString*)afinetToString:(int)remote_family {
return (remote_family == LinphoneAddressFamilyUnspec) ? @"Unspecified":(remote_family == LinphoneAddressFamilyInet) ? @"IPv4" : @"IPv6";
}
+ (NSString *)mediaEncryptionToString:(LinphoneMediaEncryption)enc {
switch (enc) {
case LinphoneMediaEncryptionDTLS:
return @"DTLS";
case LinphoneMediaEncryptionSRTP:
return @"SRTP";
case LinphoneMediaEncryptionZRTP:
return @"ZRTP";
case LinphoneMediaEncryptionNone:
break;
}
return NSLocalizedString(@"None", nil);
}
- (NSString *)updateStatsForCall:(LinphoneCall *)call stream:(LinphoneStreamType)stream {
NSMutableString *result = [[NSMutableString alloc] init];
const PayloadType *payload = NULL;
const LinphoneCallStats *stats;
const LinphoneCallParams *params = linphone_call_get_current_params(call);
NSString *name;
switch (stream) {
case LinphoneStreamTypeAudio:
name = @"Audio";
payload = linphone_call_params_get_used_audio_codec(params);
stats = linphone_call_get_audio_stats(call);
break;
case LinphoneStreamTypeText:
name = @"Text";
payload = linphone_call_params_get_used_text_codec(params);
stats = linphone_call_get_text_stats(call);
break;
case LinphoneStreamTypeVideo:
name = @"Video";
payload = linphone_call_params_get_used_video_codec(params);
stats = linphone_call_get_video_stats(call);
break;
case LinphoneStreamTypeUnknown:
break;
}
if (payload == NULL) {
return result;
}
[result appendString:@"\n"];
[result appendString:name];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Codec: %s/%iHz", payload->mime_type, payload->clock_rate]];
if (stream == LinphoneStreamTypeAudio) {
[result appendString:[NSString stringWithFormat:@"/%i channels", payload->channels]];
}
[result appendString:@"\n"];
// Encoder & decoder descriptions
const char *enc_desc = ms_factory_get_encoder(linphone_core_get_ms_factory(LC), payload->mime_type)->text;
const char *dec_desc = ms_factory_get_decoder(linphone_core_get_ms_factory(LC), payload->mime_type)->text;
if (strcmp(enc_desc, dec_desc) == 0) {
[result appendString:[NSString stringWithFormat:@"Encoder/Decoder: %s", enc_desc]];
[result appendString:@"\n"];
} else {
[result appendString:[NSString stringWithFormat:@"Encoder: %s", enc_desc]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Decoder: %s", dec_desc]];
[result appendString:@"\n"];
}
if (stats != NULL) {
[result appendString:[NSString stringWithFormat:@"Download bandwidth: %1.1f kbits/s",
linphone_call_stats_get_download_bandwidth(stats)]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Upload bandwidth: %1.1f kbits/s",
linphone_call_stats_get_upload_bandwidth(stats)]];
[result appendString:@"\n"];
if (stream == LinphoneStreamTypeVideo) {
/*[result appendString:[NSString stringWithFormat:@"Estimated download bandwidth: %1.1f kbits/s",
linphone_call_stats_get_estimated_download_bandwidth(stats)]];
[result appendString:@"\n"];*/
}
[result
appendString:[NSString stringWithFormat:@"ICE state: %@",
[self.class iceToString:linphone_call_stats_get_ice_state(stats)]]];
[result appendString:@"\n"];
[result
appendString:[NSString
stringWithFormat:@"Afinet: %@",
[self.class afinetToString:linphone_call_stats_get_ip_family_of_remote(
stats)]]];
[result appendString:@"\n"];
// RTP stats section (packet loss count, etc)
const rtp_stats_t rtp_stats = *linphone_call_stats_get_rtp_stats(stats);
[result
appendString:[NSString stringWithFormat:
@"RTP packets: %llu total, %lld cum loss, %llu discarded, %llu OOT, %llu bad",
rtp_stats.packet_recv, rtp_stats.cum_packet_loss, rtp_stats.discarded,
rtp_stats.outoftime, rtp_stats.bad]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Sender loss rate: %.2f%%",
linphone_call_stats_get_sender_loss_rate(stats)]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Receiver loss rate: %.2f%%",
linphone_call_stats_get_receiver_loss_rate(stats)]];
[result appendString:@"\n"];
if (stream == LinphoneStreamTypeVideo) {
const LinphoneVideoDefinition *recv_definition = linphone_call_params_get_received_video_definition(params);
const LinphoneVideoDefinition *sent_definition = linphone_call_params_get_sent_video_definition(params);
float sentFPS = linphone_call_params_get_sent_framerate(params);
float recvFPS = linphone_call_params_get_received_framerate(params);
[result appendString:[NSString stringWithFormat:@"Sent video resolution: %dx%d (%.1fFPS)", linphone_video_definition_get_width(sent_definition),
linphone_video_definition_get_height(sent_definition), sentFPS]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Received video resolution: %dx%d (%.1fFPS)",
linphone_video_definition_get_width(recv_definition),
linphone_video_definition_get_height(recv_definition), recvFPS]];
[result appendString:@"\n"];
}
}
return result;
}
- (void)updateStats:(NSTimer *)timer {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (!call) {
_statsLabel.text = NSLocalizedString(@"No call in progress", nil);
return;
}
NSMutableString *stats = [[NSMutableString alloc] init];
LinphoneMediaEncryption enc = linphone_call_params_get_media_encryption(linphone_call_get_current_params(call));
if (enc != LinphoneMediaEncryptionNone) {
[stats appendString:[NSString
stringWithFormat:@"Call encrypted using %@", [self.class mediaEncryptionToString:enc]]];
}
[stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeAudio]];
[stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeVideo]];
[stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeText]];
_statsLabel.text = stats;
}
@end

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="CallSideMenuView">
<connections>
<outlet property="statsLabel" destination="ZYY-EM-M2s" id="Syl-sZ-upy"/>
<outlet property="view" destination="YEG-7O-7jE" id="VGG-cE-thT"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="YEG-7O-7jE">
<rect key="frame" x="0.0" y="42" width="375" height="625"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" alpha="0.69999999999999973" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="100" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" id="ZYY-EM-M2s" userLabel="statsLabel">
<rect key="frame" x="0.0" y="0.0" width="300" height="625"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<string key="text">Audio: upr
Video: down</string>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<gestureRecognizers/>
<connections>
<outletCollection property="gestureRecognizers" destination="EB5-NY-DqU" appends="YES" id="Bz9-rW-UqV"/>
</connections>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="686.23188405797111" y="213.28125"/>
</view>
<tapGestureRecognizer id="EB5-NY-DqU">
<connections>
<action selector="onLateralSwipe:" destination="-1" id="Li9-LU-Om1"/>
</connections>
</tapGestureRecognizer>
</objects>
</document>

117
Classes/CallView.h Normal file
View file

@ -0,0 +1,117 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
#import "VideoZoomHandler.h"
#import "UICamSwitch.h"
#import "UICompositeView.h"
#import "CallPausedTableView.h"
#import "UIMutedMicroButton.h"
#import "UIPauseButton.h"
#import "UISpeakerButton.h"
#import "UIVideoButton.h"
#import "UIHangUpButton.h"
#import "UIDigitButton.h"
#import "UIRoundedImageView.h"
#import "UIBouncingView.h"
@class VideoView;
@interface CallView : TPMultiLayoutViewController <UIGestureRecognizerDelegate, UICompositeViewDelegate> {
@private
UITapGestureRecognizer *singleFingerTap;
NSTimer *hideControlsTimer;
NSTimer *videoDismissTimer;
BOOL videoHidden;
BOOL callRecording;
VideoZoomHandler *videoZoomHandler;
}
@property(nonatomic, strong) IBOutlet CallPausedTableView *pausedCallsTable;
@property(nonatomic, strong) IBOutlet UIView *videoGroup;
@property(nonatomic, strong) IBOutlet UIView *videoView;
@property(nonatomic, strong) IBOutlet UIView *videoPreview;
@property(nonatomic, strong) IBOutlet UICamSwitch *videoCameraSwitch;
@property(nonatomic, strong) IBOutlet UIActivityIndicatorView *videoWaitingForFirstImage;
@property(weak, nonatomic) IBOutlet UIView *callView;
@property(nonatomic, strong) IBOutlet UIPauseButton *callPauseButton;
@property(nonatomic, strong) IBOutlet UIButton *optionsConferenceButton;
@property(nonatomic, strong) IBOutlet UIVideoButton *videoButton;
@property(nonatomic, strong) IBOutlet UIMutedMicroButton *microButton;
@property(nonatomic, strong) IBOutlet UISpeakerButton *speakerButton;
@property(nonatomic, strong) IBOutlet UIToggleButton *routesButton;
@property(nonatomic, strong) IBOutlet UIToggleButton *optionsButton;
@property(nonatomic, strong) IBOutlet UIHangUpButton *hangupButton;
@property(nonatomic, strong) IBOutlet UIView *numpadView;
@property(nonatomic, strong) IBOutlet UIView *routesView;
@property(nonatomic, strong) IBOutlet UIView *optionsView;
@property(nonatomic, strong) IBOutlet UIButton *routesEarpieceButton;
@property(nonatomic, strong) IBOutlet UIButton *routesSpeakerButton;
@property(nonatomic, strong) IBOutlet UIButton *routesBluetoothButton;
@property(nonatomic, strong) IBOutlet UIButton *optionsAddButton;
@property(nonatomic, strong) IBOutlet UIButton *optionsTransferButton;
@property(nonatomic, strong) IBOutlet UIToggleButton *numpadButton;
@property(weak, nonatomic) IBOutlet UIPauseButton *conferencePauseButton;
@property(weak, nonatomic) IBOutlet UIBouncingView *chatNotificationView;
@property(weak, nonatomic) IBOutlet UILabel *chatNotificationLabel;
@property (weak, nonatomic) IBOutlet UIButton *recordButton;
@property (weak, nonatomic) IBOutlet UIButton *recordButtonOnView;
@property(weak, nonatomic) IBOutlet UIView *bottomBar;
@property(nonatomic, strong) IBOutlet UIDigitButton *oneButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *twoButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *threeButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *fourButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *fiveButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *sixButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *sevenButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *eightButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *nineButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *starButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *zeroButton;
@property(nonatomic, strong) IBOutlet UIDigitButton *hashButton;
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
@property(weak, nonatomic) IBOutlet UIView *pausedByRemoteView;
@property(weak, nonatomic) IBOutlet UIView *noActiveCallView;
@property(weak, nonatomic) IBOutlet UIView *conferenceView;
@property(strong, nonatomic) IBOutlet CallPausedTableView *conferenceCallsTable;
@property (weak, nonatomic) IBOutlet UIView *waitView;
@property (weak, nonatomic) IBOutlet UIView *infoView;
- (IBAction)onRoutesClick:(id)sender;
- (IBAction)onRoutesBluetoothClick:(id)sender;
- (IBAction)onRoutesEarpieceClick:(id)sender;
- (IBAction)onRoutesSpeakerClick:(id)sender;
- (IBAction)onOptionsClick:(id)sender;
- (IBAction)onOptionsTransferClick:(id)sender;
- (IBAction)onOptionsAddClick:(id)sender;
- (IBAction)onOptionsConferenceClick:(id)sender;
- (IBAction)onNumpadClick:(id)sender;
- (IBAction)onChatClick:(id)sender;
- (IBAction)onRecordClick:(id)sender;
- (IBAction)onRecordOnViewClick:(id)sender;
@end

1006
Classes/CallView.m Normal file

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,6 @@
#import "UICompositeView.h"
#import "UIRoundBorderedButton.h"
#import "UIChatBubbleTextCell.h"
@interface ChatConversationImdnView : UIViewController <UICompositeViewDelegate, UITableViewDelegate, UITableViewDataSource>
{

View file

@ -32,6 +32,7 @@
#import "UIImageViewDeletable.h"
#import "UIConfirmationDialog.h"
#import "UIInterfaceStyleButton.h"
#import "linphoneapp-Swift.h"
#import "UIChatReplyBubbleView.h"
#include "linphone/linphonecore.h"

View file

@ -28,8 +28,6 @@
#import "SVProgressHUD.h"
#import "EphemeralSettingsView.h"
#import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation FileContext
@ -1855,7 +1853,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:linphone_player_get_user_data(_sharedVoicePlayer)]};
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerLostFocus object:nil userInfo:userInfo];
}
[AudioRouteUtils routeAudioToSpeaker];
[CallManager.instance changeRouteToSpeaker];
linphone_player_set_user_data(_sharedVoicePlayer, (void *)path);
linphone_player_open(_sharedVoicePlayer, path);
linphone_player_start(_sharedVoicePlayer);

View file

@ -120,7 +120,6 @@
return nil;
}
- (NSString *)displayName {
if (_friend) {
const char *friend_name = linphone_friend_get_name(_friend);

View file

@ -20,8 +20,6 @@
#import "DevicesListView.h"
#import "PhoneMainView.h"
#import "UIDeviceCell.h"
#import "linphoneapp-Swift.h"
@implementation DevicesMenuEntry
- (id)init:(LinphoneParticipantDevice *)dev isMe:(BOOL)isMe isFirst:(BOOL)first isUnique:(BOOL)unique index:(NSInteger)idx{

View file

@ -21,7 +21,6 @@
#import "LinphoneManager.h"
#import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
@implementation DialerView
@ -398,7 +397,7 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (IBAction)onBackClick:(id)event {
[PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
[PhoneMainView.instance popToView:CallView.compositeViewDescription];
}
- (IBAction)onAddressChange:(id)sender {

View file

@ -301,83 +301,96 @@ static UICompositeViewDescription *compositeDescription = nil;
inView:(UIView *)ipadView
withDocumentMenuDelegate:(id<UIDocumentMenuDelegate>)documentMenuDelegate {
void (^block)(UIImagePickerControllerSourceType) = ^(UIImagePickerControllerSourceType type) {
ImagePickerView *view = VIEW(ImagePickerView);
view.sourceType = type;
// Displays a control that allows the user to choose picture or
// movie capture, if both are available:
view.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie,(NSString *)kUTTypeImage,nil];
// Hides the controls for moving & scaling pictures, or for
// trimming movies. To instead show the controls, use YES.
view.allowsEditing = NO;
view.imagePickerDelegate = delegate;
if (IPAD && ipadView && ipadPopoverView) {
UIView *iterview = ipadPopoverView;
CGRect ipadPopoverPosition = iterview.frame;
do {
ipadPopoverPosition =
[iterview.superview convertRect:ipadPopoverPosition toView:iterview.superview.superview];
iterview = iterview.superview;
} while (iterview && iterview.superview != ipadView);
[view.popoverController presentPopoverFromRect:ipadPopoverPosition
inView:ipadView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:FALSE];
} else {
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
ImagePickerView *view = VIEW(ImagePickerView);
view.sourceType = type;
// Displays a control that allows the user to choose picture or
// movie capture, if both are available:
view.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie,(NSString *)kUTTypeImage,nil];
// Hides the controls for moving & scaling pictures, or for
// trimming movies. To instead show the controls, use YES.
view.allowsEditing = NO;
view.imagePickerDelegate = delegate;
if (IPAD && ipadView && ipadPopoverView) {
UIView *iterview = ipadPopoverView;
CGRect ipadPopoverPosition = iterview.frame;
do {
ipadPopoverPosition =
[iterview.superview convertRect:ipadPopoverPosition toView:iterview.superview.superview];
iterview = iterview.superview;
} while (iterview && iterview.superview != ipadView);
[view.popoverController presentPopoverFromRect:ipadPopoverPosition
inView:ipadView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:FALSE];
} else {
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
};
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
block:^() {
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
return;
}
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized)
block(UIImagePickerControllerSourceTypeCamera);
else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block(UIImagePickerControllerSourceTypeCamera);
} else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] show];
}
});
}];
}
}];
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
block:^() {
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized)
block(UIImagePickerControllerSourceTypePhotoLibrary);
else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block(UIImagePickerControllerSourceTypePhotoLibrary);
} else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] show];
}
});
}];
}
}];
}
if (documentMenuDelegate) {
[sheet addButtonWithTitle:NSLocalizedString(@"Document",nil) block:^(){
[self pickDocumentForDelegate:documentMenuDelegate];
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
[sheet showInView:PhoneMainView.instance.view];
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
block:^() {
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
return;
}
block(UIImagePickerControllerSourceTypeCamera);
}];
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
block:^() {
block(UIImagePickerControllerSourceTypePhotoLibrary);
}];
}
if (documentMenuDelegate) {
[sheet addButtonWithTitle:NSLocalizedString(@"Document",nil) block:^(){
[self pickDocumentForDelegate:documentMenuDelegate];
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
[sheet showInView:PhoneMainView.instance.view];
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
block:^() {
if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
return;
}
block(UIImagePickerControllerSourceTypeCamera);
}];
}
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
[sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
block:^() {
block(UIImagePickerControllerSourceTypePhotoLibrary);
}];
}
if (documentMenuDelegate) {
[sheet addButtonWithTitle:NSLocalizedString(@"Document",nil) block:^(){
[self pickDocumentForDelegate:documentMenuDelegate];
}];
}
[sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
[sheet showInView:PhoneMainView.instance.view];
} else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
});
}];
}
}
+(void) pickDocumentForDelegate:(id<UIDocumentMenuDelegate>)documentMenuDelegate {

View file

@ -23,6 +23,7 @@
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>
#import <CoreLocation/CoreLocation.h>
#import "linphoneapp-Swift.h"
@interface LinphoneAppDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate, CLLocationManagerDelegate> {

View file

@ -140,7 +140,7 @@
if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) {
linphone_call_accept(call);
[PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
} else {
[PhoneMainView.instance displayIncomingCall:call];
}
@ -321,8 +321,6 @@
return NO;
}
VIEW(ActiveCallOrConferenceView); // to get created and all observers added
return YES;
}
@ -411,9 +409,16 @@
}
// used for callkit. Called when active video.
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]||[userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) { // tel URI handler.
if ([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
LOGI(@"CallKit: satrt video.");
CallView *view = VIEW(CallView);
[view.videoButton setOn];
}
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) { // tel URI handler.
INInteraction *interaction = userActivity.interaction;
INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent;
INPerson *contact = startAudioCallIntent.contacts[0];
@ -532,7 +537,8 @@
if ([response.actionIdentifier isEqual:@"Answer"]) {
// use the standard handler
[CallManager.instance acceptCallWithCall:call hasVideo:NO];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
linphone_call_accept(call);
} else if ([response.actionIdentifier isEqual:@"Decline"]) {
linphone_call_decline(call, LinphoneReasonDeclined);
} else if ([response.actionIdentifier isEqual:@"Reply"]) {
@ -569,6 +575,7 @@
return;
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
[CallManager.instance acceptVideoWithCall:call confirm:TRUE];
} else if ([response.actionIdentifier isEqual:@"Confirm"]) {
if (linphone_core_get_current_call(LC) == call)
@ -601,7 +608,7 @@
}
} else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) {
if (!call) return;
[PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
NSTimer *videoDismissTimer = nil;
UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body
cancelMessage:nil
@ -685,7 +692,8 @@
if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) {
// use the standard handler
[CallManager.instance acceptCallWithCall:call hasVideo:NO];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
linphone_call_accept(call);
} else if ([identifier isEqualToString:@"decline"]) {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call)
@ -722,7 +730,8 @@
if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) {
// use the standard handler
[CallManager.instance acceptCallWithCall:call hasVideo:NO];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
linphone_call_accept(call);
} else if ([identifier isEqualToString:@"decline"]) {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call)

View file

@ -25,8 +25,6 @@
#include "linphone/lpconfig.h"
#include <stdio.h>
#include <stdlib.h>
#import "linphoneapp-Swift.h"
@implementation LinphoneCoreSettingsStore

View file

@ -35,6 +35,7 @@
#include "bctoolbox/list.h"
#import "OrderedDictionary.h"
#import "linphoneapp-Swift.h"
extern NSString *const LINPHONERC_APPLICATION_KEY;
@ -48,6 +49,7 @@ extern NSString *const kLinphoneMainViewChange;
extern NSString *const kLinphoneAddressBookUpdate;
extern NSString *const kLinphoneLogsUpdate;
extern NSString *const kLinphoneSettingsUpdate;
extern NSString *const kLinphoneBluetoothAvailabilityUpdate;
extern NSString *const kLinphoneConfiguringStateUpdate;
extern NSString *const kLinphoneGlobalStateUpdate;
extern NSString *const kLinphoneNotifyReceived;
@ -202,6 +204,7 @@ typedef struct _LinphoneManagerSounds {
@property (readonly) sqlite3* database;
@property (readonly) LinphoneManagerSounds sounds;
@property (readonly) NSMutableArray *logs;
@property (nonatomic, assign) BOOL bluetoothAvailable;
@property (readonly) NSString* contactSipField;
@property (readonly,copy) NSString* contactFilter;
@property (copy) void (^silentPushCompletion)(UIBackgroundFetchResult);

View file

@ -31,6 +31,7 @@
#import "LinphoneCoreSettingsStore.h"
#import "LinphoneAppDelegate.h"
#import "LinphoneManager.h"
#import "Utils/AudioHelper.h"
#import "Utils/FileTransferDelegate.h"
#include "linphone/factory.h"
@ -45,7 +46,6 @@
#import "ChatsListView.h"
#import "ChatConversationView.h"
#import <UserNotifications/UserNotifications.h>
#import "linphoneapp-Swift.h"
#define LINPHONE_LOGS_MAX_ENTRY 5000
@ -233,7 +233,11 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
- (id)init {
if ((self = [super init])) {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
NSString *path = [[NSBundle mainBundle] pathForResource:@"msg" ofType:@"wav"];
self.messagePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:nil];
@ -1763,7 +1767,20 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
_configDb = linphone_config_new_for_shared_core(kLinphoneMsgNotificationAppGroupId.UTF8String, @"linphonerc".UTF8String, factory.UTF8String);
linphone_config_clean_entry(_configDb, "misc", "max_calls");
}
#pragma mark - Audio route Functions
- (void)audioRouteChangeListenerCallback:(NSNotification *)notif {
if (IPAD)
return;
_bluetoothAvailable = [CallManager.instance isBluetoothAvailable];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:_bluetoothAvailable], @"available", nil];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneBluetoothAvailabilityUpdate
object:self
userInfo:dict];
}
#pragma mark - Call Functions
- (void)send:(NSString *)replyText toChatRoom:(LinphoneChatRoom *)room {
@ -2145,6 +2162,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
if ([ct currentCalls] != nil) {
if (call) {
LOGI(@"Pausing SIP call because GSM call");
CallManager.instance.speakerBeforePause = [CallManager.instance isSpeakerEnabled];
linphone_call_pause(call);
[self startCallPausedLongRunningTask];
} else if (linphone_core_is_in_conference(theLinphoneCore)) {

View file

@ -1,10 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="StatusBarView">
@ -23,15 +21,15 @@
<rect key="frame" x="0.0" y="0.0" width="360" height="42"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="mcm-kl-NzJ" userLabel="backgroundView">
<rect key="frame" x="0.0" y="-69" width="360" height="111"/>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="color_A.png" id="mcm-kl-NzJ" userLabel="backgroundView">
<rect key="frame" x="0.0" y="0.0" width="360" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView>
<view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0Vp-VF-wmX" userLabel="incallView">
<view hidden="YES" contentMode="scaleToFill" id="0Vp-VF-wmX" userLabel="incallView">
<rect key="frame" x="0.0" y="0.0" width="360" height="42"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="27" userLabel="callSecurityButton">
<button opaque="NO" tag="6" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" adjustsImageWhenDisabled="NO" lineBreakMode="middleTruncation" id="27" userLabel="callSecurityButton">
<rect key="frame" x="332" y="0.0" width="24" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration">
@ -39,22 +37,22 @@
</accessibility>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" image="security_ok.png">
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleColor" red="0.19607843459999999" green="0.30980393290000002" blue="0.52156865600000002" alpha="1" colorSpace="calibratedRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="highlighted">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onSecurityClick:" destination="-1" eventType="touchUpInside" id="bdh-tU-zPP"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SKk-s0-5HE" userLabel="callQualityButton">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="SKk-s0-5HE" userLabel="callQualityButton">
<rect key="frame" x="0.0" y="0.0" width="42" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" image="call_quality_indicator_4.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onSideMenuClick:" destination="-1" eventType="touchUpInside" id="iOC-wy-MPP"/>
@ -62,42 +60,42 @@
</button>
</subviews>
</view>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="lfO-I4-PXi" userLabel="outcallView">
<view contentMode="scaleToFill" id="lfO-I4-PXi" userLabel="outcallView">
<rect key="frame" x="0.0" y="0.0" width="360" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yg7-rx-XVv" userLabel="sideMenuButton">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="yg7-rx-XVv" userLabel="sideMenuButton">
<rect key="frame" x="0.0" y="0.0" width="42" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Side menu button"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<state key="normal" image="menu.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onSideMenuClick:" destination="-1" eventType="touchUpInside" id="EeV-2U-i44"/>
</connections>
</button>
<button hidden="YES" opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="right" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3Jg-EU-ajT" userLabel="voicemailButton">
<button hidden="YES" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="3Jg-EU-ajT" userLabel="voicemailButton">
<rect key="frame" x="48" y="0.0" width="312" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="contentEdgeInsets" minX="0.0" minY="0.0" maxX="5" maxY="0.0"/>
<inset key="titleEdgeInsets" minX="6" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="1234" image="voicemail.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
</button>
</subviews>
</view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Mhg-P6-RfU" userLabel="registrationState" customClass="UIIconButton">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Mhg-P6-RfU" userLabel="registrationState" customClass="UIIconButton">
<rect key="frame" x="46" y="0.0" width="194" height="42"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Registration state"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<inset key="titleEdgeInsets" minX="6" minY="0.0" maxX="0.0" maxY="0.0"/>
<state key="normal" title="Registered" image="led_disconnected.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<connections>
<action selector="onRegistrationStateClick:" destination="-1" eventType="touchUpInside" id="erb-GT-Zef"/>
@ -106,15 +104,15 @@
</subviews>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="214.49275362318843" y="81.696428571428569"/>
<point key="canvasLocation" x="148" y="122"/>
</view>
</objects>
<resources>
<image name="call_quality_indicator_4.png" width="31.200000762939453" height="32"/>
<image name="call_quality_indicator_4.png" width="19" height="19"/>
<image name="color_A.png" width="2" height="2"/>
<image name="led_disconnected.png" width="19.200000762939453" height="19.200000762939453"/>
<image name="menu.png" width="30.399999618530273" height="29.600000381469727"/>
<image name="security_ok.png" width="22.399999618530273" height="28.799999237060547"/>
<image name="voicemail.png" width="41.599998474121094" height="19.200000762939453"/>
<image name="led_disconnected.png" width="11" height="11"/>
<image name="menu.png" width="19" height="18"/>
<image name="security_ok.png" width="13" height="18"/>
<image name="voicemail.png" width="25" height="12"/>
</resources>
</document>

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UICallConferenceCell">
<connections>
<outlet property="avatarImage" destination="PjC-yS-i03" id="srY-K7-Ajk"/>
<outlet property="durationLabel" destination="Jgc-Z9-ItD" id="EGs-SW-dRc"/>
<outlet property="kickButton" destination="nOf-6W-BeC" id="NR8-NM-f5V"/>
<outlet property="nameLabel" destination="63x-tV-T6U" id="rBP-rS-gbi"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="Sae-wc-2Qz">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="PjC-yS-i03" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="8" y="4" width="45" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" text="00:02:15" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="Jgc-Z9-ItD" userLabel="durationLabel">
<rect key="frame" x="61" y="33" width="253" height="27"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="0.98766469955444336" green="0.27512490749359131" blue="0.029739789664745331" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="63x-tV-T6U" userLabel="nameLabel">
<rect key="frame" x="61" y="0.0" width="176" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="24"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" tag="24" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nOf-6W-BeC" userLabel="kickButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="322" y="17" width="34" height="28"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Pause"/>
<state key="normal" image="conference_exit_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="selected" image="conference_exit_over.png"/>
<state key="highlighted" image="conference_exit_over.png"/>
<connections>
<action selector="onKickClick:" destination="-1" eventType="touchUpInside" id="AG0-aA-dG6"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="490.57971014492756" y="28.794642857142854"/>
</view>
</objects>
<resources>
<image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="conference_exit_default.png" width="55.200000762939453" height="46.400001525878906"/>
<image name="conference_exit_over.png" width="55.200000762939453" height="46.400001525878906"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9060" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UICallPausedCell">
<connections>
<outlet property="avatarImage" destination="NBJ-w0-Uvw" id="lGx-U6-4tn"/>
<outlet property="durationLabel" destination="tZi-KI-viq" id="TLb-yi-TBY"/>
<outlet property="nameLabel" destination="g3t-eS-m7B" id="dKD-7X-u2j"/>
<outlet property="pauseButton" destination="Izu-Zj-sHi" id="BEq-dn-lx2"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="Egc-Di-26M">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" alpha="0.5" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="color_A.png" id="S7U-QZ-e0e" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<animations/>
</imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" image="avatar.png" id="NBJ-w0-Uvw" userLabel="avatarImage" customClass="UIRoundedImageView">
<rect key="frame" x="8" y="3" width="45" height="45"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<animations/>
<accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/>
</accessibility>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="g3t-eS-m7B" userLabel="nameLabel">
<rect key="frame" x="61" y="6" width="176" height="37"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<animations/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" tag="24" contentMode="scaleToFill" selected="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Izu-Zj-sHi" userLabel="pauseButton" customClass="UIPauseButton">
<rect key="frame" x="322" y="3" width="45" height="45"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<animations/>
<accessibility key="accessibilityConfiguration" label="Pause"/>
<state key="normal" image="pause_small_default.png">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
</state>
<state key="disabled" image="pause_small_disabled.png"/>
<state key="selected" image="pause_small_over_selected.png"/>
<state key="highlighted" image="pause_small_over_selected.png"/>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="00:02:15" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" id="tZi-KI-viq" userLabel="durationLabel">
<rect key="frame" x="245" y="7" width="69" height="37"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<animations/>
<accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<animations/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="338.5" y="43"/>
</view>
</objects>
<resources>
<image name="avatar.png" width="255" height="255"/>
<image name="color_A.png" width="2" height="2"/>
<image name="pause_small_default.png" width="34" height="34"/>
<image name="pause_small_disabled.png" width="34" height="34"/>
<image name="pause_small_over_selected.png" width="34" height="34"/>
</resources>
</document>

View file

@ -20,7 +20,6 @@
#import "UIBackToCallButton.h"
#import "LinphoneManager.h"
#import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
@implementation UIBackToCallButton
@ -47,7 +46,7 @@
}
- (IBAction)onBackToCallClick:(id)sender {
[PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
[PhoneMainView.instance popToView:CallView.compositeViewDescription];
}
@end

View file

@ -18,8 +18,10 @@
*/
#import "UIBluetoothButton.h"
#import "../Utils/AudioHelper.h"
#import "Utils.h"
#import <AudioToolbox/AudioToolbox.h>
#include "linphone/linphonecore.h"
@implementation UIBluetoothButton

View file

@ -21,8 +21,6 @@
#import "LinphoneManager.h"
#import <CoreTelephony/CTCallCenter.h>
#import "linphoneapp-Swift.h"
@implementation UICallButton

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "UIRoundedImageView.h"
#import "LinphoneManager.h"
#import "UIInterfaceStyleButton.h"
#define CONFERENCE_CELL_HEIGHT 60
@interface UICallConferenceCell : UITableViewCell
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *kickButton;
@property(nonatomic, setter=setParticipant:) LinphoneParticipant *participant;
- (id)initWithIdentifier:(NSString *)identifier;
- (IBAction)onKickClick:(id)sender;
@end

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "UICallConferenceCell.h"
#import "Utils.h"
#import "PhoneMainView.h"
@implementation UICallConferenceCell
- (id)initWithIdentifier:(NSString *)identifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
if (self != nil) {
NSArray *arrayOfViews =
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
if ([arrayOfViews count] >= 1) {
// resize cell to match .nib size. It is needed when resized the cell to
// correctly adapt its height too
UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:0]);
[self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
[self addSubview:sub];
}
}
return self;
}
- (void)setParticipant:(LinphoneParticipant *)p {
_participant = p;
if (!p) {
return;
}
const LinphoneAddress *addr = linphone_participant_get_address(p);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
_durationLabel.text = [LinphoneUtils durationToString:[NSDate date].timeIntervalSince1970 - linphone_participant_get_creation_time(p)];
_kickButton.hidden = CallManager.instance.isInConferenceAsGuest;
}
- (IBAction)onKickClick:(id)sender {
if (!_participant) {
return;
}
if ([CallManager callKitEnabled]) {
LinphoneCall *call = [CallManager.instance getCallForParticipant:_participant];
if (call) {
[CallManager.instance setHeldWithCall:call hold:true];
}
}
linphone_conference_remove_participant_2([CallManager.instance getConference], _participant);
}
@end

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,17 +17,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
#import "UIRoundedImageView.h"
#import "LinphoneManager.h"
#import "UIPauseButton.h"
@interface UICallPausedCell : UITableViewCell
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
@property(weak, nonatomic) IBOutlet UIPauseButton *pauseButton;
extension Conference : CustomStringConvertible {
public var description: String {
if let username = conferenceAddress?.username {
return "<\(username)>"
}
return "<pointer:\(Unmanaged.passUnretained(self).toOpaque())>"
}
}
- (id)initWithIdentifier:(NSString *)identifier;
- (void)setCall:(LinphoneCall *)call;
@end

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "UICallPausedCell.h"
#import "Utils.h"
@implementation UICallPausedCell
- (id)initWithIdentifier:(NSString *)identifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
if (self != nil) {
NSArray *arrayOfViews =
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
if ([arrayOfViews count] >= 1) {
// resize cell to match .nib size. It is needed when resized the cell to
// correctly adapt its height too
UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:0]);
[self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
[self addSubview:sub];
}
}
return self;
}
- (void)setCall:(LinphoneCall *)call {
// if no call is provided, we assume that this is a conference
if (!call || linphone_call_get_conference(call)) {
[_pauseButton setType:UIPauseButtonType_Conference call:call];
_nameLabel.text = NSLocalizedString(@"Conference", nil);
[_avatarImage setImage:[UIImage imageNamed:@"options_start_conference_default.png"]
bordered:NO
withRoundedRadius:YES];
_durationLabel.text = @"";
} else {
[_pauseButton setType:UIPauseButtonType_Call call:call];
const LinphoneAddress *addr = linphone_call_get_remote_address(call);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
_durationLabel.text = [LinphoneUtils durationToString:linphone_call_get_duration(call)];
}
[_pauseButton update];
}
@end

View file

@ -25,8 +25,6 @@
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVKit/AVKit.h>
#import "linphoneapp-Swift.h"
#define voicePlayer VIEW(ChatConversationView).sharedVoicePlayer
#define chatView VIEW(ChatConversationView)

View file

@ -25,8 +25,6 @@
#import <AssetsLibrary/ALAsset.h>
#import <AssetsLibrary/ALAssetRepresentation.h>
#import <QuartzCore/QuartzCore.h>
#import "linphoneapp-Swift.h"
@implementation UIChatBubbleTextCell

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,11 +17,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
import Foundation
class SharedLayoutConstants {
static let buttons_bottom_margin = 15
static let margin_call_view_side_controls_buttons = 12
#import "UIIconButton.h"
@interface UIHangUpButton : UIIconButton {
}
- (void)update;
@end

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "UIHangUpButton.h"
#import "LinphoneManager.h"
#import "linphoneapp-Swift.h"
@implementation UIHangUpButton
#pragma mark - Static Functions
+ (bool)isInConference:(LinphoneCall *)call {
if (!call)
return false;
return linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
}
+ (int)callCount {
int count = 0;
const MSList *calls = linphone_core_get_calls(LC);
while (calls != 0) {
if (![UIHangUpButton isInConference:((LinphoneCall *)calls->data)]) {
count++;
}
calls = calls->next;
}
return count;
}
#pragma mark - Lifecycle Functions
- (void)initUIHangUpButton {
[self addTarget:self action:@selector(touchUp:) forControlEvents:UIControlEventTouchUpInside];
}
- (id)init {
self = [super init];
if (self) {
[self initUIHangUpButton];
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self initUIHangUpButton];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initUIHangUpButton];
}
return self;
}
#pragma mark -
- (void)update {
if (linphone_core_get_calls_nb(LC) == 1 || // One call
linphone_core_get_current_call(LC) != NULL || // In call
linphone_core_is_in_conference(LC) || // In conference
(linphone_core_get_conference_size(LC) > 0 && [UIHangUpButton callCount] == 0) // Only one conf
) {
[self setEnabled:true];
return;
}
[self setEnabled:false];
}
#pragma mark - Action Functions
- (void)touchUp:(id)sender {
LinphoneCall *currentcall = linphone_core_get_current_call(LC);
if (linphone_core_is_in_conference(LC) || // In conference
(linphone_core_get_conference_size(LC) > 0 && [UIHangUpButton callCount] == 0) // Only one conf
) {
LinphoneManager.instance.conf = TRUE;
linphone_core_terminate_conference(LC);
} else if (currentcall != NULL) {
[CallManager.instance terminateCallWithCall:currentcall];
} else {
const MSList *calls = linphone_core_get_calls(LC);
if (bctbx_list_size(calls) == 1) { // Only one call
[CallManager.instance terminateCallWithCall:(calls->data)];
}
}
}
@end

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,17 +17,22 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
#import "UIToggleButton.h"
#include "linphone/linphonecore.h"
typedef enum _UIPauseButtonType {
UIPauseButtonType_CurrentCall,
UIPauseButtonType_Call,
UIPauseButtonType_Conference
} UIPauseButtonType;
extension Participant : CustomStringConvertible {
public var description: String {
if let address = address?.asStringUriOnly() {
return "<Address: \(address)>"
}
return "<Raw pointer:\(Unmanaged.passUnretained(self).toOpaque())>"
}
@interface UIPauseButton : UIToggleButton<UIToggleButtonDelegate> {
@private
UIPauseButtonType type;
LinphoneCall* call;
}
- (void)setType:(UIPauseButtonType) type call:(LinphoneCall*)call;
@end

View file

@ -0,0 +1,180 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "UIPauseButton.h"
#import "LinphoneManager.h"
#import "Utils.h"
@implementation UIPauseButton
#pragma mark - Lifecycle Functions
- (void)initUIPauseButton {
type = UIPauseButtonType_CurrentCall;
}
- (id)init {
self = [super init];
if (self) {
[self initUIPauseButton];
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self initUIPauseButton];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initUIPauseButton];
}
return self;
}
#pragma mark - Static Functions
+ (bool)isInConference:(LinphoneCall *)call {
if (!call)
return false;
return linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
}
+ (LinphoneCall *)getCall {
LinphoneCall *currentCall = linphone_core_get_current_call(LC);
if (currentCall == nil && linphone_core_get_calls_nb(LC) == 1) {
currentCall = (LinphoneCall *)linphone_core_get_calls(LC)->data;
}
return currentCall;
}
#pragma mark -
- (void)setType:(UIPauseButtonType)atype call:(LinphoneCall *)acall {
type = atype;
call = acall;
}
#pragma mark - UIToggleButtonDelegate Functions
- (void)onOn {
switch (type) {
case UIPauseButtonType_Call: {
if (call != nil) {
if ([CallManager callKitEnabled]) {
[CallManager.instance setHeldWithCall:call hold:true];
} else {
CallManager.instance.speakerBeforePause = [CallManager.instance isSpeakerEnabled];
linphone_call_pause(call);
}
} else {
LOGW(@"Cannot toggle pause buttton, because no current call");
}
break;
}
case UIPauseButtonType_Conference: {
linphone_core_leave_conference(CallManager.instance.getConference);
// Fake event
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
break;
}
case UIPauseButtonType_CurrentCall: {
LinphoneCall *currentCall = [UIPauseButton getCall];
if (currentCall != nil) {
if ([CallManager callKitEnabled]) {
[CallManager.instance setHeldWithCall:currentCall hold:true];
} else {
CallManager.instance.speakerBeforePause = [CallManager.instance isSpeakerEnabled];
linphone_call_pause(currentCall);
}
} else {
LOGW(@"Cannot toggle pause buttton, because no current call");
}
break;
}
}
}
- (void)onOff {
switch (type) {
case UIPauseButtonType_Call: {
if (call != nil) {
if ([CallManager callKitEnabled]) {
[CallManager.instance setHeldWithCall:call hold:false];
} else {
linphone_call_resume(call);
}
} else {
LOGW(@"Cannot toggle pause buttton, because no current call");
}
break;
}
case UIPauseButtonType_Conference: {
linphone_core_enter_conference(CallManager.instance.getConference);
// Fake event
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
break;
}
case UIPauseButtonType_CurrentCall: {
LinphoneCall *currentCall = [UIPauseButton getCall];
if ([CallManager callKitEnabled]) {
[CallManager.instance setHeldWithCall:currentCall hold:false];
} else {
linphone_call_resume(currentCall);
}
break;
}
}
}
- (bool)onUpdate {
bool ret = false;
LinphoneCall *c = call;
switch (type) {
case UIPauseButtonType_Conference: {
self.enabled = CallManager.instance.getConference && (linphone_conference_get_participant_count(CallManager.instance.getConference)> 0);
if (self.enabled) {
ret = (!CallManager.instance.isInConference);
}
break;
}
case UIPauseButtonType_CurrentCall:
c = [UIPauseButton getCall];
case UIPauseButtonType_Call: {
if (c != nil) {
LinphoneCallState state = linphone_call_get_state(c);
ret = (state == LinphoneCallPaused || state == LinphoneCallPausing);
self.enabled = !linphone_core_sound_resources_locked(LC) &&
(state == LinphoneCallPaused || state == LinphoneCallPausing ||
state == LinphoneCallStreamsRunning);
} else {
self.enabled = FALSE;
}
break;
}
}
return ret;
}
@end

View file

@ -17,11 +17,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
import linphone
#import <UIKit/UIKit.h>
#import "UIToggleButton.h"
@interface UISpeakerButton : UIToggleButton<UIToggleButtonDelegate> {
extension linphonesw.PayloadType {
}
@end

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <AudioToolbox/AudioToolbox.h>
#import "UISpeakerButton.h"
#import "Utils.h"
#import "LinphoneManager.h"
#include "linphone/linphonecore.h"
@implementation UISpeakerButton
INIT_WITH_COMMON_CF {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
return self;
}
- (void)onOn {
[CallManager.instance changeRouteToSpeaker];
}
- (void)onOff {
[CallManager.instance changeRouteToDefault];
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark - UIToggleButtonDelegate Functions
- (void)audioRouteChangeListenerCallback:(NSNotification *)notif {
dispatch_async(dispatch_get_main_queue(), ^{
[self update];});
}
- (bool)onUpdate {
return [CallManager.instance isSpeakerEnabled];
}
@end

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import <UIKit/UIKit.h>
#import "UIToggleButton.h"
@interface UIVideoButton : UIToggleButton<UIToggleButtonDelegate> {
}
@property(nonatomic, strong) IBOutlet UIActivityIndicatorView *waitView;
@end

View file

@ -0,0 +1,114 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#import "UIVideoButton.h"
#include "LinphoneManager.h"
#import "Log.h"
@implementation UIVideoButton {
BOOL last_update_state;
}
@synthesize waitView;
INIT_WITH_COMMON_CF {
last_update_state = FALSE;
return self;
}
- (void)onOn {
if (!linphone_core_video_display_enabled(LC))
return;
[self setEnabled:FALSE];
[waitView startAnimating];
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
CallAppData *data = [CallManager getAppDataWithCall:call];
data.videoRequested = TRUE;/* will be used later to notify user if video was not activated because of the linphone core*/
[CallManager setAppDataWithCall:call appData:data];
LinphoneCallParams *call_params = linphone_core_create_call_params(LC,call);
linphone_call_params_enable_video(call_params, TRUE);
linphone_call_update(call, call_params);
linphone_call_params_unref(call_params);
} else if (self.inAudioConf) {
LinphoneConferenceParams *cp = linphone_core_create_conference_params(LC);
linphone_conference_params_set_video_enabled(cp, true);
linphone_conference_update_params(linphone_core_get_conference(LC), cp);
} else {
LOGW(@"Cannot toggle video button, because no current call");
}
}
- (void)onOff {
if (!linphone_core_video_display_enabled(LC))
return;
[CallManager.instance changeRouteToDefault];
//[CallManager.instance enableSpeakerWithEnable:FALSE];
[self setEnabled:FALSE];
[waitView startAnimating];
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
LinphoneCallParams *call_params = linphone_core_create_call_params(LC,call);
linphone_call_params_enable_video(call_params, FALSE);
linphone_core_update_call(LC, call, call_params);
linphone_call_params_unref(call_params);
} else if (self.inVideoConf) {
LinphoneConferenceParams *cp = linphone_core_create_conference_params(LC);
linphone_conference_params_set_video_enabled(cp, false);
linphone_conference_update_params(linphone_core_get_conference(LC), cp);
} else {
LOGW(@"Cannot toggle video button, because no current call or no video conference");
}
}
- (bool)onUpdate {
bool video_enabled = false;
LinphoneCall *currentCall = linphone_core_get_current_call(LC);
if (linphone_core_video_supported(LC)) {
if (self.inAudioConf || self.inVideoConf || (linphone_core_video_display_enabled(LC) && currentCall && !linphone_core_sound_resources_locked(LC) &&
linphone_call_get_state(currentCall) == LinphoneCallStreamsRunning)){
video_enabled = TRUE;
}
}
[self setEnabled:video_enabled];
if (last_update_state != video_enabled)
[waitView stopAnimating];
if (video_enabled) {
video_enabled = self.inVideoConf || (currentCall && linphone_call_params_video_enabled(linphone_call_get_current_params(currentCall)));
}
last_update_state = video_enabled;
return video_enabled;
}
-(BOOL) inVideoConf {
return (linphone_core_is_in_conference(LC) && linphone_core_get_conference(LC) != nil && linphone_conference_params_is_video_enabled(linphone_conference_get_current_params(linphone_core_get_conference(LC))));
}
-(BOOL) inAudioConf {
return (linphone_core_is_in_conference(LC) && linphone_core_get_conference(LC) != nil && !linphone_conference_params_is_video_enabled(linphone_conference_get_current_params(linphone_core_get_conference(LC))));
}
@end

Binary file not shown.

Binary file not shown.

View file

@ -32,11 +32,6 @@
+ (void)log:(OrtpLogLevel)severity file:(const char *)file line:(int)line format:(NSString *)format, ...;
+ (void)enableLogs:(OrtpLogLevel)level;
+ (void)directLog:(OrtpLogLevel)level text:(NSString *)text;
+ (void)d:(NSString *)text;
+ (void)i:(NSString *)text;
+ (void)w:(NSString *)text;
+ (void)e:(NSString *)text;
+ (void)f:(NSString *)text;
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args);
@end

View file

@ -27,6 +27,10 @@
#import "AboutView.h"
#import "AssistantLinkView.h"
#import "AssistantView.h"
#import "CallIncomingView.h"
#import "CallOutgoingView.h"
#import "CallSideMenuView.h"
#import "CallView.h"
#import "ChatConversationCreateView.h"
#import "ChatConversationInfoView.h"
#import "ChatConversationImdnView.h"
@ -74,7 +78,7 @@
@end
@interface PhoneMainView : UIViewController<MFMessageComposeViewControllerDelegate> {
@interface PhoneMainView : UIViewController<IncomingCallViewDelegate, MFMessageComposeViewControllerDelegate> {
@private
NSMutableArray *inhibitedEvents;
}
@ -92,7 +96,6 @@
- (void)changeCurrentView:(UICompositeViewDescription *)view;
- (UIViewController*)popCurrentView;
- (UIViewController *)popView:(UICompositeViewDescription *)view;
- (UIViewController *)popToView:(UICompositeViewDescription *)currentView;
- (void) setPreviousViewName:(NSString*)previous;
- (NSString*) getPreviousViewName;

View file

@ -22,8 +22,6 @@
#import "LinphoneAppDelegate.h"
#import "Log.h"
#import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
static RootViewManager *rootViewManagerInstance = nil;
@ -375,9 +373,7 @@ static RootViewManager *rootViewManagerInstance = nil;
break;
}
case LinphoneCallOutgoingInit: {
OutgoingCallView *v = VIEW(OutgoingCallView);
[self changeCurrentView:OutgoingCallView.compositeViewDescription];
[v setCallWithCall:call];
[self changeCurrentView:CallOutgoingView.compositeViewDescription];
break;
}
case LinphoneCallPausedByRemote:
@ -385,12 +381,39 @@ static RootViewManager *rootViewManagerInstance = nil;
if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/
[LinphoneManager.instance setupGSMInteraction];
[[UIDevice currentDevice] setProximityMonitoringEnabled:!([CallManager.instance isSpeakerEnabled] || [CallManager.instance isBluetoothEnabled])];
}
break;
}
case LinphoneCallStreamsRunning: {
[self changeCurrentView:CallView.compositeViewDescription];
break;
}
case LinphoneCallUpdatedByRemote: {
const LinphoneCallParams *current = linphone_call_get_current_params(call);
const LinphoneCallParams *remote = linphone_call_get_remote_params(call);
if (linphone_call_params_video_enabled(current) && !linphone_call_params_video_enabled(remote)) {
[self changeCurrentView:CallView.compositeViewDescription];
}
break;
}
case LinphoneCallError: {
[self displayCallError:call message:message];
}
case LinphoneCallEnd: {
const MSList *calls = linphone_core_get_calls(LC);
if (!calls || calls->data == call) {
while ((currentView == CallView.compositeViewDescription) ||
(currentView == CallIncomingView.compositeViewDescription) ||
(currentView == CallOutgoingView.compositeViewDescription)) {
[self popCurrentView];
}
} else {
[self changeCurrentView:CallView.compositeViewDescription];
}
break;
}
case LinphoneCallEarlyUpdatedByRemote:
case LinphoneCallEarlyUpdating:
case LinphoneCallIdle:
@ -610,15 +633,6 @@ static RootViewManager *rootViewManagerInstance = nil;
return [mainViewController getCurrentViewController];
}
- (UIViewController *)popView:(UICompositeViewDescription *)view {
NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
while (viewStack.count > 0 && [[viewStack lastObject] equal:view]) {
[viewStack removeLastObject];
}
return [self popToView:viewStack.lastObject ?: DialerView.compositeViewDescription];
}
- (void)changeCurrentView:(UICompositeViewDescription *)view {
[self _changeCurrentView:view transition:nil animated:ANIMATED];
}
@ -750,10 +764,10 @@ static RootViewManager *rootViewManagerInstance = nil;
[CallManager.instance acceptCallWithCall:call hasVideo:YES];
} else {
AudioServicesPlaySystemSound(lm.sounds.vibrate);
IncomingCallView *view = VIEW(IncomingCallView);
CallIncomingView *view = VIEW(CallIncomingView);
[self changeCurrentView:view.compositeViewDescription];
[view setCallWithCall:call];
//CDFIX [view setDelegate:self];
[view setCall:call];
[view setDelegate:self];
}
}
}

View file

@ -99,7 +99,7 @@ class ProviderDelegate: NSObject {
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
if CallManager.instance().endCallkit {
let call = CallManager.instance().core?.getCallByCallid(callId: callId!)
let call = CallManager.instance().lc?.getCallByCallid(callId: callId!)
if (call?.state == .PushIncomingReceived) {
try? call?.terminate()
}
@ -191,7 +191,7 @@ extension ProviderDelegate: CXProviderDelegate {
CallManager.instance().backgroundContextCameraIsEnabled = call!.params?.videoEnabled ?? false
call?.cameraEnabled = false // Disable camera while app is not on foreground
}
CallManager.instance().core?.configureAudioSession()
CallManager.instance().lc?.configureAudioSession()
CallManager.instance().acceptCall(call: call!, hasVideo: call!.params?.videoEnabled ?? false)
action.fulfill()
}
@ -206,8 +206,8 @@ extension ProviderDelegate: CXProviderDelegate {
}
do {
if (CallManager.instance().core?.isInConference ?? false && action.isOnHold) {
try CallManager.instance().core?.leaveConference()
if (CallManager.instance().lc?.isInConference ?? false && action.isOnHold) {
try CallManager.instance().lc?.leaveConference()
Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: call-id: [\(String(describing: callId))] leaving conference")
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
return
@ -219,10 +219,11 @@ extension ProviderDelegate: CXProviderDelegate {
if (call!.params?.localConferenceMode ?? false) {
return
}
CallManager.instance().speakerBeforePause = CallManager.instance().isSpeakerEnabled()
try call!.pause()
} else {
if (CallManager.instance().core?.conference != nil && CallManager.instance().core?.callsNb ?? 0 > 1) {
try CallManager.instance().core?.enterConference()
if (CallManager.instance().lc?.conference != nil && CallManager.instance().lc?.callsNb ?? 0 > 1) {
try CallManager.instance().lc?.enterConference()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
} else {
try call!.resume()
@ -251,7 +252,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fail()
}
CallManager.instance().core?.configureAudioSession()
CallManager.instance().lc?.configureAudioSession()
try CallManager.instance().doCall(addr: addr!, isSas: callInfo?.sasEnabled ?? false)
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call started failed because \(error)")
@ -262,7 +263,7 @@ extension ProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).")
CallManager.instance().addAllToLocalConference()
CallManager.instance().addAllToConference()
action.fulfill()
}
@ -270,7 +271,7 @@ extension ProviderDelegate: CXProviderDelegate {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call muted with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
CallManager.instance().core!.micEnabled = !CallManager.instance().core!.micEnabled
CallManager.instance().lc!.micEnabled = !CallManager.instance().lc!.micEnabled
action.fulfill()
}
@ -303,12 +304,12 @@ extension ProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session activated.")
CallManager.instance().core?.activateAudioSession(actived: true)
CallManager.instance().lc?.activateAudioSession(actived: true)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session deactivated.")
CallManager.instance().core?.activateAudioSession(actived: false)
CallManager.instance().lc?.activateAudioSession(actived: false)
}
}

View file

@ -1,39 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
extension UIApplication {
class func getTopMostViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
} else {
return nil
}
}
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
extension UIColor {
public convenience init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt64()
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
}

View file

@ -1,57 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
import AVFoundation
extension UIDevice {
static func ipad() -> Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
static func vibrate() {
if (!ipad()) {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
}
}
static func is5SorSEGen1() -> Bool {
return UIScreen.main.nativeBounds.height == 1136
}
static func hasNotch() -> Bool {
if (UserDefaults.standard.bool(forKey: "hasNotch")) {
return true
}
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top, topPadding > 24 else {
return false
}
UserDefaults.standard.setValue(true, forKey: "hasNotch")
return true
}
static func notchHeight() -> CGFloat {
guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top else {
return 0
}
return topPadding
}
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
extension UIImage {
func tinted(with color: UIColor?) -> UIImage? {
if (color == nil) {
return self
}
defer { UIGraphicsEndImageContext() }
UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
color!.set()
self.withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: self.size))
return UIGraphicsGetImageFromCurrentImageContext()
}
func withInsets(insets: UIEdgeInsets) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: self.size.width + insets.left + insets.right,
height: self.size.height + insets.top + insets.bottom), false, self.scale)
let _ = UIGraphicsGetCurrentContext()
let origin = CGPoint(x: insets.left, y: insets.top)
self.draw(at: origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
func withPadding(padding: CGFloat) -> UIImage? {
let insets = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
return withInsets(insets: insets)
}
}

View file

@ -1,326 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import SnapKit
import UIKit
extension UIView {
// Few constraints wrapper to abstract SnapKit functions
func removeConstraints() -> UIView {
snp.removeConstraints()
return self
}
func square(_ size:Int) -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(size)
make.height.equalTo(size)
}
return self
}
func size(w:CGFloat,h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(w)
make.height.equalTo(h)
}
return self
}
func height(_ h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.height.equalTo(h)
}
return self
}
func height(_ h:Int) -> UIView {
return height(CGFloat(h))
}
func width(_ h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(h)
}
return self
}
func width(_ h:Int) -> UIView {
return width(CGFloat(h))
}
func maxHeight(_ h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.height.lessThanOrEqualTo(h)
}
return self
}
func minWidth(_ h:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.width.greaterThanOrEqualTo(h)
}
return self
}
func matchParentSideBorders(insetedByDx:CGFloat = 0) -> UIView {
snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(insetedByDx)
make.right.equalToSuperview().offset(-insetedByDx)
}
return self
}
func matchParentDimmensions() -> UIView {
snp.makeConstraints { (make) in
make.left.right.top.bottom.equalToSuperview()
}
return self
}
func matchDimentionsOf(view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.left.right.top.bottom.equalTo(view)
}
return self
}
func matchParentHeight() -> UIView {
snp.makeConstraints { (make) in
make.top.bottom.equalToSuperview()
}
return self
}
func matchParentHeightDividedBy(_ divider : CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.height.equalToSuperview().dividedBy(divider)
}
return self
}
func matchParentWidthDividedBy(_ divider : CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.width.equalToSuperview().dividedBy(divider)
}
return self
}
func center() -> UIView {
snp.makeConstraints { (make) in
make.center.equalToSuperview()
}
return self
}
func alignParentTop(withMargin:CGFloat = 0.0) -> UIView {
snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(withMargin)
}
return self
}
func alignParentTop(withMargin:Int ) -> UIView {
return alignParentTop(withMargin:CGFloat(withMargin))
}
func alignUnder(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
snp.makeConstraints { (make) in
make.top.equalTo(view.snp.bottom).offset(withMargin)
}
return self
}
func alignUnder(view:UIView, withMargin:Int) -> UIView {
return alignUnder(view: view,withMargin:CGFloat(withMargin))
}
func matchRightOf(view:UIView, withMargin:CGFloat = 0) -> UIView {
snp.makeConstraints { (make) in
make.right.equalTo(view).offset(withMargin)
}
return self
}
func updateAlignUnder(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
snp.updateConstraints { (make) in
make.top.equalTo(view.snp.bottom).offset(withMargin)
}
return self
}
func alignParentBottom(withMargin:CGFloat = 0.0) -> UIView {
snp.makeConstraints { (make) in
make.bottom.equalToSuperview().offset(-withMargin)
}
return self
}
func alignParentBottom(withMargin:Int) -> UIView {
return alignParentBottom(withMargin:CGFloat(withMargin))
}
func alignAbove(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
snp.makeConstraints { (make) in
make.bottom.equalTo(view.snp.top).offset(-withMargin)
}
return self
}
func alignAbove(view:UIView, withMargin:Int) -> UIView {
return alignAbove(view: view,withMargin:CGFloat(withMargin))
}
func alignBottomWith(otherView:UIView) -> UIView {
snp.makeConstraints { (make) in
make.bottom.equalTo(otherView)
}
return self
}
func marginLeft(_ m:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(m)
}
return self
}
func alignParentLeft(withMargin:CGFloat = 0.0) -> UIView {
snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(withMargin)
}
return self
}
func alignParentLeft(withMargin:Int) -> UIView {
return alignParentLeft(withMargin:CGFloat(withMargin))
}
func alignParentRight(withMargin:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.right.equalToSuperview().offset(-withMargin)
}
return self
}
func alignParentRight(withMargin:CGFloat) -> UIView {
return alignParentRight(withMargin:Int(withMargin))
}
func toRightOf(_ view:UIView, withLeftMargin:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.left.equalTo(view.snp.right).offset(withLeftMargin)
}
return self
}
func toRightOf(_ view:UIView, withLeftMargin:CGFloat) -> UIView {
return toRightOf(view,withLeftMargin: Int(withLeftMargin))
}
func toLeftOf(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.right.equalTo(view.snp.left)
}
return self
}
func centerX(withDx:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.centerX.equalToSuperview().offset(withDx)
}
return self
}
func centerY(withDy:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.centerY.equalToSuperview().offset(withDy)
}
return self
}
func matchCenterXOf(view:UIView, withDx:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.centerX.equalTo(view).offset(withDx)
}
return self
}
func matchCenterYOf(view:UIView, withDy:Int = 0) -> UIView {
snp.makeConstraints { (make) in
make.centerY.equalTo(view).offset(withDy)
}
return self
}
func wrapContentY() -> UIView {
subviews.first?.snp.makeConstraints({ make in
make.top.equalToSuperview()
})
subviews.last?.snp.makeConstraints({ make in
make.bottom.equalToSuperview()
})
return self
}
func wrapContentX() -> UIView {
subviews.first?.snp.makeConstraints({ make in
make.left.equalToSuperview()
})
subviews.last?.snp.makeConstraints({ make in
make.right.equalToSuperview()
})
return self
}
func done() {
// to avoid the unused variable warning
}
// Onclick
class TapGestureRecognizer: UITapGestureRecognizer {
var action : (()->Void)? = nil
}
func onClick(action : @escaping ()->Void ){
let tap = TapGestureRecognizer(target: self , action: #selector(self.handleTap(_:)))
tap.action = action
tap.numberOfTapsRequired = 1
tap.cancelsTouchesInView = false
self.addGestureRecognizer(tap)
self.isUserInteractionEnabled = true
}
@objc func handleTap(_ sender: TapGestureRecognizer) {
sender.action!()
}
func VIEW<T>( _ desc: UICompositeViewDescription) -> T{
return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T
}
}

View file

@ -1,55 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
extension Address {
func initials() -> String? {
var initials = initials(displayName: addressBookEnhancedDisplayName())
if (initials == nil || initials!.isEmpty) {
initials = String(username.prefix(1))
}
return initials
}
private func initials(displayName: String?) -> String? { // Basic ImproveMe
return displayName?.components(separatedBy: " ")
.reduce("") {
($0.isEmpty ? "" : "\($0.first?.uppercased() ?? "")") +
($1.isEmpty ? "" : "\($1.first?.uppercased() ?? "")")
}
}
func addressBookEnhancedDisplayName() -> String? {
if let contact = FastAddressBook.getContactWith(getCobject) {
return contact.displayName
} else if (!displayName.isEmpty) {
return displayName
} else {
return username
}
}
func contact() -> Contact? {
return FastAddressBook.getContactWith(getCobject)
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
extension Call {
func answerVideoUpdateRequest(accept:Bool) {
guard let params = try?core? .createCallParams(call: self) else {
Log.i("[Call] \(self) unable to answerVideoUpdateRequest : could not create params ")
return
}
if (accept) {
params.videoEnabled = true
core?.videoCaptureEnabled = true
core?.videoDisplayEnabled = true
} else {
params.videoEnabled = false
}
try?acceptUpdate(params: params)
}
}
extension Call : CustomStringConvertible {
public var description: String {
if let callId = callLog?.callId {
return "<Call-ID: \(callId)>"
}
return "<Raw pointer:\(Unmanaged.passUnretained(self).toOpaque())>"
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
extension Core {
static func get() -> Core {
return CallManager.instance().core!
}
func showSwitchCameraButton() -> Bool {
return videoDevicesList.count > 2 // Count StaticImage camera
}
func toggleCamera() {
Log.i("[Core] Current camera device is \(videoDevice)")
videoDevicesList.forEach {
if ($0 != videoDevice && $0 != "StaticImage: Static picture") {
Log.i("[Core] New camera device will be \($0)")
try?setVideodevice(newValue: $0)
return
}
}
let inConference = conference != nil && conference!.isIn
if !inConference, let call = currentCall {
try?call.update(params: nil)
}
}
}

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
extension IceState {
func toString()->String {
switch (self) {
case .NotActivated: return NSLocalizedString("Not activated", tableName:"ICE has not been activated for this call",comment : "")
case .Failed: return NSLocalizedString("Failed", tableName:"ICE processing has failed",comment :"")
case .InProgress: return NSLocalizedString("In progress", tableName:"ICE process is in progress",comment :"")
case .HostConnection: return NSLocalizedString("Direct connection", tableName:"ICE has established a direct connection to the remote host",comment :"")
case .ReflexiveConnection: return NSLocalizedString( "NAT(s) connection", tableName:"ICE has established a connection to the remote host through one or several NATs",comment :"")
case .RelayConnection: return NSLocalizedString("Relay connection", tableName:"ICE has established a connection through a relay",comment :"")
}
}
}

View file

@ -1,138 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class MutableLiveDataOnChangeClosure<Type>: NSObject {
let value: (Type?) -> Void
let onlyOnce: Bool
init(_ function: @escaping (Type?) -> Void, onlyOnce:Bool = false) {
value = function
self.onlyOnce = onlyOnce
}
}
class MutableLiveData<T> {
private var _value : T? = nil
private var observers = [MutableLiveDataOnChangeClosure<T>] ()
private var _opposite : MutableLiveData<Bool>? = nil
init(_ initial:T) {
self.value = initial
}
init() {
}
var value : T? {
get {
return self._value
}
set {
self._value = newValue
self.notifyAllObservers(with: newValue)
}
}
func addObserver(observer: MutableLiveDataOnChangeClosure<T>, andNotifyOnce: Bool = false) {
observers.append(observer)
if (andNotifyOnce) {
notifyValue()
}
}
func removeObserver(observer: MutableLiveDataOnChangeClosure<T>) {
observers = observers.filter({$0 !== observer})
}
func clearObservers() {
observers.forEach {
removeObserver(observer: $0)
}
}
func notifyAllObservers(with newValue: T?) {
for observer in observers {
observer.value(newValue)
if (observer.onlyOnce) {
removeObserver(observer: observer)
}
}
}
func notifyValue() {
for observer in observers {
observer.value(value)
if (observer.onlyOnce) {
removeObserver(observer: observer)
}
}
}
func observe(onChange : @escaping (T?)->Void) {
let observer = MutableLiveDataOnChangeClosure<T>({ value in
onChange(value)
}, onlyOnce: false)
addObserver(observer: observer)
}
func readCurrentAndObserve(onChange : @escaping (T?)->Void) {
let observer = MutableLiveDataOnChangeClosure<T>({ value in
onChange(value)
}, onlyOnce: false)
addObserver(observer: observer)
observer.value(value)
}
func observeAsUniqueObserver (onChange : @escaping (T?)->Void, unique: Bool = false) {
let observer = MutableLiveDataOnChangeClosure<T>({ value in
onChange(value)
}, onlyOnce: false)
if (unique) {
clearObservers()
}
addObserver(observer: observer)
}
func observeOnce(onChange : @escaping (T?)->Void) {
let observer = MutableLiveDataOnChangeClosure<T>({ value in
onChange(value)
}, onlyOnce: true)
addObserver(observer: observer)
}
func opposite() -> MutableLiveData<Bool>? {
if (_opposite != nil) {
return _opposite
}
_opposite = MutableLiveData<Bool>(!(value! as! Bool))
observe { (value) in
self._opposite!.value = !(value! as! Bool)
}
return _opposite
}
}

View file

@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,17 +17,20 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AudioHelper_h
#define AudioHelper_h
import Foundation
import UIKit
#import <Foundation/Foundation.h>
@import AVFoundation;
struct ButtonTheme {
var tintableStateIcons: [UInt: TintableIcon] // State indexed
var backgroundStateColors: [UInt: LightDarkColor] // State indexed
}
@interface AudioHelper : NSObject
struct TintableIcon {
var name:String
var tintColor: LightDarkColor? = nil
}
+ (NSArray *)bluetoothRoutes;
+ (AVAudioSessionPortDescription *)bluetoothAudioDevice;
+ (AVAudioSessionPortDescription *)builtinAudioDevice;
+ (AVAudioSessionPortDescription *)speakerAudioDevice;
+ (AVAudioSessionPortDescription *)audioDeviceFromTypes:(NSArray *)types;
@end
#endif /* AudioHelper_h */

View file

@ -21,8 +21,6 @@
#import "LinphoneManager.h"
#import "PhoneMainView.h"
#import "Utils.h"
#import "linphoneapp-Swift.h"
@interface FileTransferDelegate ()
@property(strong) NSMutableData *data;

View file

@ -67,24 +67,6 @@
bctbx_log(BCTBX_LOG_DOMAIN, level, "%s", [text cStringUsingEncoding:NSUTF8StringEncoding]);
}
+(void)d:(NSString *)text {
[Log directLog:BCTBX_LOG_DEBUG text:text];
}
+(void)i:(NSString *)text {
[Log directLog:BCTBX_LOG_MESSAGE text:text];
}
+(void)w:(NSString *)text {
[Log directLog:BCTBX_LOG_WARNING text:text];
}
+(void)e:(NSString *)text {
[Log directLog:BCTBX_LOG_ERROR text:text];
}
+(void)f:(NSString *)text {
[Log directLog:BCTBX_LOG_FATAL text:text];
}
#pragma mark - Logs Functions callbacks
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args) {

View file

@ -1,191 +0,0 @@
/*
* Copyright (c) 2010-2021 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import AVFoundation
import linphonesw
@objc class AudioRouteUtils : NSObject {
static let core = Core.get()
static private func applyAudioRouteChange( call: Call?, types: [AudioDeviceType], output: Bool = true) {
let typesNames = types.map { String(describing: $0) }.joined(separator: "/")
let currentCall = core.callsNb > 0 ? (call != nil) ? call : core.currentCall != nil ? core.currentCall : core.calls[0] : nil
if (currentCall == nil) {
Log.w("[Audio Route Helper] No call found, setting audio route on Core")
}
let conference = core.conference
let capability = output ? AudioDeviceCapabilities.CapabilityPlay : AudioDeviceCapabilities.CapabilityRecord
var found = false
core.audioDevices.forEach { (audioDevice) in
Log.i("[Audio Route Helper] cdes [\(audioDevice.deviceName)] [\(audioDevice.type)] [\(audioDevice.capabilities)] ")
}
core.audioDevices.forEach { (audioDevice) in
if (!found && types.contains(audioDevice.type) && audioDevice.hasCapability(capability: capability)) {
if (conference != nil && conference?.isIn == true) {
Log.i("[Audio Route Helper] Found [\(audioDevice.type)] \(output ? "playback" : "recorder") audio device [\(audioDevice.deviceName)], routing conference audio to it")
if (output) {
conference?.outputAudioDevice = audioDevice
} else {
conference?.inputAudioDevice = audioDevice
}
} else if (currentCall != nil) {
Log.i("[Audio Route Helper] Found [\(audioDevice.type)] \(output ? "playback" : "recorder") audio device [\(audioDevice.deviceName)], routing call audio to it")
if (output) {
currentCall?.outputAudioDevice = audioDevice
}
else {
currentCall?.inputAudioDevice = audioDevice
}
} else {
Log.i("[Audio Route Helper] Found [\(audioDevice.type)] \(output ? "playback" : "recorder") audio device [\(audioDevice.deviceName)], changing core default audio device")
if (output) {
core.outputAudioDevice = audioDevice
} else {
core.inputAudioDevice = audioDevice
}
}
found = true
}
}
if (!found) {
Log.e("[Audio Route Helper] Couldn't find \(typesNames) audio device")
}
}
static private func changeCaptureDeviceToMatchAudioRoute(call: Call?, types: [AudioDeviceType]) {
switch (types.first) {
case .Bluetooth :if (isBluetoothAudioRecorderAvailable()) {
Log.i("[Audio Route Helper] Bluetooth device is able to record audio, also change input audio device")
applyAudioRouteChange(call: call, types: [AudioDeviceType.Bluetooth], output: false)
}
case .Headset, .Headphones : if (isHeadsetAudioRecorderAvailable()) {
Log.i("[Audio Route Helper] Headphones/headset device is able to record audio, also change input audio device")
applyAudioRouteChange(call:call,types: [AudioDeviceType.Headphones, AudioDeviceType.Headset], output:false)
}
default: break
}
}
static private func routeAudioTo( call: Call?, types: [AudioDeviceType]) {
let currentCall = call != nil ? call : core.currentCall != nil ? core.currentCall : core.calls[0]
if (call != nil || currentCall != nil) {
let callToUse = call != nil ? call : currentCall
applyAudioRouteChange(call: callToUse, types: types)
changeCaptureDeviceToMatchAudioRoute(call: callToUse, types: types)
} else {
applyAudioRouteChange(call: call, types: types)
changeCaptureDeviceToMatchAudioRoute(call: call, types: types)
}
}
static func routeAudioToEarpiece(call: Call? = nil) {
routeAudioTo(call: call, types: [AudioDeviceType.Microphone]) // on iOS Earpiece = Microphone
}
static func routeAudioToSpeaker(call: Call? = nil) {
routeAudioTo(call: call, types: [AudioDeviceType.Speaker])
}
@objc static func routeAudioToSpeaker() {
routeAudioTo(call: nil, types: [AudioDeviceType.Speaker])
}
static func routeAudioToBluetooth(call: Call? = nil) {
routeAudioTo(call: call, types: [AudioDeviceType.Bluetooth])
}
static func routeAudioToHeadset(call: Call? = nil) {
routeAudioTo(call: call, types: [AudioDeviceType.Headphones, AudioDeviceType.Headset])
}
static func isSpeakerAudioRouteCurrentlyUsed(call: Call? = nil) -> Bool {
let currentCall = core.callsNb > 0 ? (call != nil) ? call : core.currentCall != nil ? core.currentCall : core.calls[0] : nil
if (currentCall == nil) {
Log.w("[Audio Route Helper] No call found, setting audio route on Core")
}
let conference = core.conference
let audioDevice = conference != nil && conference?.isIn == true ? conference!.outputAudioDevice : currentCall != nil ? currentCall!.outputAudioDevice : core.outputAudioDevice
Log.i("[Audio Route Helper] Playback audio currently in use is [\(audioDevice?.deviceName ?? "n/a")] with type (\(audioDevice?.type ?? .Unknown)")
return audioDevice?.type == AudioDeviceType.Speaker
}
static func isBluetoothAudioRouteCurrentlyUsed(call: Call? = nil) -> Bool {
if (core.callsNb == 0) {
Log.w("[Audio Route Helper] No call found, so bluetooth audio route isn't used")
return false
}
let currentCall = call != nil ? call : core.currentCall != nil ? core.currentCall : core.calls[0]
let conference = core.conference
let audioDevice = conference != nil && conference?.isIn == true ? conference!.outputAudioDevice : currentCall?.outputAudioDevice
Log.i("[Audio Route Helper] Playback audio device currently in use is [\(audioDevice?.deviceName ?? "n/a")] with type (\(audioDevice?.type ?? .Unknown)")
return audioDevice?.type == AudioDeviceType.Bluetooth
}
static func isBluetoothAudioRouteAvailable() -> Bool {
if let device = core.audioDevices.first(where: { $0.type == AudioDeviceType.Bluetooth && $0.hasCapability(capability: .CapabilityPlay) }) {
Log.i("[Audio Route Helper] Found bluetooth audio device [\(device.deviceName)]")
return true
}
return false
}
static private func isBluetoothAudioRecorderAvailable() -> Bool {
if let device = core.audioDevices.first(where: { $0.type == AudioDeviceType.Bluetooth && $0.hasCapability(capability: .CapabilityRecord) }) {
Log.i("[Audio Route Helper] Found bluetooth audio recorder [\(device.deviceName)]")
return true
}
return false
}
static func isHeadsetAudioRouteAvailable() -> Bool {
if let device = core.audioDevices.first(where: { ($0.type == AudioDeviceType.Headset||$0.type == AudioDeviceType.Headphones) && $0.hasCapability(capability: .CapabilityPlay) }) {
Log.i("[Audio Route Helper] Found headset/headphones audio device [\(device.deviceName)]")
return true
}
return false
}
static private func isHeadsetAudioRecorderAvailable() -> Bool {
if let device = core.audioDevices.first(where: { ($0.type == AudioDeviceType.Headset||$0.type == AudioDeviceType.Headphones) && $0.hasCapability(capability: .CapabilityRecord) }) {
Log.i("[Audio Route Helper] Found headset/headphones audio recorder [\(device.deviceName)]")
return true
}
return false
}
static func isReceiverEnabled() -> Bool {
if let outputDevice = core.outputAudioDevice {
return outputDevice.type == AudioDeviceType.Microphone
}
return false
}
}

View file

@ -1,181 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
import Foundation
class CallData {
var call : Call
let address :String?
let isPaused = MutableLiveData<Bool>()
let isRemotelyPaused = MutableLiveData<Bool>()
let canBePaused = MutableLiveData<Bool>()
let isRecording = MutableLiveData<Bool>()
let isRemotelyRecorded = MutableLiveData<Bool>()
let isInRemoteConference = MutableLiveData<Bool>()
let remoteConferenceSubject = MutableLiveData<String>()
let isOutgoing = MutableLiveData<Bool>()
let isIncoming = MutableLiveData<Bool>()
let callState = MutableLiveData<Call.State>()
let iFrameReceived = MutableLiveData(false)
let outgoingEarlyMedia = MutableLiveData<Bool>()
let enteredDTMF = MutableLiveData(" ")
var chatRoom: ChatRoom? = nil
private var callDelegate : CallDelegateStub?
init (call:Call) {
self.call = call
address = call.remoteAddress?.asStringUriOnly()
callDelegate = CallDelegateStub(
onStateChanged : { (call: linphonesw.Call, state: linphonesw.Call.State, message: String) -> Void in
self.update()
},
onNextVideoFrameDecoded : { (call: linphonesw.Call) -> Void in
self.iFrameReceived.value = true
},
onRemoteRecording: { (call: linphonesw.Call, recording:Bool) -> Void in
self.isRemotelyRecorded.value = true
}
)
call.addDelegate(delegate: callDelegate!)
update()
initChatRoom()
}
private func isCallPaused() -> Bool {
return [Call.State.Paused, Call.State.Pausing].contains(call.state)
}
private func isCallRemotelyPaused() -> Bool {
return [Call.State.PausedByRemote].contains(call.state)
}
private func isOutGoing() -> Bool {
return [Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging].contains(call.state)
}
private func isInComing() -> Bool {
return [Call.State.IncomingReceived, Call.State.IncomingEarlyMedia].contains(call.state)
}
private func canCallBePaused() -> Bool {
return !call.mediaInProgress() && [Call.State.StreamsRunning, Call.State.PausedByRemote].contains(call.state)
}
private func update() {
isPaused.value = isCallPaused()
isRemotelyPaused.value = isCallRemotelyPaused()
canBePaused.value = canCallBePaused()
let conference = call.conference
isInRemoteConference.value = conference != nil
if (conference != nil) {
remoteConferenceSubject.value = conference?.subject != nil && (conference?.subject.count)! > 0 ? conference!.subject : VoipTexts.conference_default_title
}
isOutgoing.value = isOutGoing()
isIncoming.value = isInComing()
if (call.mediaInProgress()) {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
self.update()
}
}
outgoingEarlyMedia.value = callState.value == .OutgoingEarlyMedia
isRecording.value = call.params?.isRecording
callState.value = call.state
}
private func initChatRoom() {
let localSipUri = Core.get().defaultAccount?.params?.identityAddress?.asStringUriOnly()
let remoteSipUri = call.remoteAddress?.asStringUriOnly()
guard
let localSipUri = Core.get().defaultAccount?.params?.identityAddress?.asStringUriOnly(),
let remoteSipUri = call.remoteAddress?.asStringUriOnly(),
let localAddress = try?Factory.Instance.createAddress(addr: localSipUri),
let remoteSipAddress = try?Factory.Instance.createAddress(addr: remoteSipUri)
else {
Log.e("[Call] Failed to get either local \(localSipUri.logable) or remote \(remoteSipUri.logable) SIP address!")
return
}
do {
chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: [])
if (chatRoom == nil) {
chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress])
}
if (chatRoom == nil) {
Log.w("[Call] Failed to find existing chat room for local address [$localSipUri] and remote address [$remoteSipUri]")
let chatRoomParams = try Core.get().createDefaultChatRoomParams()
// TODO: configure chat room params
chatRoom = try Core.get().createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress])
}
if (chatRoom == nil) {
Log.e("[Call] Failed to create a chat room for local address \(localSipUri) and remote address \(remoteSipUri)!")
}
} catch {
Log.e("[Call] Exception caught initiating a chat room for local address \(localSipUri) and remote address \(remoteSipUri) Error : \(error)!")
}
}
func sendDTMF(dtmf:String) {
enteredDTMF.value = enteredDTMF.value! + dtmf
Core.get().playDtmf(dtmf: dtmf.utf8CString[0], durationMs: 1000)
try?call.sendDtmf(dtmf: dtmf.utf8CString[0])
}
func destroy() {
call.removeDelegate(delegate: callDelegate!)
isPaused.clearObservers()
isRemotelyPaused.clearObservers()
canBePaused.clearObservers()
isRecording.clearObservers()
isRemotelyRecorded.clearObservers()
isInRemoteConference.clearObservers()
remoteConferenceSubject.clearObservers()
isOutgoing.clearObservers()
isIncoming.clearObservers()
callState.clearObservers()
iFrameReceived.clearObservers()
outgoingEarlyMedia.clearObservers()
enteredDTMF.clearObservers()
}
func toggleRecord() {
if (call.params?.isRecording == true) {
call.stopRecording()
} else {
call.startRecording()
}
isRecording.value = call.params?.isRecording
}
func togglePause() {
if (isCallPaused()) {
try?call.resume()
} else {
try?call.pause()
}
isPaused.value = isCallPaused()
}
}

View file

@ -1,171 +0,0 @@
/*
* Copyright (c) 2010-2021 Belledonne Communications SARL.
*
* This file is part of linphone-android
* (see https://www.linphone.org).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
class CallStatisticsData {
var call : Call
var audioStats:[StatItemData] = []
var videoStats:[StatItemData] = []
let isVideoEnabled = MutableLiveData<Bool>()
let statsUpdated = MutableLiveData(true)
private var callDelegate : CallDelegateStub?
init (call:Call) {
self.call = call
callDelegate = CallDelegateStub(
onStatsUpdated : { (call: Call, stats: CallStats) -> Void in
self.isVideoEnabled.value = call.currentParams?.videoEnabled
self.updateCallStats(stats: stats)
self.statsUpdated.value = true
}
)
call.addDelegate(delegate: callDelegate!)
initCallStats()
isVideoEnabled.value = call.currentParams?.videoEnabled
call.audioStats.map { updateCallStats(stats: $0) }
call.videoStats.map { updateCallStats(stats: $0) }
}
private func initCallStats() {
audioStats.append(StatItemData(type: StatType.CAPTURE))
audioStats.append(StatItemData(type: StatType.PLAYBACK))
audioStats.append(StatItemData(type: StatType.PAYLOAD))
audioStats.append(StatItemData(type: StatType.ENCODER))
audioStats.append(StatItemData(type: StatType.DECODER))
audioStats.append(StatItemData(type: StatType.DOWNLOAD_BW))
audioStats.append(StatItemData(type: StatType.UPLOAD_BW))
audioStats.append(StatItemData(type: StatType.ICE))
audioStats.append(StatItemData(type: StatType.IP_FAM))
audioStats.append(StatItemData(type: StatType.SENDER_LOSS))
audioStats.append(StatItemData(type: StatType.RECEIVER_LOSS))
audioStats.append(StatItemData(type: StatType.JITTER))
videoStats.append(StatItemData(type: StatType.CAPTURE))
videoStats.append(StatItemData(type: StatType.PLAYBACK))
videoStats.append(StatItemData(type: StatType.PAYLOAD))
videoStats.append(StatItemData(type: StatType.ENCODER))
videoStats.append(StatItemData(type: StatType.DECODER))
videoStats.append(StatItemData(type: StatType.DOWNLOAD_BW))
videoStats.append(StatItemData(type: StatType.UPLOAD_BW))
videoStats.append(StatItemData(type: StatType.ESTIMATED_AVAILABLE_DOWNLOAD_BW))
videoStats.append(StatItemData(type: StatType.ICE))
videoStats.append(StatItemData(type: StatType.IP_FAM))
videoStats.append(StatItemData(type: StatType.SENDER_LOSS))
videoStats.append(StatItemData(type: StatType.RECEIVER_LOSS))
videoStats.append(StatItemData(type: StatType.SENT_RESOLUTION))
videoStats.append(StatItemData(type: StatType.RECEIVED_RESOLUTION))
videoStats.append(StatItemData(type: StatType.SENT_FPS))
videoStats.append(StatItemData(type: StatType.RECEIVED_FPS))
}
private func updateCallStats(stats: CallStats) {
if (stats.type == StreamType.Audio) {
audioStats.forEach{ $0.update(call: call, stats: stats)}
} else if (stats.type == StreamType.Video) {
videoStats.forEach{ $0.update(call: call, stats: stats)}
}
}
}
enum StatType {
case CAPTURE
case PLAYBACK
case PAYLOAD
case ENCODER
case DECODER
case DOWNLOAD_BW
case UPLOAD_BW
case ICE
case IP_FAM
case SENDER_LOSS
case RECEIVER_LOSS
case JITTER
case SENT_RESOLUTION
case RECEIVED_RESOLUTION
case SENT_FPS
case RECEIVED_FPS
case ESTIMATED_AVAILABLE_DOWNLOAD_BW
}
struct StatItemData {
var type:StatType
let value = MutableLiveData<String>()
func update(call: Call, stats: CallStats) {
guard let payloadType = stats.type == StreamType.Audio ? call.currentParams?.usedAudioPayloadType : call.currentParams?.usedVideoPayloadType, let core = call.core else {
value.value = "n/a"
return
}
switch(type) {
case StatType.CAPTURE: value.value = stats.type == StreamType.Audio ? core.captureDevice : core.videoDevice
case StatType.PLAYBACK: value.value = stats.type == StreamType.Audio ? core.playbackDevice : core.videoDisplayFilter
case StatType.PAYLOAD: value.value = "\(payloadType.mimeType)/\(payloadType.clockRate / 1000) kHz"
case StatType.ENCODER: value.value = payloadType.description
case StatType.DECODER: value.value = payloadType.description
case StatType.DOWNLOAD_BW: value.value = "\(stats.downloadBandwidth) kbits/s"
case StatType.UPLOAD_BW: value.value = "\(stats.uploadBandwidth) kbits/s"
case StatType.ICE: value.value = stats.iceState.toString()
case StatType.IP_FAM: value.value = stats.ipFamilyOfRemote == AddressFamily.Inet6 ? "IPv6" : "IPv4"
case StatType.SENDER_LOSS: value.value = String(format: "%.2f%",stats.senderLossRate)
case StatType.RECEIVER_LOSS: value.value = String(format: "%.2f%",stats.receiverLossRate)
case StatType.JITTER: value.value = String(format: "%.2f ms",stats.jitterBufferSizeMs)
case StatType.SENT_RESOLUTION: value.value = call.currentParams?.sentVideoDefinition?.name
case StatType.RECEIVED_RESOLUTION: value.value = call.currentParams?.receivedVideoDefinition?.name
case StatType.SENT_FPS: value.value = "\(call.currentParams?.sentFramerate ?? 0.0)"
case StatType.RECEIVED_FPS: value.value = "\(call.currentParams?.receivedFramerate ?? 0.0)"
case StatType.ESTIMATED_AVAILABLE_DOWNLOAD_BW: value.value = "\(stats.estimatedDownloadBandwidth) kbit/s"
}
}
func getTypeTitle() -> String {
switch (type) {
case .CAPTURE: return VoipTexts.call_stats_capture_filter
case .PLAYBACK: return VoipTexts.call_stats_player_filter
case .PAYLOAD: return VoipTexts.call_stats_codec
case .ENCODER: return VoipTexts.call_stats_encoder_name
case .DECODER: return VoipTexts.call_stats_decoder_name
case .DOWNLOAD_BW: return VoipTexts.call_stats_download
case .UPLOAD_BW: return VoipTexts.call_stats_upload
case .ICE: return VoipTexts.call_stats_ice
case .IP_FAM: return VoipTexts.call_stats_ip
case .SENDER_LOSS: return VoipTexts.call_stats_sender_loss_rate
case .RECEIVER_LOSS: return VoipTexts.call_stats_receiver_loss_rate
case .JITTER: return VoipTexts.call_stats_jitter_buffer
case .SENT_RESOLUTION: return VoipTexts.call_stats_video_resolution_sent
case .RECEIVED_RESOLUTION: return VoipTexts.call_stats_video_resolution_received
case .SENT_FPS: return VoipTexts.call_stats_video_fps_sent
case .RECEIVED_FPS: return VoipTexts.call_stats_video_fps_received
case .ESTIMATED_AVAILABLE_DOWNLOAD_BW: return VoipTexts.call_stats_estimated_download
}
}
}

View file

@ -1,189 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
import AVFoundation
class CallsViewModel {
let currentCallData = MutableLiveData<CallData?>(nil)
let callsData = MutableLiveData<[CallData]>([])
let inactiveCallsCount = MutableLiveData(0)
let currentCallUnreadChatMessageCount = MutableLiveData(0)
let chatAndCallsCount = MutableLiveData(0)
let callConnectedEvent = MutableLiveData<Call>()
let callUpdateEvent = MutableLiveData<Call>()
let noMoreCallEvent = MutableLiveData(false)
let core = Core.get()
static let shared = CallsViewModel()
private var coreDelegate : CoreDelegateStub?
init () {
coreDelegate = CoreDelegateStub(
onCallStateChanged : { (core: Core, call: Call, state: Call.State, message:String) -> Void in
Log.i("[Calls] Call state changed: \(call) : \(state)")
let currentCall = core.currentCall
if (currentCall != nil && self.currentCallData.value??.call.getCobject != currentCall?.getCobject) {
self.updateCurrentCallData(currentCall: currentCall)
} else if (currentCall == nil && core.callsNb > 1) {
self.updateCurrentCallData(currentCall: currentCall)
}
if ([.End,.Released,.Error].contains(state)) {
self.removeCallFromList(call: call)
} else if ([.OutgoingInit].contains(state)) {
self.addCallToList(call:call)
} else if ([.IncomingReceived].contains(state)) {
self.addCallToList(call:call)
} else if (state == .UpdatedByRemote) {
let remoteVideo = call.remoteParams?.videoEnabled == true
let localVideo = call.currentParams?.videoEnabled == true
let autoAccept = call.core?.videoActivationPolicy?.automaticallyAccept == true
if (remoteVideo && !localVideo && !autoAccept) {
if (core.videoCaptureEnabled || core.videoDisplayEnabled) {
try?call.deferUpdate()
self.callUpdateEvent.value = call
} else {
call.answerVideoUpdateRequest(accept: false)
}
}
}else if (state == Call.State.Connected) {
self.callConnectedEvent.value = call
} else if (state == Call.State.StreamsRunning) {
self.callUpdateEvent.value = call
}
self.updateInactiveCallsCount()
self.callsData.notifyValue()
},
onMessageReceived : { (core: Core, room: ChatRoom, message: ChatMessage) -> Void in
self.updateUnreadChatCount()
},
onChatRoomRead : { (core: Core, room: ChatRoom) -> Void in
self.updateUnreadChatCount()
},
onLastCallEnded: { (core: Core) -> Void in
self.currentCallData.value??.destroy()
self.currentCallData.value = nil
self.noMoreCallEvent.value = true
}
)
Core.get().addDelegate(delegate: coreDelegate!)
if let currentCall = core.currentCall {
currentCallData.value??.destroy()
currentCallData.value = CallData(call:currentCall)
}
chatAndCallsCount.value = 0
inactiveCallsCount.readCurrentAndObserve { (_) in
self.updateCallsAndChatCount()
}
currentCallUnreadChatMessageCount.readCurrentAndObserve { (_) in
self.updateCallsAndChatCount()
}
initCallList()
updateInactiveCallsCount()
updateUnreadChatCount()
}
private func initCallList() {
core.calls.forEach { addCallToList(call: $0) }
}
private func removeCallFromList(call: Call) {
Log.i("[Calls] Removing call \(call) from calls list")
if let removeCandidate = callsData.value?.filter{$0.call.getCobject == call.getCobject}.first {
removeCandidate.destroy()
}
callsData.value = callsData.value?.filter(){$0.call.getCobject != call.getCobject}
callsData.notifyValue()
}
private func addCallToList(call: Call) {
Log.i("[Calls] Adding call \(call) to calls list")
callsData.value?.append(CallData(call: call))
callsData.notifyValue()
}
private func updateUnreadChatCount() {
guard let unread = currentCallData.value??.chatRoom?.unreadMessagesCount else {
currentCallUnreadChatMessageCount.value = 0
return
}
currentCallUnreadChatMessageCount.value = unread
}
private func updateInactiveCallsCount() {
inactiveCallsCount.value = core.callsNb - 1
}
private func updateCallsAndChatCount() {
var value = 0
if let calls = inactiveCallsCount.value {
value += calls
}
if let chats = currentCallUnreadChatMessageCount.value {
value += chats
}
chatAndCallsCount.value = value
}
func mergeCallsIntoLocalConference() {
CallManager.instance().startLocalConference()
}
private func updateCurrentCallData(currentCall: Call?) {
var callToUse = currentCall
if (currentCall == nil) {
Log.w("[Calls] Current call is now null")
let firstCall = core.calls.first
if (firstCall != nil && currentCallData.value??.call.getCobject != firstCall?.getCobject) {
Log.i("[Calls] Using first call as \"current\" call")
callToUse = firstCall
}
}
guard let callToUse = callToUse else {
Log.w("[Calls] No call found to be used as \"current\"")
return
}
let firstToUse = callsData.value?.filter{$0.call.getCobject != callToUse.getCobject}.first
if (firstToUse != nil) {
currentCallData.value = firstToUse
} else {
Log.w("[Calls] Call not found in calls data list, shouldn't happen!")
currentCallData.value = CallData(call: callToUse)
}
updateUnreadChatCount()
}
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
import Foundation
class ConferenceParticipantData {
var conference:Conference
var participant:Participant
let isAdmin = MutableLiveData<Bool>()
let isMeAdmin = MutableLiveData<Bool>()
private var callDelegate : CallDelegateStub?
init (conference:Conference, participant:Participant) {
self.conference = conference
self.participant = participant
isAdmin.value = participant.isAdmin
isMeAdmin.value = conference.me?.isAdmin
Log.i("[Conference Participant] Participant \(sipUri!) is admin=\(isAdmin.value!)")
}
var sipUri:String? {
get {
return self.participant.address?.asString()
}
}
func destroy() {
isAdmin.clearObservers()
isMeAdmin.clearObservers()
}
}

View file

@ -1,90 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import linphonesw
import Foundation
class ConferenceParticipantDeviceData {
var participantDevice:ParticipantDevice
let isMe:Bool
let videoEnabled = MutableLiveData<Bool>()
let activeSpeaker = MutableLiveData<Bool>()
let isInConference = MutableLiveData<Bool>()
let core = Core.get()
private var participantDeviceDelegate : ParticipantDeviceDelegate?
init (participantDevice:ParticipantDevice, isMe:Bool) {
self.isMe = isMe
self.participantDevice = participantDevice
participantDeviceDelegate = ParticipantDeviceDelegateStub(
onIsSpeakingChanged: { (participantDevice, isSpeaking) in
Log.i("[Conference Participant Device] Participant \(participantDevice ) isspeaking = \(isSpeaking)")
self.activeSpeaker.value = isSpeaking
}, onConferenceJoined: { (participantDevice) in
Log.i("[Conference Participant Device] Participant \(participantDevice) has joined the conference")
self.isInConference.value = true
}, onConferenceLeft: { (participantDevice) in
Log.i("[Conference Participant Device] Participant \(participantDevice) has left the conference")
self.isInConference.value = false
}, onAudioDirectionChanged: { (participantDevice, direction) in
Log.i("[Conference Participant Device] Participant \(participantDevice) audio stream direction changed: \(direction)")
}, onVideoDirectionChanged: { (participantDevice, direction) in
Log.i("[Conference Participant Device] Participant \(participantDevice) video stream direction changed: \(direction)")
self.videoEnabled.value = direction == MediaDirection.SendOnly || direction == MediaDirection.SendRecv
}, onTextDirectionChanged: { (participantDevice, direction) in
Log.i("[Conference Participant Device] Participant \(participantDevice) text stream direction changed: \(direction)")
})
participantDevice.addDelegate(delegate: participantDeviceDelegate!)
activeSpeaker.value = false
// TODO: What happens if we have disabled video locally?
videoEnabled.value = participantDevice.videoDirection == MediaDirection.SendOnly || participantDevice.videoDirection == MediaDirection.SendRecv
isInConference.value = participantDevice.isInConference
}
func destroy() {
participantDevice.removeDelegate(delegate: participantDeviceDelegate!)
}
func switchCamera() {
Core.get().toggleCamera()
}
func isSwitchCameraAvailable() -> Bool {
return isMe && Core.get().showSwitchCameraButton()
}
func setVideoView(view:UIView) {
if (!isMe && participantDevice.videoDirection != MediaDirection.SendRecv) {
Log.e("[Conference Participant Device] Participant \(participantDevice) device video direction is \(participantDevice.videoDirection), don't set video window!")
return
}
Log.i("[Conference Participant Device] Setting textureView \(view) for participant \(participantDevice)")
if (isMe) { // TODO: remove
core.nativePreviewWindow = view
} else {
participantDevice.nativeVideoWindowId = UnsafeMutableRawPointer(Unmanaged.passRetained(view).toOpaque())
}
}
}

View file

@ -1,292 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
import AVFoundation
class ConferenceViewModel {
let core = Core.get()
static let shared = ConferenceViewModel()
let isConferencePaused = MutableLiveData<Bool>()
let canResumeConference = MutableLiveData<Bool>()
let isMeConferenceFocus = MutableLiveData<Bool>()
let isMeAdmin = MutableLiveData<Bool>()
let conferenceAddress = MutableLiveData<Address>()
let conferenceParticipants = MutableLiveData<[ConferenceParticipantData]>()
let conferenceParticipantDevices = MutableLiveData<[ConferenceParticipantDeviceData]>()
let conferenceDisplayMode = MutableLiveData<ConferenceLayout>()
let isInConference = MutableLiveData<Bool>()
let isVideoConference = MutableLiveData<Bool>()
let isRecording = MutableLiveData<Bool>()
let isRemotelyRecorded = MutableLiveData<Bool>()
let subject = MutableLiveData<String>()
let conference = MutableLiveData<Conference>()
let maxParticipantsForMosaicLayout = ConfigManager.instance().lpConfigIntForKey(key: "max_conf_part_mosaic_layout",defaultValue: 6)
private var conferenceDelegate : ConferenceDelegateStub?
private var coreDelegate : CoreDelegateStub?
init () {
conferenceDelegate = ConferenceDelegateStub(onParticipantAdded: { (conference: Conference, participant: Participant)in
if (conference.isMe(uri: participant.address!)) {
Log.i("[Conference] \(conference) Entered conference")
self.isConferencePaused.value = false
} else {
Log.i("[Conference] \(conference) Participant \(participant) added")
}
self.updateParticipantsList(conference)
self.updateParticipantsDevicesList(conference)
let count = self.conferenceParticipantDevices.value!.count
if (count > self.maxParticipantsForMosaicLayout) {
Log.w("[Conference] \(conference) More than \(self.maxParticipantsForMosaicLayout) participants \(count), forcing active speaker layout")
self.conferenceDisplayMode.value = .ActiveSpeaker
}
}, onParticipantRemoved: {(conference: Conference, participant: Participant) in
if (conference.isMe(uri: participant.address!)) {
Log.i("[Conference] \(conference) \(participant) Left conference")
self.isConferencePaused.value = true
} else {
Log.i("[Conference] \(conference) \(participant) Participant removed")
}
self.updateParticipantsList(conference)
self.updateParticipantsDevicesList(conference)
}, onParticipantDeviceAdded: {(conference: Conference, participantDevice: ParticipantDevice) in
Log.i("[Conference] \(conference) Participant device \(participantDevice) added")
self.updateParticipantsDevicesList(conference)
}, onParticipantDeviceRemoved: { (conference: Conference, participantDevice: ParticipantDevice) in
Log.i("[Conference] \(conference) Participant device \(participantDevice) removed")
self.updateParticipantsDevicesList(conference)
}, onParticipantAdminStatusChanged: { (conference: Conference, participant: Participant) in
Log.i("[Conference] \(conference) Participant admin status changed")
self.isMeAdmin.value = conference.me?.isAdmin
self.updateParticipantsList(conference)
}
)
coreDelegate = CoreDelegateStub(
onConferenceStateChanged: { (core, conference, state) in
Log.i("[Conference] \(conference) Conference state changed: \(state)")
self.isConferencePaused.value = !conference.isIn
self.canResumeConference.value = true // TODO: How can this value be false?
self.isVideoConference.value = conference.currentParams?.isVideoEnabled == true
if (state == Conference.State.Instantiated) {
self.conference.value = conference
self.isInConference.value = true
conference.addDelegate(delegate: self.conferenceDelegate!)
} else if (state == Conference.State.Created) {
self.updateParticipantsList(conference)
self.updateParticipantsDevicesList(conference)
self.isMeConferenceFocus.value = conference.me?.isFocus == true
self.isMeAdmin.value = conference.me?.isAdmin == true
self.conferenceAddress.value = conference.conferenceAddress
self.subject.value = conference.subject.isEmpty ? (
conference.me?.isFocus == true ? (
VoipTexts.conference_local_title
) : (
VoipTexts.conference_default_title
)
) : (
conference.subject
)
} else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) {
self.isInConference.value = false
self.isVideoConference.value = false
conference.removeDelegate(delegate: self.conferenceDelegate!)
self.conferenceParticipants.value?.forEach{ $0.destroy()}
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
self.conferenceParticipants.value = []
self.conferenceParticipantDevices.value = []
}
let layout = conference.layout == .None ? .Grid : conference.layout
self.conferenceDisplayMode.value = layout
Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
}
)
Core.get().addDelegate(delegate: coreDelegate!)
conferenceParticipants.value = []
conferenceParticipantDevices.value = []
conferenceDisplayMode.value = .Grid
subject.value = VoipTexts.conference_default_title
if let conference = core.conference != nil ? core.conference : core.currentCall?.conference {
Log.i("[Conference] Found an existing conference: \(conference)")
self.conference.value = conference
conference.addDelegate(delegate: self.conferenceDelegate!)
isInConference.value = true
isConferencePaused.value = !conference.isIn
isMeConferenceFocus.value = conference.me?.isFocus == true
isMeAdmin.value = conference.me?.isAdmin == true
isVideoConference.value = conference.currentParams?.isVideoEnabled == true
conferenceAddress.value = conference.conferenceAddress
if (!conference.subject.isEmpty) {
subject.value = conference.subject
}
let layout = conference.layout == .None ? .Grid : conference.layout
conferenceDisplayMode.value = layout
Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
updateParticipantsList(conference)
updateParticipantsDevicesList(conference)
}
}
func destroy() {
core.removeDelegate(delegate: self.coreDelegate!)
self.conferenceParticipants.value?.forEach{ $0.destroy()}
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
}
func pauseConference() {
let defaultProxyConfig = core.defaultProxyConfig
let localAddress = defaultProxyConfig?.identityAddress
let participants : [Address] = []
let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants)
let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants)
let conference = remoteConference != nil ? remoteConference : localConference
if (conference != nil) {
Log.i("[Conference] Leaving conference with address \(conference) temporarily")
conference!.leave()
} else {
Log.w("[Conference] Unable to find conference with address \(conference)")
}
}
func resumeConference() {
let defaultProxyConfig = core.defaultProxyConfig
let localAddress = defaultProxyConfig?.identityAddress
let participants : [Address] = []
let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants)
let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants)
if let conference = remoteConference != nil ? remoteConference : localConference {
Log.i("[Conference] Entering again conference with address \(conference)")
conference.enter()
} else {
Log.w("[Conference] Unable to find conference with address \(conference)")
}
}
func togglePlayPause () {
if (isConferencePaused.value == true) {
resumeConference()
isConferencePaused.value = false
} else {
pauseConference()
isConferencePaused.value = true
}
}
func toggleRecording() {
guard let conference = core.conference else {
Log.e("[Conference] Failed to find conference!")
return
}
if (conference.isRecording == true) {
conference.stopRecording()
} else {
let path = AppManager.recordingFilePathFromCall(address: conference.conferenceAddress?.asStringUriOnly() ?? "")
Log.i("[Conference] Starting recording \(conference) in file \(path)")
conference.startRecording(path: path)
}
isRecording.value = conference.isRecording
}
private func updateParticipantsList(_ conference: Conference) {
self.conferenceParticipants.value?.forEach{ $0.destroy()}
var participants :[ConferenceParticipantData] = []
let participantsList = conference.participantList
Log.i("[Conference] \(conference) Conference has \(participantsList.count) participants")
participantsList.forEach { (participant) in
let participantDevices = participant.devices
Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)")
let participantData = ConferenceParticipantData(conference: conference, participant: participant)
participants.append(participantData)
}
conferenceParticipants.value = participants
}
private func updateParticipantsDevicesList(_ conference: Conference) {
self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
var devices :[ConferenceParticipantDeviceData] = []
let participantsList = conference.participantList
Log.i("[Conference] \(conference) Conference has \(participantsList.count) participants")
participantsList.forEach { (participant) in
let participantDevices = participant.devices
Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)")
participantDevices.forEach { (device) in
Log.i("[Conference] \(conference) Participant device found: \(device.name) (\(device.address!.asStringUriOnly()))")
let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: false)
devices.append(deviceData)
}
}
conference.me?.devices.forEach { (device) in
Log.i("[Conference] \(conference) Participant device for myself found: \(device.name) (\(device.address!.asStringUriOnly()))")
let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: true)
devices.append(deviceData)
}
conferenceParticipantDevices.value = devices
}
}
enum FlexDirection {
case ROW
case ROW_REVERSE
case COLUMN
case COLUMN_REVERSE
}

View file

@ -1,265 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
import AVFoundation
class ControlsViewModel {
let core = Core.get()
let isSpeakerSelected = MutableLiveData<Bool>()
let isMicrophoneMuted = MutableLiveData<Bool>()
let isMuteMicrophoneEnabled = MutableLiveData<Bool>()
let isBluetoothHeadsetSelected = MutableLiveData<Bool>()
let nonEarpieceOutputAudioDevice = MutableLiveData<Bool>()
let audioRoutesSelected = MutableLiveData<Bool>()
let audioRoutesEnabled = MutableLiveData<Bool>()
let isVideoUpdateInProgress = MutableLiveData<Bool>()
let isVideoEnabled = MutableLiveData<Bool>()
let isVideoAvailable = MutableLiveData<Bool>()
let fullScreenMode = MutableLiveData(false)
let numpadVisible = MutableLiveData(false)
let callStatsVisible = MutableLiveData(false)
let goToConferenceLayoutSettings = MutableLiveData(false)
let goToConferenceParticipantsListEvent = MutableLiveData(false)
let goToChatEvent = MutableLiveData(false)
let goToCallsListEvent = MutableLiveData(false)
let hideExtraButtons = MutableLiveData(true)
let proximitySensorEnabled = MutableLiveData<Bool>()
static let shared = ControlsViewModel()
private var coreDelegate : CoreDelegateStub?
private var previousCallState = Call.State.Idle
init () {
coreDelegate = CoreDelegateStub(
onCallStateChanged : { (core: Core, call: Call, state: Call.State, message:String) -> Void in
Log.i("[Call Controls] Call state changed: \(call) : \(state)")
if (state == Call.State.StreamsRunning) {
self.isVideoUpdateInProgress.value = false
}
self.updateUI()
self.setAudioRoutes(call,state)
self.previousCallState = state
},
onAudioDeviceChanged : { (core: Core, audioDevice: AudioDevice) -> Void in
Log.i("[Call Controls] Audio device changed: \(audioDevice.deviceName)")
self.nonEarpieceOutputAudioDevice.value = audioDevice.type != AudioDeviceType.Microphone // on iOS Earpiece = Microphone
self.updateSpeakerState()
self.updateBluetoothHeadsetState()
}
)
Core.get().addDelegate(delegate: coreDelegate!)
proximitySensorEnabled.value = shouldProximitySensorBeEnabled()
isVideoEnabled.readCurrentAndObserve { _ in
self.proximitySensorEnabled.value = self.shouldProximitySensorBeEnabled()
}
nonEarpieceOutputAudioDevice.readCurrentAndObserve { _ in
self.proximitySensorEnabled.value = self.shouldProximitySensorBeEnabled()
}
proximitySensorEnabled.readCurrentAndObserve { (enabled) in
UIDevice.current.isProximityMonitoringEnabled = enabled == true
}
updateUI()
}
private func setAudioRoutes(_ call:Call,_ state:Call.State) {
if (state == .OutgoingProgress) {
if (core.callsNb == 1 && ConfigManager.instance().lpConfigBoolForKey(key: "route_audio_to_bluetooth_if_available",defaultValue:true)) {
AudioRouteUtils.routeAudioToBluetooth(call: call)
}
}
if (state == .StreamsRunning) {
if (core.callsNb == 1) {
// Only try to route bluetooth / headphone / headset when the call is in StreamsRunning for the first time
if (previousCallState == Call.State.Connected) {
Log.i("[Context] First call going into StreamsRunning state for the first time, trying to route audio to headset or bluetooth if available")
if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) {
AudioRouteUtils.routeAudioToHeadset(call: call)
} else if (ConfigManager.instance().lpConfigBoolForKey(key: "route_audio_to_bluetooth_if_available",defaultValue:true) &&
AudioRouteUtils.isBluetoothAudioRouteAvailable()) {
AudioRouteUtils.routeAudioToBluetooth(call: call)
}
}
}
if (ConfigManager.instance().lpConfigBoolForKey(key: "route_audio_to_speaker_when_video_enabled",defaultValue:true) && call.currentParams?.videoEnabled == true) {
// Do not turn speaker on when video is enabled if headset or bluetooth is used
if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() &&
!AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(call: call)
) {
Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker")
AudioRouteUtils.routeAudioToSpeaker(call: call)
}
}
}
}
private func shouldProximitySensorBeEnabled() -> Bool {
return isVideoEnabled.value != true && nonEarpieceOutputAudioDevice.value != true
}
func hangUp() {
if (core.currentCall != nil) {
try?core.currentCall?.terminate()
} else if (core.conference?.isIn == true) {
try?core.terminateConference()
} else {
try?core.terminateAllCalls()
}
}
func toggleVideo() {
if let conference = core.conference, conference.isIn {
if let params = try?core.createConferenceParams() {
let videoEnabled = conference.currentParams?.isVideoEnabled == true
params.videoEnabled = !videoEnabled
_ = conference.updateParams(params: params)
}
} else if let currentCall = core.currentCall {
let state = currentCall.state
if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {
return
}
isVideoUpdateInProgress.value = true
if let params = try?core.createCallParams(call: currentCall) {
params.videoEnabled = !(currentCall.currentParams?.videoEnabled == true)
try?currentCall.update(params: params)
if (params.videoEnabled) {
currentCall.requestNotifyNextVideoFrameDecoded()
}
}
}
}
private func updateUI() {
updateVideoAvailable()
updateVideoEnabled()
updateMicState()
updateSpeakerState()
updateAudioRoutesState()
proximitySensorEnabled.value = shouldProximitySensorBeEnabled()
}
private func updateAudioRoutesState() {
let bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable()
audioRoutesEnabled.value = bluetoothDeviceAvailable
if (!bluetoothDeviceAvailable) {
audioRoutesSelected.value = false
audioRoutesEnabled.value = false
}
}
private func updateSpeakerState() {
isSpeakerSelected.value = AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()
}
private func updateBluetoothHeadsetState() {
isBluetoothHeadsetSelected.value = AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed()
}
private func updateVideoAvailable() {
let currentCall = core.currentCall
isVideoAvailable.value = (core.videoCaptureEnabled || core.videoPreviewEnabled) &&
((currentCall != nil && currentCall?.mediaInProgress() != true) || core.conference?.isIn == true)
}
private func updateVideoEnabled() {
let enabled = isVideoCallOrConferenceActive()
isVideoEnabled.value = enabled
}
func updateMicState() {
isMicrophoneMuted.value = !micAuthorized() || !core.micEnabled
isMuteMicrophoneEnabled.value = core.currentCall != nil || core.conference?.isIn == true
}
func micAuthorized() -> Bool {
return AVCaptureDevice.authorizationStatus(for: .audio) == .authorized
}
func isVideoCallOrConferenceActive() -> Bool {
if let conference = core.conference, conference.isIn {
return conference.currentParams?.videoEnabled == true
} else {
return core.currentCall?.currentParams?.videoEnabled == true
}
}
func toggleFullScreen() {
if (isVideoEnabled.value == true) {
fullScreenMode.value = fullScreenMode.value != true
}
}
func toggleMuteMicrophone() {
if (!micAuthorized()) {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
self.core.micEnabled = !self.core.micEnabled
self.updateMicState()
}
}
}
core.micEnabled = !core.micEnabled
updateMicState()
}
func forceEarpieceAudioRoute() {
if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) {
Log.i("[Call Controls] Headset found, route audio to it instead of earpiece")
AudioRouteUtils.routeAudioToHeadset()
} else {
AudioRouteUtils.routeAudioToEarpiece()
}
}
func forceSpeakerAudioRoute() {
AudioRouteUtils.routeAudioToSpeaker()
}
func forceBluetoothAudioRoute() {
AudioRouteUtils.routeAudioToBluetooth()
}
func toggleSpeaker() {
if (AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()) {
forceEarpieceAudioRoute()
} else {
forceSpeakerAudioRoute()
}
}
func toggleRoutesMenu() {
audioRoutesSelected.value = audioRoutesSelected.value != true
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class LightDarkColor {
var light: UIColor
var dark : UIColor
init(_ l:UIColor,_ d:UIColor){
light = l
dark = d
}
func get() -> UIColor {
if #available(iOS 13.0, *) {
if UITraitCollection.current.userInterfaceStyle == .light {
return light
} else {
return dark
}
} else {
return light
}
}
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
struct TextStyle {
var fgColor:LightDarkColor
var bgColor:LightDarkColor
var allCaps:Bool
var align:NSTextAlignment
var font:String
var size:Float
func boldEd() -> TextStyle {
return self.font.contains("Bold") ? self : TextStyle(fgColor: self.fgColor,bgColor: self.bgColor,allCaps: self.allCaps,align: self.align,font: self.font.replacingOccurrences(of: "Regular", with: "Bold"), size: self.size)
}
}
extension UILabel {
func applyStyle(_ style:TextStyle) {
textColor = style.fgColor.get()
backgroundColor = style.bgColor.get()
if (style.allCaps) {
text = self.text?.uppercased()
tag = 1
}
textAlignment = style.align
let fontSizeMultiplier: Float = (UIDevice.ipad() ? 1.25 : UIDevice.is5SorSEGen1() ? 0.9 : 1.0)
font = UIFont.init(name: style.font, size: CGFloat(style.size*fontSizeMultiplier))
}
}
extension UIButton {
func applyTitleStyle(_ style:TextStyle) {
titleLabel?.applyStyle(style)
if (style.allCaps) {
setTitle(self.title(for: .normal)?.uppercased(), for: .normal)
tag = 1
}
setTitleColor(style.fgColor.get(), for: .normal)
contentHorizontalAlignment = style.align == .left ? .left : style.align == .center ? .center : style.align == .right ? .right : .left
}
}

View file

@ -1,137 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class VoipTexts { // From android key names. Added intentionnally with NSLocalizedString calls for each key, so it can be picked up by translation system (Weblate or Xcode).
static let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
// Calls
static let call_incoming_title = NSLocalizedString("Incoming Call",comment:"")
static let call_outgoing_title = NSLocalizedString("Outgoing Call",comment:"")
static let call_notification_paused = NSLocalizedString("Paused call",comment:"")
static let call_notification_outgoing = NSLocalizedString("Outgoing call",comment:"")
static let call_notification_active = NSLocalizedString("Call running",comment:"")
static let call_error_declined = NSLocalizedString("Call has been declined",comment:"")
static let call_error_user_busy = NSLocalizedString("User is busy",comment:"")
static let call_error_user_not_found = NSLocalizedString("User hasn't been found",comment:"")
static let call_error_incompatible_media_params = NSLocalizedString("Incompatible media parameters",comment:"")
static let call_error_network_unreachable = NSLocalizedString("Network is unreachable",comment:"")
static let call_error_io_error = NSLocalizedString("Service unavailable or network error",comment:"")
static let call_error_server_timeout = NSLocalizedString("Server timeout",comment:"")
static let call_error_temporarily_unavailable = NSLocalizedString("Temporarily unavailable",comment:"")
static let call_error_generic = NSLocalizedString("Error: %s",comment:"")
static let call_video_update_requested_dialog = NSLocalizedString("Correspondent would like to turn the video on",comment:"")
static let call_action_participants_list = NSLocalizedString("Participants list",comment:"")
static let call_action_chat = NSLocalizedString("Chat",comment:"")
static let call_action_calls_list = NSLocalizedString("Calls list",comment:"")
static let call_action_numpad = NSLocalizedString("Numpad",comment:"")
static let call_action_change_conf_layout = NSLocalizedString("Change layout",comment:"")
static let call_action_transfer_call = NSLocalizedString("Transfer call",comment:"")
static let call_action_add_call = NSLocalizedString("Start new call",comment:"")
static let call_action_statistics = NSLocalizedString("Call statistics",comment:"")
static let call_context_action_resume = NSLocalizedString("Resume call",comment:"")
static let call_context_action_pause = NSLocalizedString("Pause call",comment:"")
static let call_context_action_transfer = NSLocalizedString("Transfer call",comment:"")
static let call_context_action_answer = NSLocalizedString("Answer call",comment:"")
static let call_context_action_hangup = NSLocalizedString("Terminate call",comment:"")
static let call_remote_recording = NSLocalizedString("This call is being recorded.",comment:"")
static let call_remotely_paused_title = NSLocalizedString("Call has been paused by remote.",comment:"")
// Conference
static let conference_schedule_title = NSLocalizedString("Start a conference",comment:"")
static let conference_schedule_later = NSLocalizedString("Do you want to schedule this conference for later?",comment:"")
static let conference_schedule_mandatory_field = NSLocalizedString("Mandatory",comment:"")
static let conference_schedule_subject_title = NSLocalizedString("Subject",comment:"")
static let conference_schedule_subject_hint = NSLocalizedString("Conference subject",comment:"")
static let conference_schedule_address_title = NSLocalizedString("Conference address",comment:"")
static let conference_schedule_description_title = NSLocalizedString("Add a description",comment:"")
static let conference_schedule_description_hint = NSLocalizedString("Description",comment:"")
static let conference_schedule_date = NSLocalizedString("Date",comment:"")
static let conference_schedule_time = NSLocalizedString("Time",comment:"")
static let conference_schedule_duration = NSLocalizedString("Duration",comment:"")
static let conference_schedule_timezone = NSLocalizedString("Timezone",comment:"")
static let conference_schedule_send_invite_chat = NSLocalizedString("Send invite via \(appName)",comment:"")
static let conference_schedule_send_invite_email = NSLocalizedString("Send invite via email",comment:"")
static let conference_schedule_encryption = NSLocalizedString("Would you like to encrypt the conference?",comment:"")
static let conference_schedule_send_invite_chat_summary = NSLocalizedString("Invite will be sent out from my \(appName) account",comment:"")
static let conference_schedule_participants_list = NSLocalizedString("Participants list",comment:"")
static let conference_schedule_summary = NSLocalizedString("Conference info",comment:"")
static let conference_schedule_create = NSLocalizedString("Create conference",comment:"")
static let conference_schedule = NSLocalizedString("Schedule conference",comment:"")
static let conference_schedule_address_copied_to_clipboard = NSLocalizedString("Conference address copied into clipboard",comment:"")
static let conference_schedule_creation_failure = NSLocalizedString("Failed to create conference!",comment:"")
static let conference_schedule_info_not_sent_to_participant = NSLocalizedString("Failed to send conference info to a participant",comment:"")
static let conference_paused_title = NSLocalizedString("You are currently out of the conference.",comment:"")
static let conference_paused_subtitle = NSLocalizedString("Click on play button to join it back.",comment:"")
static let conference_default_title = NSLocalizedString("Remote conference",comment:"")
static let conference_local_title = NSLocalizedString("Local conference",comment:"")
static let conference_invite_title = NSLocalizedString("Conference invite:",comment:"")
static let conference_description_title = NSLocalizedString("Description:",comment:"")
static let conference_invite_join = NSLocalizedString("Join",comment:"")
static let conference_invite_participants_count = NSLocalizedString("%d participants",comment:"")
static let conference_display_mode_mosaic = NSLocalizedString("Mosaic mode",comment:"")
static let conference_display_mode_active_speaker = NSLocalizedString("Active speaker mode",comment:"")
static let conference_display_no_active_speaker = NSLocalizedString("No active speaker",comment:"")
static let conference_waiting_room_start_call = NSLocalizedString("Start",comment:"")
static let conference_waiting_room_cancel_call = NSLocalizedString("Cancel",comment:"")
static let conference_scheduled = NSLocalizedString("Conferences",comment:"")
static let conference_too_many_participants_for_mosaic_layout = NSLocalizedString("You can't change conference layout as there is too many participants",comment:"")
static let conference_participant_paused = NSLocalizedString("(paused)",comment:"")
// Call Stats
static let call_stats_audio = "Audio"
static let call_stats_video = "Video"
static let call_stats_codec = "Codec:"
static let call_stats_ip = "IP Family:"
static let call_stats_upload = "Upload bandwidth:"
static let call_stats_download = "Download bandwidth:"
static let call_stats_estimated_download = "Estimated download bandwidth:"
static let call_stats_ice = "ICE connectivity:"
static let call_stats_video_resolution_sent = "Sent video resolution:"
static let call_stats_video_resolution_received = "Received video resolution:"
static let call_stats_video_fps_sent = "Sent video fps:"
static let call_stats_video_fps_received = "Received video fps:"
static let call_stats_sender_loss_rate = "Sender loss rate:"
static let call_stats_receiver_loss_rate = "Receiver loss rate:"
static let call_stats_jitter_buffer = "Jitter buffer:"
static let call_stats_encoder_name = "Encoder:"
static let call_stats_decoder_name = "Decoder:"
static let call_stats_player_filter = "Player filter:"
static let call_stats_capture_filter = "Capture filter:"
// Added in iOS
static let camera_required_for_video = NSLocalizedString("Camera use is not Authorized for \(appName). This permission is required to activate Video.",comment:"")
static let ok = NSLocalizedString("ok",comment:"")
static let cancel = NSLocalizedString("cancel",comment:"")
static let dialog_accept = NSLocalizedString("Accept",comment:"")
static let dialog_decline = NSLocalizedString("Decline",comment:"")
// Participants list :
static let chat_room_group_info_admin = NSLocalizedString("Admin",comment:"")
}

View file

@ -1,342 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class VoipTheme { // Names & values replicated from Android
// Voip Colors
static let voip_gray_blue_color = UIColor(hex:"#798791")
static let voip_light_gray = UIColor(hex:"#D0D8DE")
static let voip_dark_gray = UIColor(hex:"#4B5964")
static let voip_gray = UIColor(hex:"#96A5B1")
static let voip_gray_background = UIColor(hex:"#D8D8D8")
static let voip_call_record_background = UIColor(hex:"#EBEBEB")
static let voip_calls_list_inactive_background = UIColor(hex:"#F0F1F2")
static let voip_translucent_popup_background = UIColor(hex:"#A64B5964")
static let voip_translucent_popup_alt_background = UIColor(hex:"#E64B5964")
static let voip_numpad_background = UIColor(hex:"#E4E4E4")
static let voip_contact_avatar_background_alt = UIColor(hex:"#AFAFAF")
static let voip_contact_avatar_calls_list = UIColor(hex:"#A1A1A1")
static let voip_conference_participant_paused_background = UIColor(hex:"#303030")
static let voip_drawable_color = UIColor(hex:"#A6B2BC")
static let voip_dark_color = UIColor(hex:"#252E35")
static let voip_dark_color2 = UIColor(hex:"#3F464B")
static let voip_dark_color3 = UIColor(hex:"#475663")
static let voip_dark_color4 = UIColor(hex:"#2D3841")
// General colors (used by VoIP)
static let primary_color = UIColor(hex:"#ff5e00")
static let primary_dark_color = UIColor(hex:"#e65000")
static let green_color = UIColor(hex:"#96c11f")
static let dark_green_color = UIColor(hex:"#7d9f21")
static let toolbar_color = UIColor(hex:"#e1e1e1")
static let form_field_gray_background = UIColor(hex:"#F7F7F7")
static let light_grey_color = UIColor(hex:"#c4c4c4")
static let header_background_color = UIColor(hex:"#f3f3f3")
static let dark_grey_color = UIColor(hex:"#444444")
// Light / Dark variations
static let voipBackgroundColor = LightDarkColor(voip_gray_blue_color,voip_dark_color)
static let voipBackgroundBWColor = LightDarkColor(UIColor.white,voip_dark_color)
static let voipParticipantBackgroundColor = LightDarkColor(voip_gray_background,voip_dark_color2)
static let voipExtraButtonsBackgroundColor = LightDarkColor(voip_gray,voip_dark_color3)
static let voipToolbarBackgroundColor = LightDarkColor(toolbar_color,voip_dark_color4)
static let voipDrawableColor = LightDarkColor(voip_dark_gray,.white)
static let voipDrawableColorHighlighted = LightDarkColor(voip_gray,voip_gray)
static let voipTextColor = LightDarkColor(voip_dark_gray,UIColor.white)
static let voipFormBackgroundColor = LightDarkColor(form_field_gray_background,voip_dark_color4)
static let voipFormFieldBackgroundColor = LightDarkColor(light_grey_color,voip_dark_color4)
static let voipFormDisabledFieldBackgroundColor = LightDarkColor(header_background_color,voip_dark_color4)
static let primarySubtextLightColor = LightDarkColor(light_grey_color,toolbar_color)
static let primaryTextColor = LightDarkColor(dark_grey_color,.white)
// Text styles
static let fontName = "Roboto"
static let call_header_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 18.0)
static let call_header_subtitle = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 14.0)
static let call_generated_avatar_large = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 53.0)
static let call_generated_avatar_medium = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 27.0)
static let call_generated_avatar_small = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 16.0)
static let dtmf_label = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 30.0)
static let call_remote_name = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 18.0)
static let call_remote_recording = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 16.0)
static let call_or_conference_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 30.0)
static let call_or_conference_subtitle = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 20.0)
static let basic_popup_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 21.0)
static let big_button = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Bold", size: 17.0)
static let call_display_name_duration = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
static let call_sip_address = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 14.0)
static let voip_extra_button = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
static let unread_count_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 11.0)
static let call_stats_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
static let call_stats_font_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
static let calls_list_header_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 20.0)
static let call_list_active_name_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
static let call_list_active_sip_uri_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
static let call_list_name_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
static let call_list_sip_uri_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
static let call_context_menu_item_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .left, font: fontName+"-Bold", size: 16.0)
static let conference_participant_admin_label = TextStyle(fgColor: primarySubtextLightColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 13.0)
static let conference_participant_name_font = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
static let conference_participant_sip_uri_font = TextStyle(fgColor: LightDarkColor(primary_color,primary_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
static let conference_participant_name_font_grid = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 15.0)
static let conference_participant_name_font_as = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 12.0)
static let conference_mode_title = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
static let conference_mode_title_selected = conference_mode_title.boldEd()
// Buttons Background (State colors)
static let button_background = [
UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
UIButton.State.selected.union(.highlighted).rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
]
static let button_background_reverse = [
UIButton.State.normal.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
UIButton.State.highlighted.rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.selected.union(.highlighted).rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
]
static let button_call_recording_background = [
UIButton.State.normal.rawValue : LightDarkColor(voip_call_record_background,voip_call_record_background),
UIButton.State.selected.rawValue : LightDarkColor(primary_color,primary_color),
UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
]
static let button_toggle_background = [
UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.selected.rawValue : LightDarkColor(primary_color,primary_color),
UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
]
static let button_toggle_background_reverse = [
UIButton.State.normal.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
UIButton.State.selected.rawValue : LightDarkColor(primary_color,primary_color),
UIButton.State.highlighted.rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
]
static let primary_colors_background = [
UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color),
UIButton.State.highlighted.rawValue : LightDarkColor(primary_dark_color,primary_dark_color),
]
static let primary_colors_background_gray = [
UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
]
static let numpad_digit_background = [
UIButton.State.normal.rawValue : LightDarkColor(voip_numpad_background,voip_numpad_background),
UIButton.State.highlighted.rawValue : LightDarkColor(voip_gray_blue_color,voip_gray_blue_color)
]
static let button_round_background = [
UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color),
UIButton.State.highlighted.rawValue : LightDarkColor(dark_grey_color,dark_grey_color),
UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
]
static let button_call_context_menu_background = [
UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
UIButton.State.highlighted.rawValue : LightDarkColor(primary_color,primary_color),
]
// Buttons Icons (State colors) + Background colors
static let call_terminate = ButtonTheme(
tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_hangup",tintColor: LightDarkColor(.white,.white))],
backgroundStateColors: [
UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color),
UIButton.State.highlighted.rawValue : LightDarkColor(primary_dark_color,primary_dark_color)
])
static let call_record = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_record",tintColor: LightDarkColor(voip_gray_blue_color,voip_gray_blue_color)),
UIButton.State.selected.rawValue : TintableIcon(name: "voip_call_record",tintColor: LightDarkColor(.white,.white)),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_record",tintColor: LightDarkColor(primary_color,primary_color)),
],
backgroundStateColors: button_call_recording_background)
static let call_pause = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_pause",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_toggle_background)
static let call_accept = ButtonTheme(
tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_call",tintColor: LightDarkColor(.white,.white))],
backgroundStateColors: [
UIButton.State.normal.rawValue : LightDarkColor(green_color,green_color),
UIButton.State.highlighted.rawValue : LightDarkColor(dark_green_color,dark_green_color)
])
static let call_mute = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_micro_on",tintColor: LightDarkColor(.white,.white)),
UIButton.State.selected.rawValue : TintableIcon(name: "voip_micro_off",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_background_reverse)
static let call_speaker = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_speaker_off",tintColor: LightDarkColor(.white,.white)),
UIButton.State.selected.rawValue : TintableIcon(name: "voip_speaker_on",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_background_reverse)
static let call_audio_route = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_audio_routes",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_toggle_background_reverse)
static let call_video = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_camera_off",tintColor: LightDarkColor(.white,.white)),
UIButton.State.selected.rawValue : TintableIcon(name: "voip_camera_on",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_background_reverse)
static let call_numpad = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_numpad",tintColor: LightDarkColor(.white,.white)),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_numpad",tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray)),
UIButton.State.disabled.rawValue : TintableIcon(name: "voip_call_numpad",tintColor: LightDarkColor(voip_light_gray,voip_light_gray)),
],
backgroundStateColors: button_background)
// AUdio routes
static let route_bluetooth = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_bluetooth",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_toggle_background_reverse)
static let route_earpiece = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_earpiece",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_toggle_background_reverse)
static let route_speaker = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_speaker_on",tintColor: LightDarkColor(.white,.white)),
],
backgroundStateColors: button_toggle_background_reverse)
static let call_more = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_more",tintColor: LightDarkColor(.white,.white))
],
backgroundStateColors: button_background)
static let voip_cancel = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_cancel",tintColor: voipDrawableColor),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_cancel",tintColor: voipDrawableColorHighlighted)
],
backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
static let voip_cancel_light = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_cancel",tintColor: LightDarkColor(voip_gray,voip_gray)),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_cancel",tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray))
],
backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
static let voip_edit = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_edit",tintColor: LightDarkColor(dark_grey_color,dark_grey_color)),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_edit",tintColor: voipDrawableColorHighlighted)
],
backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
static let radio_button = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_radio_off",tintColor: LightDarkColor(dark_grey_color,dark_grey_color)),
UIButton.State.selected.rawValue : TintableIcon(name: "voip_radio_on",tintColor: LightDarkColor(primary_color,primary_color))
],
backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
static let voip_call_list_active_menu = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: LightDarkColor(.white,.white)),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: voipDrawableColorHighlighted)
],
backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
static let voip_call_list_menu = ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: voipTextColor),
UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: voipDrawableColorHighlighted)
],
backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
static func call_action(_ iconName:String) -> ButtonTheme {
return ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(.white,.white)),
UIButton.State.highlighted.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray)),
UIButton.State.disabled.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(voip_light_gray,voip_light_gray)),
],
backgroundStateColors: [:])
}
static let call_add = ButtonTheme(
tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_add",tintColor: LightDarkColor(.white,.white))],
backgroundStateColors: button_round_background)
static let call_merge = ButtonTheme(
tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_merge_calls",tintColor: LightDarkColor(.white,.white))],
backgroundStateColors: button_round_background)
}

View file

@ -1,348 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import linphonesw
class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // Replaces CallView
// Layout constants
let content_inset = 12.0
var callPausedByRemoteView : PausedCallOrConferenceView? = nil
var conferencePausedView : PausedCallOrConferenceView? = nil
var currentCallView : ActiveCallView? = nil
var conferenceGridView: VoipConferenceGridView? = nil
var conferenceActiveSpeakerView: VoipConferenceActiveSpeakerView? = nil
let extraButtonsView = VoipExtraButtonsView()
var numpadView : NumpadView? = nil
var currentCallStatsVew : CallStatsView? = nil
var shadingMask = UIView()
var videoAcceptDialog : VoipDialog? = nil
var dismissableView : DismissableView? = nil
var participantsListView : ParticipantsListView? = nil
var audioRoutesView : AudioRoutesView? = nil
static let compositeDescription = UICompositeViewDescription(ActiveCallOrConferenceView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil)
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = VoipTheme.voipBackgroundColor.get()
// Hangup
let hangup = CallControlButton(width: 65, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
ControlsViewModel.shared.hangUp()
})
view.addSubview(hangup)
hangup.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
// Controls
let controlsView = ControlsView(showVideo: true)
view.addSubview(controlsView)
controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
// Container fiew
let fullScreenMutableContainerView = UIView()
fullScreenMutableContainerView.backgroundColor = .clear
self.view.addSubview(fullScreenMutableContainerView)
fullScreenMutableContainerView.matchParentSideBorders(insetedByDx: content_inset).matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
// Current (Single) Call (VoipCallView)
currentCallView = ActiveCallView()
currentCallView!.isHidden = true
fullScreenMutableContainerView.addSubview(currentCallView!)
CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in
self.updateNavigation()
self.currentCallView!.isHidden = currentCallData == nil || ConferenceViewModel.shared.isInConference.value == true
self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil
currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in
self.callPausedByRemoteView?.isHidden = remotelyPaused != true
}
if (currentCallData == nil) {
self.callPausedByRemoteView?.isHidden = true
} else {
currentCallData??.isIncoming.readCurrentAndObserve { _ in self.updateNavigation() }
currentCallData??.isOutgoing.readCurrentAndObserve { _ in self.updateNavigation() }
}
self.extraButtonsView.isHidden = true
self.conferencePausedView?.isHidden = true
}
currentCallView!.matchParentDimmensions().done()
// Paused by remote (Call)
callPausedByRemoteView = PausedCallOrConferenceView(iconName: "voip_conference_paused_big",titleText: VoipTexts.call_remotely_paused_title,subTitleText: nil)
view.addSubview(callPausedByRemoteView!)
callPausedByRemoteView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
callPausedByRemoteView?.isHidden = true
// Conference paused
conferencePausedView = PausedCallOrConferenceView(iconName: "voip_conference_paused_big",titleText: VoipTexts.conference_paused_title,subTitleText: VoipTexts.conference_paused_subtitle)
view.addSubview(conferencePausedView!)
conferencePausedView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
conferencePausedView?.isHidden = true
// Conference grid
conferenceGridView = VoipConferenceGridView()
fullScreenMutableContainerView.addSubview(conferenceGridView!)
conferenceGridView?.matchParentDimmensions().done()
conferenceGridView?.isHidden = true
ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isInConference) in
self.updateNavigation()
if (isInConference == true) {
self.currentCallView!.isHidden = true
self.extraButtonsView.isHidden = true
self.conferencePausedView?.isHidden = true
self.conferenceGridView!.isHidden = false
self.conferenceActiveSpeakerView!.isHidden = true
self.conferenceGridView?.conferenceViewModel = ConferenceViewModel.shared
} else {
self.conferenceGridView?.isHidden = true
}
}
// Conference active speaker
conferenceActiveSpeakerView = VoipConferenceActiveSpeakerView()
fullScreenMutableContainerView.addSubview(conferenceActiveSpeakerView!)
conferenceActiveSpeakerView?.matchParentDimmensions().done()
conferenceActiveSpeakerView?.isHidden = true
// Conference mode switching
ConferenceViewModel.shared.conferenceDisplayMode.readCurrentAndObserve { (conferenceMode) in
if (ConferenceViewModel.shared.isInConference.value == true) {
self.conferenceGridView!.isHidden = conferenceMode != .Grid
self.conferenceActiveSpeakerView!.isHidden = conferenceMode != .ActiveSpeaker
self.conferenceActiveSpeakerView?.conferenceViewModel = ConferenceViewModel.shared
} else {
self.conferenceActiveSpeakerView?.isHidden = true
}
}
ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isInConference) in
self.updateNavigation()
}
// Calls List
ControlsViewModel.shared.goToCallsListEvent.observe { (_) in
self.dismissableView = CallsListView()
self.view.addSubview(self.dismissableView!)
self.dismissableView?.matchParentDimmensions().done()
}
// Conference Participants List
ControlsViewModel.shared.goToConferenceParticipantsListEvent.observe { (_) in
self.participantsListView = ParticipantsListView()
self.view.addSubview(self.participantsListView!)
self.participantsListView?.matchParentDimmensions().done()
}
// Goto chat
ControlsViewModel.shared.goToChatEvent.observe { (_) in
self.goToChat()
}
// Conference mode selection
ControlsViewModel.shared.goToConferenceLayoutSettings.observe { (_) in
self.dismissableView = VoipConferenceDisplayModeSelectionView()
self.view.addSubview(self.dismissableView!)
self.dismissableView?.matchParentDimmensions().done()
let activeDisplayMode = ConferenceViewModel.shared.conferenceDisplayMode.value!
let indexPath = IndexPath(row: activeDisplayMode == .Grid ? 0 : 1, section: 0)
(self.dismissableView as! VoipConferenceDisplayModeSelectionView).optionsListView.selectRow(at:indexPath, animated: true, scrollPosition: .bottom)
}
// Shading mask, everything before will be shaded upon displaying of the mask
shadingMask.backgroundColor = VoipTheme.voip_translucent_popup_background
shadingMask.isHidden = true
self.view.addSubview(shadingMask)
shadingMask.matchParentDimmensions().done()
// Extra Buttons
let showextraButtons = CallControlButton(imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_more, onClickAction: {
self.showModalSubview(view: self.extraButtonsView)
ControlsViewModel.shared.audioRoutesSelected.value = false
})
view.addSubview(showextraButtons)
showextraButtons.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
let boucingCounter = BouncingCounter(inButton:showextraButtons)
view.addSubview(boucingCounter)
boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount
view.addSubview(extraButtonsView)
extraButtonsView.matchParentSideBorders(insetedByDx: content_inset).alignParentBottom().done()
ControlsViewModel.shared.hideExtraButtons.readCurrentAndObserve { (_) in
self.hideModalSubview(view: self.extraButtonsView)
}
self.view.onClick {
if (!self.extraButtonsView.isHidden) {
self.hideModalSubview(view: self.extraButtonsView)
}
ControlsViewModel.shared.audioRoutesSelected.value = false
}
// Numpad
ControlsViewModel.shared.numpadVisible.readCurrentAndObserve { (visible) in
if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) {
self.numpadView?.removeFromSuperview()
self.shadingMask.isHidden = false
self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!, onDismissAction: {
self.numpadView?.removeFromSuperview()
self.shadingMask.isHidden = true
})
}
}
// Call stats
ControlsViewModel.shared.callStatsVisible.readCurrentAndObserve { (visible) in
if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) {
self.currentCallStatsVew?.removeFromSuperview()
self.shadingMask.isHidden = false
self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!, onDismissAction: {
self.currentCallStatsVew?.removeFromSuperview()
self.shadingMask.isHidden = true
})
}
}
// Video activation dialog request
CallsViewModel.shared.callUpdateEvent.observe { (call) in
let core = Core.get()
if (call?.state == .StreamsRunning) {
self.videoAcceptDialog?.removeFromSuperview()
self.videoAcceptDialog = nil
} else if (call?.state == .UpdatedByRemote) {
if (core.videoCaptureEnabled || core.videoDisplayEnabled) {
if (call?.currentParams?.videoEnabled != call?.remoteParams?.videoEnabled) {
let accept = ButtonAttributes(text:VoipTexts.dialog_accept, action: {call?.answerVideoUpdateRequest(accept: true)}, isDestructive:false)
let cancel = ButtonAttributes(text:VoipTexts.dialog_decline, action: {call?.answerVideoUpdateRequest(accept: false)}, isDestructive:true)
self.videoAcceptDialog = VoipDialog(message:VoipTexts.call_video_update_requested_dialog, givenButtons: [cancel,accept])
self.videoAcceptDialog?.show()
}
} else {
Log.w("[Call] Video display & capture are disabled, don't show video dialog")
}
}
}
// Audio Routes
audioRoutesView = AudioRoutesView()
view.addSubview(audioRoutesView!)
audioRoutesView!.alignBottomWith(otherView: controlsView).done()
ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in
self.audioRoutesView!.isHidden = audioRoutesSelected != true
}
audioRoutesView!.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
extraButtonsView.refresh()
ControlsViewModel.shared.callStatsVisible.notifyValue()
CallsViewModel.shared.currentCallData.notifyValue()
ControlsViewModel.shared.audioRoutesSelected.value = false
}
override func viewWillDisappear(_ animated: Bool) {
dismissableView?.removeFromSuperview()
dismissableView = nil
participantsListView?.removeFromSuperview()
participantsListView = nil
ControlsViewModel.shared.fullScreenMode.value = false
super.viewWillDisappear(animated)
}
func showModalSubview(view:UIView) {
view.isHidden = false
shadingMask.isHidden = false
}
func hideModalSubview(view:UIView) {
view.isHidden = true
shadingMask.isHidden = true
}
func updateNavigation() {
if (Core.get().callsNb == 0) {
PhoneMainView.instance().popView(self.compositeViewDescription())
} else {
if let data = CallsViewModel.shared.currentCallData.value {
if (data?.isOutgoing.value == true || data?.isIncoming.value == true) {
PhoneMainView.instance().popView(self.compositeViewDescription())
} else {
PhoneMainView.instance().changeCurrentView(self.compositeViewDescription())
}
} else {
PhoneMainView.instance().changeCurrentView(self.compositeViewDescription())
}
}
}
func goToChat() {
let core = Core.get()
guard
let localSipUri = core.defaultAccount?.params?.identityAddress?.asStringUriOnly(),
let remoteSipUri = ConferenceViewModel.shared.isInConference.value == true ? ConferenceViewModel.shared.conferenceAddress.value?.asStringUriOnly() : core.currentCall?.remoteAddress?.asStringUriOnly(),
let localAddress = try?Factory.Instance.createAddress(addr: localSipUri),
let remoteSipAddress = try?Factory.Instance.createAddress(addr: remoteSipUri),
let chatRoomParams = try?core.createDefaultChatRoomParams()
else {
return
}
var chatRoom = core.searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: [])
if (chatRoom == nil) {
chatRoom = core.searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress])
}
if (chatRoom == nil) {
Log.w("[Call] Failed to find existing chat room for local address \(localSipUri) and remote address \(remoteSipUri)")
// TODO: configure chat room params
if (ConferenceViewModel.shared.isInConference.value == true) {
// TODO: compute conference participants addresses list
} else {
chatRoom = try?core.createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress])
}
}
if (chatRoom != nil) {
PhoneMainView.instance().go(to: chatRoom?.getCobject)
} else {
Log.w("[Call] Failed to create chat room for local address \(localSipUri) and remote address \(remoteSipUri)")
}
}
}

View file

@ -1,81 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
@objc class IncomingCallView: IncomingOutgoingCommonView, UICompositeViewDelegate {
// Layout constants
let buttons_distance_from_center_x = 38
static let compositeDescription = UICompositeViewDescription(IncomingCallView.self, statusBar: nil, tabBar: nil, sideMenu: nil, fullscreen: true, isLeftFragment: false,fragmentWith: nil)
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
var earlyMediaView : UIView? = nil
override func viewDidLoad() {
super.viewDidLoad(forCallType: VoipTexts.call_incoming_title)
// Accept
let accept = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_accept, onClickAction: {
self.callData.map { CallManager.instance().acceptCall(call: $0.call.getCobject, hasVideo: false)}
})
view.addSubview(accept)
accept.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
// Decline
let decline = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)}
})
view.addSubview(decline)
decline.centerX(withDx: -buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
}
@objc override func setCall(call:OpaquePointer) {
super.setCall(call: call)
callData?.iFrameReceived.observe(onChange: { (video) in
if (video == true) {
Core.get().nativeVideoWindow = self.earlyMediaView
self.earlyMediaView?.isHidden = false
}
})
callData?.callState.readCurrentAndObserve(onChange: { (state) in
if (ConfigManager.instance().lpConfigBoolForKey(key: "pref_accept_early_media") && state == .IncomingReceived) {
try?self.callData?.call.acceptEarlyMedia()
self.callData?.call.requestNotifyNextVideoFrameDecoded()
}
})
callData?.isIncoming.readCurrentAndObserve { (incoming) in
if (incoming != true) {
PhoneMainView.instance().popView(self.compositeViewDescription())
}
}
if (ConfigManager.instance().lpConfigBoolForKey(key: "auto_answer")) {
CallManager.instance().acceptCall(call: call, hasVideo: false) // TODO check with old version for Video accept separate button - Not implemented in Android
}
}
}

View file

@ -1,102 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
@objc class OutgoingCallView: IncomingOutgoingCommonView, UICompositeViewDelegate {
// Layout constants
let numpad_icon_padding = 10.0
var numpadView : NumpadView? = nil
var showNumPad : CallControlButton? = nil
var shadingMask = UIView()
static let compositeDescription = UICompositeViewDescription(OutgoingCallView.self, statusBar: nil, tabBar: nil, sideMenu: nil, fullscreen: true, isLeftFragment: false,fragmentWith: nil)
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
func compositeViewDescription() -> UICompositeViewDescription! { return OutgoingCallView.compositeDescription }
override func viewDidLoad() {
super.viewDidLoad(forCallType: VoipTexts.call_outgoing_title)
// Cancel
let cancelCall = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)}
})
view.addSubview(cancelCall)
cancelCall.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
// Controls
let controlsView = ControlsView(showVideo: false)
view.addSubview(controlsView)
controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
// Shading mask, everything after will be shaded upon displayed
shadingMask.backgroundColor = VoipTheme.voip_translucent_popup_background
shadingMask.isHidden = true
self.view.addSubview(shadingMask)
shadingMask.matchParentDimmensions().done()
// Numpad
showNumPad = CallControlButton(imageInset:UIEdgeInsets(top: numpad_icon_padding, left: numpad_icon_padding, bottom: numpad_icon_padding, right: numpad_icon_padding), buttonTheme: VoipTheme.call_numpad, onClickAction: {
self.numpadView?.removeFromSuperview()
self.shadingMask.isHidden = false
self.numpadView = NumpadView(superView: self.view,callData: self.callData!, onDismissAction: {
self.numpadView?.removeFromSuperview()
self.shadingMask.isHidden = true
})
})
view.addSubview(showNumPad!)
showNumPad?.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
showNumPad!.isHidden = true
// Audio Routes
let audioRoutesView = AudioRoutesView()
view.addSubview(audioRoutesView)
audioRoutesView.alignBottomWith(otherView: controlsView).done()
ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in
audioRoutesView.isHidden = audioRoutesSelected != true
}
audioRoutesView.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).matchRightOf(view: controlsView, withMargin:+ControlsView.controls_button_spacing).done()
}
override func viewWillAppear(_ animated: Bool) {
ControlsViewModel.shared.audioRoutesSelected.value = false
super.viewWillAppear(animated)
}
@objc override func setCall(call:OpaquePointer) {
super.setCall(call: call)
self.callData?.outgoingEarlyMedia.readCurrentAndObserve(onChange: { (outgoingEM) in
self.showNumPad!.isHidden = outgoingEM != true
})
callData?.isOutgoing.readCurrentAndObserve { (outgoing) in
if (outgoing != true) {
PhoneMainView.instance().popView(self.compositeViewDescription())
}
}
}
}

View file

@ -1,231 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class ActiveCallView: UIView { // = currentCall
// Layout constants :
static let top_displayname_margin_top = 20.0
let sip_address_margin_top = 4.0
static let remote_recording_margin_top = 10.0
static let remote_recording_height = 30
static let bottom_displayname_margin_bottom = 10.0
static let bottom_displayname_margin_left = 12.0
static let center_view_margin_top = 15.0
static let center_view_corner_radius = 20.0
let record_pause_button_size = 40
let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
let record_pause_button_margin = 10.0
static let local_video_width = 150.0
static let local_video_margins = 15.0
let displayNameTop = StyledLabel(VoipTheme.call_display_name_duration)
let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
let sipAddress = StyledLabel(VoipTheme.call_sip_address)
let remotelyRecordedIndicator = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
let avatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let displayNameBottom = StyledLabel(VoipTheme.call_remote_name)
var recordCallButtons : [CallControlButton] = []
var pauseCallButtons : [CallControlButton] = []
let remoteVideo = UIView()
let localVideo = LocalVideoView(width: local_video_width)
var callData: CallData? = nil {
didSet {
duration.call = callData?.call
callData?.call.remoteAddress.map {
avatar.fillFromAddress(address: $0)
if let displayName = $0.addressBookEnhancedDisplayName() {
displayNameTop.text = displayName+" - "
displayNameBottom.text = displayName
}
sipAddress.text = $0.asStringUriOnly()
}
self.remotelyRecordedIndicator.isRemotelyRecorded = callData?.isRemotelyRecorded
callData?.isRecording.readCurrentAndObserve { (selected) in
self.recordCallButtons.forEach {
$0.isSelected = selected == true
}
}
callData?.isPaused.readCurrentAndObserve { (paused) in
self.pauseCallButtons.forEach {
$0.isSelected = paused == true
}
if (paused == true) {
self.localVideo.isHidden = true
}
}
Core.get().nativeVideoWindow = remoteVideo
Core.get().nativePreviewWindow = localVideo
ControlsViewModel.shared.isVideoEnabled.readCurrentAndObserve{ (video) in
self.remoteVideo.isHidden = video != true
self.localVideo.isHidden = video != true
self.recordCallButtons.first?.isHidden = video != true
self.pauseCallButtons.first?.isHidden = video != true
self.recordCallButtons.last?.isHidden = video == true
self.pauseCallButtons.last?.isHidden = video == true
}
}
}
init() {
super.init(frame: .zero)
let stack = UIStackView()
stack.distribution = .equalSpacing
stack.alignment = .bottom
stack.spacing = record_pause_button_margin
stack.axis = .vertical
let displayNameDurationSipAddress = UIView()
displayNameDurationSipAddress.addSubview(displayNameTop)
displayNameTop.alignParentLeft().done()
displayNameDurationSipAddress.addSubview(duration)
duration.toRightOf(displayNameTop).alignParentRight().done()
displayNameDurationSipAddress.addSubview(sipAddress)
sipAddress.matchParentSideBorders().alignUnder(view: displayNameTop,withMargin:sip_address_margin_top).done()
let upperSection = UIStackView()
upperSection.distribution = .equalSpacing
upperSection.alignment = .center
upperSection.spacing = record_pause_button_margin
upperSection.axis = .horizontal
upperSection.addArrangedSubview(displayNameDurationSipAddress)
displayNameDurationSipAddress.wrapContentY().done()
let recordPauseView = UIStackView()
recordPauseView.spacing = record_pause_button_margin
// Record (with video)
var recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
self.callData.map { $0.toggleRecord() }
})
recordCallButtons.append(recordCall)
recordPauseView.addArrangedSubview(recordCall)
// Pause (with video)
var pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
self.callData.map { $0.togglePause() }
})
pauseCallButtons.append(pauseCall)
recordPauseView.addArrangedSubview(pauseCall)
upperSection.addArrangedSubview(recordPauseView)
stack.addArrangedSubview(upperSection)
upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
stack.addArrangedSubview(remotelyRecordedIndicator)
remotelyRecordedIndicator.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
// Center Section : Avatar + video + record/pause buttons + videos
let centerSection = UIView()
centerSection.layer.cornerRadius = ActiveCallView.center_view_corner_radius
centerSection.clipsToBounds = true
centerSection.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
// Record (w/o video)
recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
self.callData.map { $0.toggleRecord() }
})
recordCallButtons.append(recordCall)
centerSection.addSubview(recordCall)
recordCall.alignParentLeft(withMargin:record_pause_button_margin).alignParentTop(withMargin:record_pause_button_margin).done()
// Pause (w/o video)
pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
self.callData.map { $0.togglePause() }
})
pauseCallButtons.append(pauseCall)
centerSection.addSubview(pauseCall)
pauseCall.alignParentRight(withMargin:record_pause_button_margin).alignParentTop(withMargin:record_pause_button_margin).done()
// Avatar
centerSection.addSubview(avatar)
avatar.square(Avatar.diameter_for_call_views).center().done()
// Remote Video Display
centerSection.addSubview(remoteVideo)
remoteVideo.isHidden = true
remoteVideo.matchParentDimmensions().done()
// Local Video Display
centerSection.addSubview(localVideo)
localVideo.backgroundColor = .black
localVideo.alignParentBottom(withMargin: ActiveCallView.local_video_margins).alignParentRight(withMargin: ActiveCallView.local_video_margins).done()
localVideo.isHidden = true
localVideo.dragZone = centerSection
// Full screen video togggle
remoteVideo.onClick {
ControlsViewModel.shared.toggleFullScreen()
}
ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in
if (self.isHidden) {
return
}
self.remoteVideo.removeConstraints().done()
self.localVideo.removeConstraints().done()
if (fullScreen == true) {
self.remoteVideo.removeFromSuperview()
self.localVideo.removeFromSuperview()
PhoneMainView.instance().mainViewController.view?.addSubview(self.remoteVideo)
PhoneMainView.instance().mainViewController.view?.addSubview(self.localVideo)
} else {
self.remoteVideo.removeFromSuperview()
self.localVideo.removeFromSuperview()
centerSection.addSubview(self.remoteVideo)
centerSection.addSubview(self.localVideo)
}
self.remoteVideo.matchParentDimmensions().done()
self.localVideo.alignParentBottom(withMargin: ActiveCallView.local_video_margins).alignParentRight(withMargin: ActiveCallView.local_video_margins).done()
self.localVideo.setSizeConstraint()
}
// Bottom display name
centerSection.addSubview(displayNameBottom)
displayNameBottom.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentRight().alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
stack.addArrangedSubview(centerSection)
centerSection.matchParentSideBorders().alignUnder(view:upperSection,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
addSubview(stack)
stack.matchParentDimmensions().done()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,80 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class AudioRoutesView: UIStackView {
// Layout constants
let corner_radius = 6.7
let margin = 10.0
init () {
super.init(frame: .zero)
axis = .vertical
distribution = .equalCentering
alignment = .center
spacing = ControlsView.controls_button_spacing
backgroundColor = VoipTheme.voip_gray
layer.cornerRadius = corner_radius
clipsToBounds = true
// bluetooth
let blueTooth = CallControlButton(buttonTheme: VoipTheme.route_bluetooth, onClickAction: {
ControlsViewModel.shared.forceBluetoothAudioRoute()
})
addArrangedSubview(blueTooth)
ControlsViewModel.shared.isBluetoothHeadsetSelected.readCurrentAndObserve { (selected) in
blueTooth.isSelected = selected == true
}
// Earpiece
let earpiece = CallControlButton(buttonTheme: VoipTheme.route_earpiece, onClickAction: {
ControlsViewModel.shared.forceEarpieceAudioRoute()
})
addArrangedSubview(earpiece)
ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (isSpeakerSelected) in
earpiece.isSelected = isSpeakerSelected != true && ControlsViewModel.shared.isBluetoothHeadsetSelected.value != true
}
ControlsViewModel.shared.isBluetoothHeadsetSelected.readCurrentAndObserve { (isBluetoothHeadsetSelected) in
earpiece.isSelected = isBluetoothHeadsetSelected != true && ControlsViewModel.shared.isSpeakerSelected.value != true
}
// Speaker
let speaker = CallControlButton(buttonTheme: VoipTheme.route_speaker, onClickAction: {
ControlsViewModel.shared.forceSpeakerAudioRoute()
})
addArrangedSubview(speaker)
ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (selected) in
speaker.isSelected = selected == true
}
size(w:CGFloat(CallControlButton.default_size)+margin, h : 3*CGFloat(CallControlButton.default_size)+2*CGFloat(ControlsView.controls_button_spacing)+margin).done()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,105 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
@objc class CallStatsView: UIView {
// Layout constants
let side_margins = 10.0
let margin_top = 50
let corner_radius = 20.0
let view_height = 600
let audio_video_margin = 20
init(superView:UIView, callData:CallData, onDismissAction : @escaping ()->Void) {
super.init(frame:.zero)
backgroundColor = VoipTheme.voip_translucent_popup_background
layer.cornerRadius = corner_radius
clipsToBounds = true
superView.addSubview(self)
snp.makeConstraints { make in
make.left.equalToSuperview().offset(side_margins)
make.right.equalToSuperview().offset(-side_margins)
make.height.equalTo(view_height)
make.bottom.equalToSuperview().offset(-side_margins)
}
callData.callState.observe { state in
if (state == Call.State.End) {
onDismissAction()
}
}
let hide = CallControlButton(buttonTheme: VoipTheme.voip_cancel_light, onClickAction: {
onDismissAction()
})
addSubview(hide)
hide.alignParentRight(withMargin: side_margins).alignParentTop(withMargin: side_margins).done()
let model = CallStatisticsData(call: callData.call)
let audioTitle = StyledLabel(VoipTheme.call_stats_font_title,NSLocalizedString("Audio", comment: ""))
addSubview(audioTitle)
audioTitle.matchParentSideBorders().alignParentTop(withMargin: margin_top).done()
let audioStats = StyledLabel(VoipTheme.call_stats_font)
audioStats.numberOfLines = 0
addSubview(audioStats)
audioStats.matchParentSideBorders().alignUnder(view: audioTitle).done()
let videoTitle = StyledLabel(VoipTheme.call_stats_font_title,NSLocalizedString("Video", comment: ""))
addSubview(videoTitle)
videoTitle.alignUnder(view: audioStats, withMargin:audio_video_margin).matchParentSideBorders().done()
let videoStats = StyledLabel(VoipTheme.call_stats_font)
videoStats.numberOfLines = 0
addSubview(videoStats)
videoStats.matchParentSideBorders().alignUnder(view: videoTitle).done()
model.isVideoEnabled.readCurrentAndObserve { (video) in
videoTitle.isHidden = video != true
videoStats.isHidden = video != true
}
model.statsUpdated.readCurrentAndObserve { (updated) in
var stats = ""
model.audioStats.forEach {
stats += "\n\($0.getTypeTitle())\($0.value.value ?? "n/a")"
}
audioStats.text = stats
stats = ""
model.videoStats.forEach {
stats += "\n\($0.getTypeTitle())\($0.value.value ?? "n/a")"
}
videoStats.text = stats
}
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}

View file

@ -1,139 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
@objc class CallsListView: DismissableView, UITableViewDataSource {
// Layout constants
let buttons_distance_from_center_x = 38
let buttons_size = 60
let callsListTableView = UITableView()
let menuView = VoipCallContextMenu()
var callsDataObserver : MutableLiveDataOnChangeClosure<[CallData]>? = nil
init() {
super.init(title: VoipTexts.call_action_calls_list)
// New Call
let newCall = CallControlButton(width: buttons_size,height: buttons_size, buttonTheme: VoipTheme.call_add, onClickAction: {
let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
view.setAddress("")
CallManager.instance().nextCallIsTransfer = false
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
})
addSubview(newCall)
newCall.centerX(withDx: -buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
// Merge Calls
let mergeIntoLocalConference = CallControlButton(width: buttons_size,height: buttons_size, buttonTheme: VoipTheme.call_merge, onClickAction: {
self.removeFromSuperview()
CallsViewModel.shared.mergeCallsIntoLocalConference()
})
addSubview(mergeIntoLocalConference)
mergeIntoLocalConference.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
CallsViewModel.shared.callsData.readCurrentAndObserve{ (callsData) in
if let callsData = callsData {
mergeIntoLocalConference.isEnabled = callsData.count >= 2 && Core.get().conference?.isIn != true
} else {
mergeIntoLocalConference.isEnabled = false
}
self.callsListTableView.reloadData()
}
// CallsList
super.contentView.addSubview(callsListTableView)
callsListTableView.matchParentDimmensions().done()
callsListTableView.dataSource = self
callsListTableView.register(VoipCallCell.self, forCellReuseIdentifier: "VoipCallCell")
callsListTableView.allowsSelection = false
if #available(iOS 15.0, *) {
callsListTableView.allowsFocus = false
}
callsListTableView.separatorStyle = .singleLine
callsListTableView.separatorColor = .white
callsListTableView.onClick {
self.hideMenu()
}
// Floating menu
super.contentView.addSubview(menuView)
menuView.isHidden = true
}
func toggleMenu(forCell:VoipCallCell) {
if (menuView.isHidden) {
showMenu(forCell: forCell)
} else if (menuView.callData?.call.callLog?.callId != forCell.callData?.call.callLog?.callId) {
hideMenu()
showMenu(forCell: forCell)
} else {
hideMenu()
}
}
func showMenu(forCell:VoipCallCell) {
menuView.removeConstraints().alignUnder(view: forCell).alignParentRight().done()
menuView.callData = forCell.callData
menuView.isHidden = false
}
func hideMenu() {
menuView.isHidden = true
}
// TableView datasource delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let callsData = CallsViewModel.shared.callsData.value else {
return 0
}
return callsData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:VoipCallCell = tableView.dequeueReusableCell(withIdentifier: "VoipCallCell") as! VoipCallCell
guard let callData = CallsViewModel.shared.callsData.value?[indexPath.row] else {
return cell
}
cell.selectionStyle = .none
cell.callData = callData
cell.owningCallsListView = self
return cell
}
// View controller
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,106 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class VoipCallCell: UITableViewCell {
// Layout Constants
let cell_height = 80.0
let call_status_icon_size = 65.0
static let avatar_size = 45.0
let avatar_left_margin = 40.0
let texts_left_margin = 20.0
let side_menu_icon_size = 80.0
var onMenuClickAction : (()->Void) = {}
let callStatusIcon = UIImageView()
let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:LightDarkColor(VoipTheme.voip_contact_avatar_calls_list,VoipTheme.voip_contact_avatar_calls_list), textStyle: VoipTheme.call_generated_avatar_small)
let conferenceAvatar = UIImageView(image:UIImage(named:"voip_multiple_contacts_avatar"))
let displayName = StyledLabel(VoipTheme.call_list_active_name_font)
let sipAddress = StyledLabel(VoipTheme.call_list_active_sip_uri_font)
var menuButton : CallControlButton? = nil
var owningCallsListView : CallsListView? = nil
var callData: CallData? = nil {
didSet {
if let data = callData {
contentView.backgroundColor = data.isPaused.value == true ? VoipTheme.voip_calls_list_inactive_background : VoipTheme.voip_dark_gray
callStatusIcon.image =
data.isIncoming.value == true ? UIImage(named:"voip_call_header_incoming") :
data.isOutgoing.value == true ? UIImage(named:"voip_call_header_outgoing") :
data.isPaused.value == true ? UIImage(named:"voip_call_header_paused") :
UIImage(named:"voip_call_header_active")
if (data.isInRemoteConference.value == true) {
avatar.isHidden = true
conferenceAvatar.isHidden = false
displayName.text = data.remoteConferenceSubject.value
} else {
displayName.text = data.call.remoteAddress?.addressBookEnhancedDisplayName()
avatar.fillFromAddress(address: data.call.remoteAddress!)
avatar.isHidden = false
conferenceAvatar.isHidden = true
}
sipAddress.text = data.call.remoteAddress?.asStringUriOnly()
displayName.applyStyle(data.isPaused.value == true ? VoipTheme.call_list_name_font : VoipTheme.call_list_active_name_font)
sipAddress.applyStyle(data.isPaused.value == true ? VoipTheme.call_list_sip_uri_font : VoipTheme.call_list_active_sip_uri_font)
menuButton?.applyTintedIcons(tintedIcons: data.isPaused.value == true ? VoipTheme.voip_call_list_menu.tintableStateIcons : VoipTheme.voip_call_list_active_menu.tintableStateIcons)
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.height(cell_height).matchParentSideBorders().done()
contentView.addSubview(callStatusIcon)
callStatusIcon.size(w: call_status_icon_size, h: call_status_icon_size).done()
contentView.addSubview(avatar)
avatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done()
contentView.addSubview(conferenceAvatar)
conferenceAvatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done()
let nameAddress = UIView()
nameAddress.addSubview(displayName)
nameAddress.addSubview(sipAddress)
displayName.alignParentTop().done()
sipAddress.alignUnder(view: displayName).done()
contentView.addSubview(nameAddress)
nameAddress.toRightOf(avatar,withLeftMargin:texts_left_margin).toRightOf(conferenceAvatar,withLeftMargin:texts_left_margin).wrapContentY().centerY().done()
menuButton = CallControlButton(buttonTheme: VoipTheme.voip_call_list_active_menu, onClickAction: {
self.owningCallsListView?.toggleMenu(forCell: self)
})
contentView.addSubview(menuButton!)
menuButton!.size(w: side_menu_icon_size, h: side_menu_icon_size).alignParentRight().centerY().done()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,160 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class VoipCallContextMenu: UIStackView {
//Layout constants
static let item_height = 50.0
let width = 250.0
let margin_bw_items = 1.0
static let texts_margin_left = 10.0
let resume : ButtonWithStateBackgrounds
let pause : ButtonWithStateBackgrounds
let transfer : ButtonWithStateBackgrounds
let answer : ButtonWithStateBackgrounds
let terminate : ButtonWithStateBackgrounds
var callData: CallData? = nil {
didSet {
callData?.callState.readCurrentAndObserve(onChange: { (state) in
self.resume.isHidden = false
self.pause.isHidden = false
self.transfer.isHidden = false
self.answer.isHidden = false
self.terminate.isHidden = false
var count = 5.0
if let callData = self.callData {
if (callData.isPaused.value == true ||
callData.isIncoming.value == true ||
callData.isOutgoing.value == true ||
callData.isInRemoteConference.value == true
) {
self.pause.isHidden = true
count -= 1
}
if (callData.isIncoming.value == true ||
callData.isOutgoing.value == true ||
callData.isInRemoteConference.value == true
) {
self.resume.isHidden = true
self.transfer.isHidden = true
count -= 2
} else if (callData.isPaused.value == false) {
self.resume.isHidden = true
count -= 1
}
if (callData.isIncoming.value == false) {
count -= 1
self.answer.isHidden = true
}
self.size(w:self.width,h:count*VoipCallContextMenu.item_height).done()
}
})
}
}
init () {
resume = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_resume)
pause = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_pause)
transfer = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_transfer)
answer = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_answer)
terminate = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_hangup)
super.init(frame: .zero)
backgroundColor = .white
axis = .vertical
spacing = margin_bw_items
addArrangedSubview(resume)
addArrangedSubview(pause)
addArrangedSubview(transfer)
addArrangedSubview(answer)
addArrangedSubview(terminate)
resume.onClick {
self.isHidden = true
guard let call = self.callData?.call else { return }
if (CallManager.callKitEnabled()) {
CallManager.instance().setHeld(call:call,hold:false);
} else {
try?call.resume()
}
}
pause.onClick {
self.isHidden = true
guard let call = self.callData?.call else { return }
if (CallManager.callKitEnabled()) {
CallManager.instance().setHeld(call:call,hold:true);
} else {
try?call.pause()
}
}
transfer.onClick {
let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
view.setAddress("")
CallManager.instance().nextCallIsTransfer = true
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
}
answer.onClick {
self.isHidden = true
guard let call = self.callData?.call else { return }
if (CallManager.callKitEnabled()) {
CallManager.instance().acceptCall(call: call, hasVideo: false)
} else {
try?call.accept()
}
}
terminate.onClick {
self.isHidden = true
guard let call = self.callData?.call else { return }
try?call.terminate()
}
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
static func getButton(title:String) -> ButtonWithStateBackgrounds {
let button = ButtonWithStateBackgrounds(backgroundStateColors: VoipTheme.button_call_context_menu_background)
button.setTitle(title, for: .normal)
button.applyTitleStyle(VoipTheme.call_context_menu_item_font)
button.titleEdgeInsets = UIEdgeInsets(top: 0, left: texts_margin_left, bottom: 0, right: 0)
button.height(VoipCallContextMenu.item_height).done()
return button
}
}

View file

@ -1,121 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class VoipActiveSpeakerParticipantCell: UICollectionViewCell {
// Layout Constants
let corner_radius = 20.0
static let avatar_size = 80.0
let switch_camera_button_margins = 8.0
let switch_camera_button_size = 30
let videoView = UIView()
let avatar = Avatar(diameter:VoipGridParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
let displayName = StyledLabel(VoipTheme.conference_participant_name_font_as)
let pauseLabel = StyledLabel(VoipTheme.conference_participant_name_font_as,VoipTexts.conference_participant_paused)
var participantData: ConferenceParticipantDeviceData? = nil {
didSet {
if let data = participantData {
data.isInConference.readCurrentAndObserve { (isIn) in
self.updateBackground()
self.pause.isHidden = isIn == true
self.pauseLabel.isHidden = self.pause.isHidden
}
data.videoEnabled.readCurrentAndObserve { (videoEnabled) in
self.updateBackground()
if (videoEnabled == true) {
data.setVideoView(view: self.videoView)
self.avatar.isHidden = true
} else {
self.avatar.isHidden = false
}
self.switchCamera.isHidden = videoEnabled != true || !data.isSwitchCameraAvailable()
}
data.participantDevice.address.map {
avatar.fillFromAddress(address: $0)
if let displayName = $0.addressBookEnhancedDisplayName() {
self.displayName.text = displayName
}
}
}
}
}
func updateBackground() {
if let data = participantData {
if (data.isInConference.value != true) {
self.contentView.backgroundColor = VoipTheme.voip_conference_participant_paused_background
} else if (data.videoEnabled.value == true) {
self.contentView.backgroundColor = .black
} else {
self.contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
}
}
}
override init(frame:CGRect) {
super.init(frame:.zero)
layer.cornerRadius = corner_radius
clipsToBounds = true
contentView.addSubview(videoView)
videoView.matchParentDimmensions().done()
contentView.addSubview(avatar)
avatar.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
contentView.addSubview(pause)
pause.layer.cornerRadius = VoipGridParticipantCell.avatar_size/2
pause.clipsToBounds = true
pause.backgroundColor = VoipTheme.voip_gray
pause.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
contentView.addSubview(switchCamera)
switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done()
switchCamera.contentMode = .scaleAspectFit
switchCamera.onClick {
Core.get().toggleCamera()
}
contentView.addSubview(displayName)
displayName.matchParentSideBorders(insetedByDx:ActiveCallView.bottom_displayname_margin_left).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
contentView.addSubview(pauseLabel)
pauseLabel.toRightOf(displayName).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
contentView.matchParentDimmensions().done()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,260 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// Layout constants :
let inter_cell = 10.0
let record_pause_button_margin = 10.0
let duration_margin_top = 4.0
let record_pause_button_size = 40
let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
let grid_height = 150.0
let cell_width = 100.0
let subjectLabel = StyledLabel(VoipTheme.call_display_name_duration)
let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
let remotelyRecording = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
var recordCallButtons : [CallControlButton] = []
var pauseCallButtons : [CallControlButton] = []
let activeSpeakerView = UIView()
let activeSpeakerVideoView = UIView()
let activeSpeakerAvatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
let activeSpeakerDisplayName = StyledLabel(VoipTheme.call_remote_name)
var activeSpeakerMonitorTimer : Timer? = nil
var grid : UICollectionView
var conferenceViewModel: ConferenceViewModel? = nil {
didSet {
if let model = conferenceViewModel {
model.subject.readCurrentAndObserve { (subject) in
self.subjectLabel.text = subject
}
duration.conference = model.conference.value
self.remotelyRecording.isRemotelyRecorded = model.isRemotelyRecorded
model.conferenceParticipantDevices.readCurrentAndObserve { (_) in
self.grid.reloadData()
}
model.isConferencePaused.readCurrentAndObserve { (paused) in
self.pauseCallButtons.forEach {
$0.isSelected = paused == true
}
}
model.isRecording.readCurrentAndObserve { (selected) in
self.recordCallButtons.forEach {
$0.isSelected = selected == true
}
}
Core.get().nativeVideoWindow = self.activeSpeakerVideoView
activeSpeakerMonitorTimer?.invalidate()
activeSpeakerMonitorTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
var thereIsAnActiveSpeaker = false
model.conferenceParticipantDevices.value?.forEach { (data) in
if (data.activeSpeaker.value == true) {
thereIsAnActiveSpeaker = true
data.participantDevice.address.map {
self.activeSpeakerAvatar.isHidden = false
self.activeSpeakerAvatar.fillFromAddress(address: $0)
self.activeSpeakerDisplayName.text = $0.addressBookEnhancedDisplayName()
}
self.activeSpeakerVideoView.isHidden = data.videoEnabled.value != true
return
}
}
if (!thereIsAnActiveSpeaker) {
self.activeSpeakerAvatar.isHidden = true
self.activeSpeakerVideoView.isHidden = true
self.activeSpeakerDisplayName.text = VoipTexts.conference_display_no_active_speaker
}
}
} else {
activeSpeakerMonitorTimer?.invalidate()
}
self.grid.reloadData()
}
}
init() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width:cell_width, height:grid_height)
grid = UICollectionView(frame:.zero, collectionViewLayout: layout)
super.init(frame: .zero)
let headerView = UIStackView()
addSubview(headerView)
headerView.matchParentSideBorders().alignParentTop().done()
headerView.distribution = .equalSpacing
headerView.alignment = .bottom
headerView.spacing = record_pause_button_margin
headerView.axis = .vertical
let subjectDuration = UIView()
subjectDuration.addSubview(subjectLabel)
subjectLabel.alignParentLeft().done()
subjectDuration.addSubview(duration)
duration.alignParentLeft().alignUnder(view: subjectLabel,withMargin:duration_margin_top).done()
let upperSection = UIStackView()
upperSection.distribution = .equalSpacing
upperSection.alignment = .center
upperSection.spacing = record_pause_button_margin
upperSection.axis = .horizontal
upperSection.addArrangedSubview(subjectDuration)
subjectDuration.wrapContentY().done()
// Record (with video)
let recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
self.conferenceViewModel?.toggleRecording()
})
let recordPauseView = UIStackView()
recordPauseView.spacing = record_pause_button_margin
recordCallButtons.append(recordCall)
recordPauseView.addArrangedSubview(recordCall)
// Pause (with video)
let pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
self.conferenceViewModel?.togglePlayPause()
})
pauseCallButtons.append(pauseCall)
recordPauseView.addArrangedSubview(pauseCall)
upperSection.addArrangedSubview(recordPauseView)
headerView.addArrangedSubview(upperSection)
upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
headerView.addArrangedSubview(remotelyRecording)
remotelyRecording.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
// Container view that can toggle full screen by ckick
let fullScreenMutableView = UIView()
addSubview(fullScreenMutableView)
fullScreenMutableView.backgroundColor = VoipTheme.voipBackgroundColor.get()
fullScreenMutableView.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
// Active speaker
fullScreenMutableView.addSubview(activeSpeakerView)
activeSpeakerView.layer.cornerRadius = ActiveCallView.center_view_corner_radius
activeSpeakerView.clipsToBounds = true
activeSpeakerView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
activeSpeakerView.matchParentSideBorders().alignParentTop().done()
activeSpeakerView.addSubview(activeSpeakerAvatar)
activeSpeakerAvatar.square(Avatar.diameter_for_call_views).center().done()
activeSpeakerView.addSubview(activeSpeakerVideoView)
activeSpeakerVideoView.matchParentDimmensions().done()
activeSpeakerView.addSubview(activeSpeakerDisplayName)
activeSpeakerDisplayName.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentRight().alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
// CollectionView
grid.dataSource = self
grid.delegate = self
grid.register(VoipActiveSpeakerParticipantCell.self, forCellWithReuseIdentifier: "VoipActiveSpeakerParticipantCell")
grid.backgroundColor = .clear
grid.isScrollEnabled = true
fullScreenMutableView.addSubview(grid)
grid.matchParentSideBorders().height(grid_height).alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done()
// Full screen video togggle
activeSpeakerView.onClick {
ControlsViewModel.shared.toggleFullScreen()
}
ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in
if (self.isHidden) {
return
}
fullScreenMutableView.removeConstraints().done()
if (fullScreen == true) {
fullScreenMutableView.removeFromSuperview()
PhoneMainView.instance().mainViewController.view?.addSubview(fullScreenMutableView)
fullScreenMutableView.matchParentDimmensions().done()
} else {
fullScreenMutableView.removeFromSuperview()
self.addSubview(fullScreenMutableView)
fullScreenMutableView.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
}
}
}
// UICollectionView related delegates
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return inter_cell
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return inter_cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
return .zero
}
return participantsCount
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:VoipActiveSpeakerParticipantCell = collectionView.dequeueReusableCell(withReuseIdentifier: "VoipActiveSpeakerParticipantCell", for: indexPath) as! VoipActiveSpeakerParticipantCell
guard let participantData = conferenceViewModel?.conferenceParticipantDevices.value?[indexPath.row] else {
return cell
}
cell.participantData = participantData
return cell
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,140 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
@objc class VoipConferenceDisplayModeSelectionView: DismissableView, UITableViewDataSource, UITableViewDelegate{
// Layout constants
let buttons_distance_from_center_x = 38
let buttons_size = 60
let optionsListView = UITableView()
init() {
super.init(title: VoipTexts.call_action_change_conf_layout)
super.contentView.addSubview(optionsListView)
optionsListView.matchParentDimmensions().done()
optionsListView.dataSource = self
optionsListView.delegate = self
optionsListView.register(ConferenceDisplayModeSelectionCell.self, forCellReuseIdentifier: "ConferenceDisplayModeSelectionCell")
optionsListView.separatorStyle = .singleLine
optionsListView.separatorColor = VoipTheme.light_grey_color
optionsListView.isScrollEnabled = false
}
// TableView datasource delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ConferenceDisplayModeSelectionCell = tableView.dequeueReusableCell(withIdentifier: "ConferenceDisplayModeSelectionCell") as! ConferenceDisplayModeSelectionCell
cell.selectionStyle = .none
if (indexPath.row == 0) {
cell.setOption(title: VoipTexts.conference_display_mode_mosaic, onSelectAction: {
ConferenceViewModel.shared.conference.value?.layout = .Grid
ConferenceViewModel.shared.conferenceDisplayMode.value = .Grid
}, image:(UIImage(named: "voip_conference_mosaic")?.tinted(with: VoipTheme.voipDrawableColor.get())!)!)
cell.isUserInteractionEnabled = ConferenceViewModel.shared.conferenceParticipantDevices.value!.count <= ConferenceViewModel.shared.maxParticipantsForMosaicLayout
}
if (indexPath.row == 1) {
cell.setOption(title: VoipTexts.conference_display_mode_active_speaker, onSelectAction: {
ConferenceViewModel.shared.conference.value?.layout = .ActiveSpeaker
ConferenceViewModel.shared.conferenceDisplayMode.value = .ActiveSpeaker
}, image:(UIImage(named: "voip_conference_active_speaker")?.tinted(with: VoipTheme.voipDrawableColor.get())!)!)
cell.isUserInteractionEnabled = true
}
cell.separatorInset = .zero
cell.selectionStyle = .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ConferenceDisplayModeSelectionCell
cell.onSelectAction?()
cell.isSelected = true
if (indexPath.row == 0) {
tableView.deselectRow(at: IndexPath(row: 1, section: 0), animated: false)
}
if (indexPath.row == 1) {
tableView.deselectRow(at: IndexPath(row: 0, section: 0), animated: false)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ConferenceDisplayModeSelectionCell
cell.isSelected = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ConferenceDisplayModeSelectionCell : UITableViewCell {
let cell_height = 60.0
let icon_size = 40.0
let side_margins = 20.0
let radio = CallControlButton(buttonTheme: VoipTheme.radio_button)
let label = StyledLabel(VoipTheme.conference_mode_title)
let icon = UIImageView()
var onSelectAction : (()->Void)? = nil
override var isSelected: Bool {
didSet {
radio.isSelected = isSelected
label.applyStyle(isSelected ? VoipTheme.conference_mode_title_selected : VoipTheme.conference_mode_title)
}
}
func setOption(title:String, onSelectAction:@escaping ()->Void, image:UIImage) {
self.onSelectAction = onSelectAction
label.text = title
icon.image = image
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.height(cell_height).matchParentSideBorders().done()
contentView.addSubview(radio)
radio.alignParentLeft(withMargin: side_margins).centerY().done()
contentView.addSubview(label)
label.toRightOf(radio).centerY().done()
contentView.addSubview(icon)
icon.size(w: icon_size, h: icon_size).alignParentRight(withMargin: side_margins).centerY().done()
radio.isUserInteractionEnabled = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,242 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// Layout constants :
let inter_cell = 10.0
let record_pause_button_margin = 10.0
let duration_margin_top = 4.0
let record_pause_button_size = 40
let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
let subjectLabel = StyledLabel(VoipTheme.call_display_name_duration)
let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
let remotelyRecording = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
var recordCallButtons : [CallControlButton] = []
var pauseCallButtons : [CallControlButton] = []
var grid : UICollectionView
var conferenceViewModel: ConferenceViewModel? = nil {
didSet {
if let model = conferenceViewModel {
model.subject.readCurrentAndObserve { (subject) in
self.subjectLabel.text = subject
}
duration.conference = model.conference.value
self.remotelyRecording.isRemotelyRecorded = model.isRemotelyRecorded
model.conferenceParticipantDevices.readCurrentAndObserve { (_) in
self.grid.reloadData()
}
model.isConferencePaused.readCurrentAndObserve { (paused) in
self.pauseCallButtons.forEach {
$0.isSelected = paused == true
}
}
model.isRecording.readCurrentAndObserve { (selected) in
self.recordCallButtons.forEach {
$0.isSelected = selected == true
}
}
}
self.grid.reloadData()
}
}
init() {
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
grid = UICollectionView(frame:.zero, collectionViewLayout: layout)
super.init(frame: .zero)
let headerView = UIStackView()
addSubview(headerView)
headerView.distribution = .equalSpacing
headerView.alignment = .bottom
headerView.spacing = record_pause_button_margin
headerView.axis = .vertical
let subjectDuration = UIView()
subjectDuration.addSubview(subjectLabel)
subjectLabel.alignParentLeft().done()
subjectDuration.addSubview(duration)
duration.alignParentLeft().alignUnder(view: subjectLabel,withMargin:duration_margin_top).done()
let upperSection = UIStackView()
upperSection.distribution = .equalSpacing
upperSection.alignment = .center
upperSection.spacing = record_pause_button_margin
upperSection.axis = .horizontal
upperSection.addArrangedSubview(subjectDuration)
subjectDuration.wrapContentY().done()
// Record (with video)
let recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
self.conferenceViewModel?.toggleRecording()
})
let recordPauseView = UIStackView()
recordPauseView.spacing = record_pause_button_margin
recordCallButtons.append(recordCall)
recordPauseView.addArrangedSubview(recordCall)
// Pause (with video)
let pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
self.conferenceViewModel?.togglePlayPause()
})
pauseCallButtons.append(pauseCall)
recordPauseView.addArrangedSubview(pauseCall)
upperSection.addArrangedSubview(recordPauseView)
headerView.addArrangedSubview(upperSection)
upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
headerView.addArrangedSubview(remotelyRecording)
remotelyRecording.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
// CollectionView
grid.dataSource = self
grid.delegate = self
grid.register(VoipGridParticipantCell.self, forCellWithReuseIdentifier: "VoipGridParticipantCell")
grid.backgroundColor = VoipTheme.voipBackgroundColor.get()
grid.isScrollEnabled = false
addSubview(grid)
grid.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
headerView.matchParentSideBorders().alignParentTop().done()
// Full screen video togggle
grid.onClick {
ControlsViewModel.shared.toggleFullScreen()
}
ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in
if (self.isHidden) {
return
}
self.grid.removeConstraints().done()
if (fullScreen == true) {
self.grid.removeFromSuperview()
PhoneMainView.instance().mainViewController.view?.addSubview(self.grid)
self.grid.matchParentDimmensions().center().done()
self.grid.reloadData() // Cauz of the frames
} else {
self.grid.removeFromSuperview()
self.addSubview(self.grid)
self.grid.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
self.grid.reloadData()
}
}
}
// UICollectionView related delegates
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return inter_cell
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return inter_cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
return .zero
}
return participantsCount
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:VoipGridParticipantCell = collectionView.dequeueReusableCell(withReuseIdentifier: "VoipGridParticipantCell", for: indexPath) as! VoipGridParticipantCell
guard let participantData = conferenceViewModel?.conferenceParticipantDevices.value?[indexPath.row] else {
return cell
}
cell.participantData = participantData
return cell
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
return .zero
}
var cellSize : CGSize = .zero
let availableSize = collectionView.frame.size
if (participantsCount == 1) {
cellSize = availableSize
} else if (participantsCount == 2) {
cellSize = CGSize(width:availableSize.width, height:availableSize.height/2)
cellSize.height -= inter_cell/2
} else if (participantsCount == 3) {
cellSize = CGSize(width:availableSize.width, height:availableSize.height/3)
cellSize.height -= 2*inter_cell/3
} else if (participantsCount == 4) {
cellSize = CGSize(width:availableSize.width/2, height:availableSize.height/2)
cellSize.height -= inter_cell/2
cellSize.width -= inter_cell/2
} else if (participantsCount == 5) {
if (indexPath.row == 4) { // last (local) participant takes full width (under discussion)
cellSize = CGSize(width:availableSize.width, height:availableSize.height/3)
} else {
cellSize = CGSize(width:availableSize.width/2, height:availableSize.height/3)
cellSize.width -= inter_cell/2
}
cellSize.height -= 2*inter_cell/3
} else {
cellSize = CGSize(width:availableSize.width/2, height:availableSize.height/CGFloat((participantsCount/2)))
cellSize.height -= 2*inter_cell/3
cellSize.width -= inter_cell/2
}
return cellSize
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,129 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import SnapKit
import linphonesw
class VoipGridParticipantCell: UICollectionViewCell {
// Layout Constants
let corner_radius = 20.0
static let avatar_size = 80.0
let switch_camera_button_margins = 8.0
let switch_camera_button_size = 30
let videoView = UIView()
let avatar = Avatar(diameter:VoipGridParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
let displayName = StyledLabel(VoipTheme.conference_participant_name_font_grid)
let pauseLabel = StyledLabel(VoipTheme.conference_participant_name_font_grid,VoipTexts.conference_participant_paused)
var participantData: ConferenceParticipantDeviceData? = nil {
didSet {
if let data = participantData {
data.isInConference.readCurrentAndObserve { (isIn) in
self.updateBackground()
self.pause.isHidden = isIn == true
self.pauseLabel.isHidden = self.pause.isHidden
}
data.videoEnabled.readCurrentAndObserve { (videoEnabled) in
self.updateBackground()
if (videoEnabled == true) {
data.setVideoView(view: self.videoView)
self.avatar.isHidden = true
} else {
self.avatar.isHidden = false
}
self.switchCamera.isHidden = videoEnabled != true || !data.isSwitchCameraAvailable()
}
data.participantDevice.address.map {
avatar.fillFromAddress(address: $0)
if let displayName = $0.addressBookEnhancedDisplayName() {
self.displayName.text = displayName
}
}
data.activeSpeaker.readCurrentAndObserve { (active) in
if (active == true) {
self.layer.borderWidth = 2
} else {
self.layer.borderWidth = 0
}
}
}
}
}
func updateBackground() {
if let data = participantData {
if (data.isInConference.value != true) {
self.contentView.backgroundColor = VoipTheme.voip_conference_participant_paused_background
} else if (data.videoEnabled.value == true) {
self.contentView.backgroundColor = .black
} else {
self.contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
}
}
}
override init(frame:CGRect) {
super.init(frame:.zero)
layer.cornerRadius = corner_radius
clipsToBounds = true
layer.borderColor = VoipTheme.primary_color.cgColor
contentView.addSubview(videoView)
videoView.matchParentDimmensions().done()
contentView.addSubview(avatar)
avatar.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
contentView.addSubview(pause)
pause.layer.cornerRadius = VoipGridParticipantCell.avatar_size/2
pause.clipsToBounds = true
pause.backgroundColor = VoipTheme.voip_gray
pause.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
contentView.addSubview(switchCamera)
switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done()
switchCamera.contentMode = .scaleAspectFit
switchCamera.onClick {
Core.get().toggleCamera()
}
contentView.addSubview(displayName)
displayName.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
contentView.addSubview(pauseLabel)
pauseLabel.toRightOf(displayName).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
contentView.matchParentDimmensions().done()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,109 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class ControlsView: UIStackView {
// Layout constants
static let controls_button_spacing = 5.0
init (showVideo:Bool) {
super.init(frame: .zero)
axis = .horizontal
distribution = .equalSpacing
alignment = .center
spacing = ControlsView.controls_button_spacing
// Mute
let mute = CallControlButton(buttonTheme: VoipTheme.call_mute, onClickAction: {
ControlsViewModel.shared.toggleMuteMicrophone()
})
addArrangedSubview(mute)
ControlsViewModel.shared.isMicrophoneMuted.readCurrentAndObserve { (muted) in
mute.isSelected = muted == true
}
ControlsViewModel.shared.isMuteMicrophoneEnabled.readCurrentAndObserve { (enabled) in
mute.isEnabled = enabled == true
}
// Speaker
let speaker = CallControlButton(buttonTheme: VoipTheme.call_speaker, onClickAction: {
ControlsViewModel.shared.toggleSpeaker()
})
addArrangedSubview(speaker)
ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (selected) in
speaker.isSelected = selected == true
}
// Audio routes
let routes = CallControlButton(buttonTheme: VoipTheme.call_audio_route, onClickAction: {
ControlsViewModel.shared.toggleRoutesMenu()
})
addArrangedSubview(routes)
ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (selected) in
routes.isSelected = selected == true
}
ControlsViewModel.shared.audioRoutesEnabled.readCurrentAndObserve { (routesEnabled) in
speaker.isHidden = routesEnabled == true
routes.isHidden = !speaker.isHidden
}
// Video
if (showVideo) {
let video = CallControlButton(buttonTheme: VoipTheme.call_video, onClickAction: {
if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
ControlsViewModel.shared.toggleVideo()
} else {
AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
if granted {
ControlsViewModel.shared.toggleVideo()
} else {
VoipDialog(message:VoipTexts.camera_required_for_video).show()
}
})
}
})
addArrangedSubview(video)
video.showActivityIndicatorDataSource = ControlsViewModel.shared.isVideoUpdateInProgress
ControlsViewModel.shared.isVideoEnabled.readCurrentAndObserve { (selected) in
video.isSelected = selected == true
}
ControlsViewModel.shared.isVideoAvailable.readCurrentAndObserve { (available) in
video.isEnabled = available == true && ControlsViewModel.shared.isVideoUpdateInProgress.value != true
}
ControlsViewModel.shared.isVideoUpdateInProgress.readCurrentAndObserve { (updateInProgress) in
video.isEnabled = updateInProgress != true && ControlsViewModel.shared.isVideoAvailable.value == true
}
}
height(CallControlButton.default_size).done()
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -1,61 +0,0 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
class DismissableView: UIView {
// Layout constants
let header_height = 60.0
let title_left_margin = 20
let dismiss_right_margin = 10
let dismiss_icon_inset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let headerView = UIView()
let contentView = UIView()
var dismiss : CallControlButton? = nil
init(title:String) {
super.init(frame:.zero)
headerView.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get()
self.addSubview(headerView)
headerView.matchParentSideBorders().alignParentTop().height(header_height).done()
dismiss = CallControlButton(imageInset:dismiss_icon_inset,buttonTheme: VoipTheme.voip_cancel, onClickAction: {
self.removeFromSuperview()
})
headerView.addSubview(dismiss!)
dismiss?.alignParentRight(withMargin: dismiss_right_margin).centerY().done()
let title = StyledLabel(VoipTheme.calls_list_header_font,title)
headerView.addSubview(title)
title.alignParentTop().alignParentLeft(withMargin: title_left_margin).centerY().done()
self.addSubview(contentView)
contentView.alignUnder(view: headerView).matchParentSideBorders().alignParentBottom().done()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Some files were not shown because too many files have changed in this diff Show more