forked from mirrors/linphone-iphone
Revert "Video conference and new Call Views"
This reverts commit 608577034d.
This commit is contained in:
parent
e23a4a7951
commit
306162228e
198 changed files with 8070 additions and 7792 deletions
52
Classes/AudioHelper.m
Normal file
52
Classes/AudioHelper.m
Normal 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
|
||||
317
Classes/Base.lproj/CallIncomingView.xib
Normal file
317
Classes/Base.lproj/CallIncomingView.xib
Normal 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>
|
||||
623
Classes/Base.lproj/CallOutgoingView.xib
Normal file
623
Classes/Base.lproj/CallOutgoingView.xib
Normal 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>
|
||||
1857
Classes/Base.lproj/CallView.xib
Normal file
1857
Classes/Base.lproj/CallView.xib
Normal file
File diff suppressed because it is too large
Load diff
1397
Classes/Base.lproj/CallView~ipad.xib
Normal file
1397
Classes/Base.lproj/CallView~ipad.xib
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
65
Classes/CallConferenceTableView.m
Normal file
65
Classes/CallConferenceTableView.m
Normal 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
|
||||
54
Classes/CallIncomingView.h
Normal file
54
Classes/CallIncomingView.h
Normal 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
150
Classes/CallIncomingView.m
Normal 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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
61
Classes/CallOutgoingView.h
Normal file
61
Classes/CallOutgoingView.h
Normal 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
267
Classes/CallOutgoingView.m
Normal 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
|
||||
|
|
@ -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
|
||||
104
Classes/CallPausedTableView.m
Normal file
104
Classes/CallPausedTableView.m
Normal 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
|
||||
|
|
@ -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
230
Classes/CallSideMenuView.m
Normal 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
|
||||
49
Classes/CallSideMenuView.xib
Normal file
49
Classes/CallSideMenuView.xib
Normal 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
117
Classes/CallView.h
Normal 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
1006
Classes/CallView.m
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
#import "UICompositeView.h"
|
||||
#import "UIRoundBorderedButton.h"
|
||||
#import "UIChatBubbleTextCell.h"
|
||||
|
||||
@interface ChatConversationImdnView : UIViewController <UICompositeViewDelegate, UITableViewDelegate, UITableViewDataSource>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#import "UIImageViewDeletable.h"
|
||||
#import "UIConfirmationDialog.h"
|
||||
#import "UIInterfaceStyleButton.h"
|
||||
#import "linphoneapp-Swift.h"
|
||||
#import "UIChatReplyBubbleView.h"
|
||||
#include "linphone/linphonecore.h"
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@
|
|||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)displayName {
|
||||
if (_friend) {
|
||||
const char *friend_name = linphone_friend_get_name(_friend);
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@
|
|||
#include "linphone/lpconfig.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#import "linphoneapp-Swift.h"
|
||||
|
||||
|
||||
@implementation LinphoneCoreSettingsStore
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
76
Classes/LinphoneUI/Base.lproj/UICallConferenceCell.xib
Normal file
76
Classes/LinphoneUI/Base.lproj/UICallConferenceCell.xib
Normal 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>
|
||||
80
Classes/LinphoneUI/Base.lproj/UICallPausedCell.xib
Normal file
80
Classes/LinphoneUI/Base.lproj/UICallPausedCell.xib
Normal 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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@
|
|||
*/
|
||||
|
||||
#import "UIBluetoothButton.h"
|
||||
#import "../Utils/AudioHelper.h"
|
||||
#import "Utils.h"
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
#include "linphone/linphonecore.h"
|
||||
|
||||
@implementation UIBluetoothButton
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@
|
|||
#import "LinphoneManager.h"
|
||||
|
||||
#import <CoreTelephony/CTCallCenter.h>
|
||||
#import "linphoneapp-Swift.h"
|
||||
|
||||
|
||||
@implementation UICallButton
|
||||
|
||||
|
|
|
|||
37
Classes/LinphoneUI/UICallConferenceCell.h
Normal file
37
Classes/LinphoneUI/UICallConferenceCell.h
Normal 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
|
||||
69
Classes/LinphoneUI/UICallConferenceCell.m
Normal file
69
Classes/LinphoneUI/UICallConferenceCell.m
Normal 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
|
||||
|
|
@ -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
|
||||
60
Classes/LinphoneUI/UICallPausedCell.m
Normal file
60
Classes/LinphoneUI/UICallPausedCell.m
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@
|
|||
#import <AssetsLibrary/ALAsset.h>
|
||||
#import <AssetsLibrary/ALAssetRepresentation.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import "linphoneapp-Swift.h"
|
||||
|
||||
|
||||
@implementation UIChatBubbleTextCell
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
111
Classes/LinphoneUI/UIHangUpButton.m
Normal file
111
Classes/LinphoneUI/UIHangUpButton.m
Normal 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
|
||||
|
|
@ -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
|
||||
180
Classes/LinphoneUI/UIPauseButton.m
Normal file
180
Classes/LinphoneUI/UIPauseButton.m
Normal 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
|
||||
|
|
@ -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
|
||||
61
Classes/LinphoneUI/UISpeakerButton.m
Normal file
61
Classes/LinphoneUI/UISpeakerButton.m
Normal 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
|
||||
29
Classes/LinphoneUI/UIVideoButton.h
Normal file
29
Classes/LinphoneUI/UIVideoButton.h
Normal 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
|
||||
114
Classes/LinphoneUI/UIVideoButton.m
Normal file
114
Classes/LinphoneUI/UIVideoButton.m
Normal 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
|
||||
BIN
Classes/LinphoneUI/fr.lproj/UICallConferenceCell.strings
Normal file
BIN
Classes/LinphoneUI/fr.lproj/UICallConferenceCell.strings
Normal file
Binary file not shown.
BIN
Classes/LinphoneUI/fr.lproj/UICallPausedCell.strings
Normal file
BIN
Classes/LinphoneUI/fr.lproj/UICallPausedCell.strings
Normal file
Binary file not shown.
BIN
Classes/LinphoneUI/hu.lproj/UICallConferenceCell.strings
Normal file
BIN
Classes/LinphoneUI/hu.lproj/UICallConferenceCell.strings
Normal file
Binary file not shown.
BIN
Classes/LinphoneUI/hu.lproj/UICallPausedCell.strings
Normal file
BIN
Classes/LinphoneUI/hu.lproj/UICallPausedCell.strings
Normal file
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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())>"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 :"")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
@ -21,8 +21,6 @@
|
|||
#import "LinphoneManager.h"
|
||||
#import "PhoneMainView.h"
|
||||
#import "Utils.h"
|
||||
#import "linphoneapp-Swift.h"
|
||||
|
||||
|
||||
@interface FileTransferDelegate ()
|
||||
@property(strong) NSMutableData *data;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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:"")
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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
Loading…
Add table
Reference in a new issue