Merge new UI for Call and Conferences

This commit is contained in:
Christophe Deschamps 2022-06-15 17:33:36 +02:00
commit 524d429960
265 changed files with 11977 additions and 7957 deletions

View file

@ -7,13 +7,13 @@ variables:
job-ios: job-ios:
stage: build stage: build
tags: [ "macosx-xcode12" ] tags: [ "macmini-m1-xcode13" ]
script: script:
- pod install --repo-update - pod install --repo-update
- pwd - pwd
- xcodebuild archive -scheme $archive_scheme -archivePath ./$archive_path -configuration Release -workspace ./linphone.xcworkspace -UseModernBuildSystem=NO - xcodebuild archive -scheme $archive_scheme -archivePath ./$archive_path -configuration Release -workspace ./linphone.xcworkspace -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
- xcodebuild -exportArchive -archivePath ./$archive_path -exportPath ./$export_path -exportOptionsPlist ./$export_options_plist -allowProvisioningUpdates -UseModernBuildSystem=NO - xcodebuild -exportArchive -archivePath ./$archive_path -exportPath ./$export_path -exportOptionsPlist ./$export_options_plist -allowProvisioningUpdates -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
artifacts: artifacts:

View file

@ -1,52 +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 "AudioHelper.h"
@implementation AudioHelper
+ (NSArray *)bluetoothRoutes {
return @[AVAudioSessionPortBluetoothHFP, AVAudioSessionPortCarAudio, AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE ];
}
+ (AVAudioSessionPortDescription *)bluetoothAudioDevice {
return [AudioHelper audioDeviceFromTypes:[AudioHelper bluetoothRoutes]];
}
+ (AVAudioSessionPortDescription *)builtinAudioDevice {
NSArray *builtinRoutes = @[ AVAudioSessionPortBuiltInMic ];
return [AudioHelper audioDeviceFromTypes:builtinRoutes];
}
+ (AVAudioSessionPortDescription *)speakerAudioDevice {
NSArray *builtinRoutes = @[ AVAudioSessionPortBuiltInSpeaker ];
return [AudioHelper audioDeviceFromTypes:builtinRoutes];
}
+ (AVAudioSessionPortDescription *)audioDeviceFromTypes:(NSArray *)types {
NSArray *routes = [[AVAudioSession sharedInstance] availableInputs];
for (AVAudioSessionPortDescription *route in routes) {
if ([types containsObject:route.portType]) {
return route;
}
}
return nil;
}
@end

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -21,7 +21,6 @@
<outlet property="ephemeralndicator" destination="LN7-ci-kNn" id="hqU-98-qkJ"/> <outlet property="ephemeralndicator" destination="LN7-ci-kNn" id="hqU-98-qkJ"/>
<outlet property="imagesCollectionView" destination="JGQ-p2-HCX" id="6dt-1f-jpa"/> <outlet property="imagesCollectionView" destination="JGQ-p2-HCX" id="6dt-1f-jpa"/>
<outlet property="imagesView" destination="3qd-ys-t2L" id="f9L-FU-PMI"/> <outlet property="imagesView" destination="3qd-ys-t2L" id="f9L-FU-PMI"/>
<outlet property="infoButton" destination="Vqb-Un-4xv" id="pa1-Iz-5QQ"/>
<outlet property="landscapeView" destination="VoU-7Q-fgp" id="iRJ-sh-thF"/> <outlet property="landscapeView" destination="VoU-7Q-fgp" id="iRJ-sh-thF"/>
<outlet property="listSwipeGestureRecognizer" destination="dzw-n4-l9i" id="JVP-Vl-lIa"/> <outlet property="listSwipeGestureRecognizer" destination="dzw-n4-l9i" id="JVP-Vl-lIa"/>
<outlet property="listTapGestureRecognizer" destination="tkk-Tm-A7C" id="gqU-iJ-RGm"/> <outlet property="listTapGestureRecognizer" destination="tkk-Tm-A7C" id="gqU-iJ-RGm"/>
@ -185,22 +184,6 @@
<action selector="onSelectionToggle:" destination="29" eventType="touchUpInside" id="eP5-bU-LEA"/> <action selector="onSelectionToggle:" destination="29" eventType="touchUpInside" id="eP5-bU-LEA"/>
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Vqb-Un-4xv" userLabel="infoButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="248" y="0.0" width="83" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Select all"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
<state key="normal" image="chat_group_informations.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="select_all_disabled.png"/>
<state key="selected" image="select_all_default.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
<connections>
<action selector="onInfoClick:" destination="-1" eventType="touchUpInside" id="VfD-K7-V15"/>
</connections>
</button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="addresses" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ncq-Zc-X6j" userLabel="participantsLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="addresses" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ncq-Zc-X6j" userLabel="participantsLabel">
<rect key="frame" x="75" y="36" width="160" height="25"/> <rect key="frame" x="75" y="36" width="160" height="25"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
@ -507,22 +490,6 @@
<action selector="onToggleMenu:" destination="-1" eventType="touchUpInside" id="vYL-BQ-UTF"/> <action selector="onToggleMenu:" destination="-1" eventType="touchUpInside" id="vYL-BQ-UTF"/>
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="uVy-PC-gn9" userLabel="infoButton" customClass="UIIconButton">
<rect key="frame" x="248" y="0.0" width="37" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Select all"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
<state key="normal" image="chat_group_informations.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="select_all_disabled.png"/>
<state key="selected" image="select_all_default.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
<connections>
<action selector="onInfoClick:" destination="-1" eventType="touchUpInside" id="HQo-52-fEq"/>
</connections>
</button>
<button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wag-QV-oUD" userLabel="callButton" customClass="UIIconButton"> <button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wag-QV-oUD" userLabel="callButton" customClass="UIIconButton">
<rect key="frame" x="248" y="0.0" width="37" height="66"/> <rect key="frame" x="248" y="0.0" width="37" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
@ -721,7 +688,6 @@
<image name="chat_attachment_default.png" width="65.599998474121094" height="65.599998474121094"/> <image name="chat_attachment_default.png" width="65.599998474121094" height="65.599998474121094"/>
<image name="chat_attachment_disabled.png" width="65.599998474121094" height="65.599998474121094"/> <image name="chat_attachment_disabled.png" width="65.599998474121094" height="65.599998474121094"/>
<image name="chat_attachment_over.png" width="65.599998474121094" height="65.599998474121094"/> <image name="chat_attachment_over.png" width="65.599998474121094" height="65.599998474121094"/>
<image name="chat_group_informations.png" width="38" height="38"/>
<image name="chat_send_default.png" width="65.599998474121094" height="65.599998474121094"/> <image name="chat_send_default.png" width="65.599998474121094" height="65.599998474121094"/>
<image name="chat_send_disabled.png" width="65.599998474121094" height="65.599998474121094"/> <image name="chat_send_disabled.png" width="65.599998474121094" height="65.599998474121094"/>
<image name="chat_send_over.png" width="65.599998474121094" height="65.599998474121094"/> <image name="chat_send_over.png" width="65.599998474121094" height="65.599998474121094"/>
@ -731,7 +697,7 @@
<image name="delete_disabled.png" width="34.400001525878906" height="44.799999237060547"/> <image name="delete_disabled.png" width="34.400001525878906" height="44.799999237060547"/>
<image name="deselect_all.png" width="43.200000762939453" height="43.200000762939453"/> <image name="deselect_all.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/> <image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="more_menu_default.png" width="7.1999998092651367" height="9.3599996566772461"/> <image name="more_menu_default.png" width="118.40000152587891" height="118.40000152587891"/>
<image name="security_1_indicator.png" width="27.5" height="32.5"/> <image name="security_1_indicator.png" width="27.5" height="32.5"/>
<image name="select_all_default.png" width="43.200000762939453" height="43.200000762939453"/> <image name="select_all_default.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="select_all_disabled.png" width="43.200000762939453" height="43.200000762939453"/> <image name="select_all_disabled.png" width="43.200000762939453" height="43.200000762939453"/>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>

View file

@ -1,13 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <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"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="HistoryListView"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="HistoryListView">
<connections> <connections>
<outlet property="allButton" destination="4" id="27"/> <outlet property="allButton" destination="4" id="27"/>
<outlet property="conferenceButton" destination="VWZ-Nd-W2s" id="wy8-oW-FqP"/>
<outlet property="missedButton" destination="5" id="28"/> <outlet property="missedButton" destination="5" id="28"/>
<outlet property="selectedButtonImage" destination="o8E-gw-vhI" id="hNf-FA-7aQ"/> <outlet property="selectedButtonImage" destination="o8E-gw-vhI" id="hNf-FA-7aQ"/>
<outlet property="tableController" destination="18" id="26"/> <outlet property="tableController" destination="18" id="26"/>
@ -29,7 +33,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="38" userLabel="switchView"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="38" userLabel="switchView">
<rect key="frame" x="0.0" y="0.0" width="150" height="66"/> <rect key="frame" x="0.0" y="0.0" width="225" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4" userLabel="allButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4" userLabel="allButton" customClass="UIInterfaceStyleButton">
@ -49,7 +53,7 @@
</button> </button>
<button opaque="NO" contentMode="bottom" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5" userLabel="missedButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" contentMode="bottom" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5" userLabel="missedButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="75" y="0.0" width="75" height="66"/> <rect key="frame" x="75" y="0.0" width="75" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Missed contacts filter"/> <accessibility key="accessibilityConfiguration" label="Missed contacts filter"/>
<state key="normal" image="history_missed_default.png"> <state key="normal" image="history_missed_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -61,6 +65,21 @@
<action selector="onMissedClick:" destination="-1" eventType="touchUpInside" id="30"/> <action selector="onMissedClick:" destination="-1" eventType="touchUpInside" id="30"/>
</connections> </connections>
</button> </button>
<button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VWZ-Nd-W2s" userLabel="conferenceButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="150" y="0.0" width="75" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Missed contacts filter"/>
<inset key="imageEdgeInsets" minX="10" minY="5" maxX="10" maxY="5"/>
<state key="normal" image="voip_conference_new.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<state key="disabled" image="history_missed_disabled.png"/>
<state key="selected" image="voip_conference_new_selected.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
<connections>
<action selector="onConferenceClick:" destination="-1" eventType="touchUpInside" id="9hJ-lr-NB7"/>
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="o8E-gw-vhI" userLabel="selectedButtonImage"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="o8E-gw-vhI" userLabel="selectedButtonImage">
<rect key="frame" x="0.0" y="63" width="75" height="3"/> <rect key="frame" x="0.0" y="63" width="75" height="3"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES"/>
@ -127,12 +146,12 @@
</connections> </connections>
</button> </button>
</subviews> </subviews>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="35" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="17" userLabel="tableView"> <tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="35" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="17" userLabel="tableView">
<rect key="frame" x="0.0" y="66" width="375" height="493"/> <rect key="frame" x="0.0" y="66" width="375" height="493"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<inset key="scrollIndicatorInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/> <inset key="scrollIndicatorInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/>
<color key="separatorColor" red="0.67030966281890869" green="0.71867996454238892" blue="0.75078284740447998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="separatorColor" red="0.67030966281890869" green="0.71867996454238892" blue="0.75078284740447998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections> <connections>
@ -143,7 +162,7 @@
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No call in your history" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xtr-Fp-60Z" userLabel="emptyTableLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No call in your history" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xtr-Fp-60Z" userLabel="emptyTableLabel">
<rect key="frame" x="0.0" y="66" width="375" height="493"/> <rect key="frame" x="0.0" y="66" width="375" height="493"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@ -184,5 +203,13 @@
<image name="history_missed_selected.png" width="52.799999237060547" height="52.799999237060547"/> <image name="history_missed_selected.png" width="52.799999237060547" height="52.799999237060547"/>
<image name="select_all_default.png" width="43.200000762939453" height="43.200000762939453"/> <image name="select_all_default.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="select_all_disabled.png" width="43.200000762939453" height="43.200000762939453"/> <image name="select_all_disabled.png" width="43.200000762939453" height="43.200000762939453"/>
<image name="voip_conference_new.png" width="97.599998474121094" height="97.599998474121094"/>
<image name="voip_conference_new_selected.png" width="97.599998474121094" height="97.599998474121094"/>
<systemColor name="secondarySystemBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources> </resources>
</document> </document>

View file

@ -1,26 +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/UIKit.h>
@interface CallConferenceTableView : UITableViewController
- (void)update;
@end

View file

@ -1,66 +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 "linphoneapp-Swift.h"
#import "CallConferenceTableView.h"
#import "UICallConferenceCell.h"
#import "LinphoneManager.h"
#import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation CallConferenceTableView
#pragma mark - UI change
- (void)update {
[self.tableView reloadData];
}
#pragma mark - UITableViewDataSource Functions
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *kCellId = NSStringFromClass(UICallConferenceCell.class);
UICallConferenceCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
if (cell == nil) {
cell = [[UICallConferenceCell alloc] initWithIdentifier:kCellId];
}
LinphoneConference *c = [CallManager.instance getConference];
if (c != nil) {
[cell setParticipant:bctbx_list_nth_data(linphone_conference_get_participant_list(c),(int)indexPath.row)];
}
cell.contentView.userInteractionEnabled = NO;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [CallManager.instance getConference] ? linphone_conference_get_participant_count([CallManager.instance getConference]) : 0;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1e-5;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 1e-5;
}
@end

View file

@ -1,54 +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/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

View file

@ -1,150 +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 "CallIncomingView.h"
#import "LinphoneManager.h"
#import "FastAddressBook.h"
#import "PhoneMainView.h"
#import "Utils.h"
@implementation CallIncomingView
#pragma mark - ViewController Functions
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(callUpdateEvent:)
name:kLinphoneCallUpdate
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCallUpdate object:nil];
_call = NULL;
}
#pragma mark - UICompositeViewDelegate Functions
static UICompositeViewDescription *compositeDescription = nil;
+ (UICompositeViewDescription *)compositeViewDescription {
if (compositeDescription == nil) {
compositeDescription = [[UICompositeViewDescription alloc] init:self.class
statusBar:StatusBarView.class
tabBar:nil
sideMenu:CallSideMenuView.class
fullscreen:false
isLeftFragment:YES
fragmentWith:nil];
compositeDescription.darkBackground = true;
}
return compositeDescription;
}
- (UICompositeViewDescription *)compositeViewDescription {
return self.class.compositeViewDescription;
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
if (_earlyMedia && [LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"] && linphone_core_get_calls_nb(LC) < 2) {
_earlyMediaView.hidden = NO;
linphone_core_set_native_video_window_id(LC, (__bridge void *)(_earlyMediaView));
}
if (_call) {
[self update];
}
}
#pragma mark - Event Functions
- (void)callUpdateEvent:(NSNotification *)notif {
LinphoneCall *acall = [[notif.userInfo objectForKey:@"call"] pointerValue];
LinphoneCallState astate = [[notif.userInfo objectForKey:@"state"] intValue];
[self callUpdate:acall state:astate];
}
- (void)callUpdate:(LinphoneCall *)acall state:(LinphoneCallState)astate {
if (_call == acall && (astate == LinphoneCallEnd || astate == LinphoneCallError)) {
[_delegate incomingCallAborted:_call];
} else if ([LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"]) {
LinphoneCallState state = linphone_call_get_state(_call);
if (state == LinphoneCallIncomingReceived) {
LOGI(@"Auto answering call");
[self onAcceptClick:nil];
}
}
}
#pragma mark -
- (void)update {
const LinphoneAddress *addr = linphone_call_get_remote_address(_call);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr withAddressLabel:_addressLabel];
char *uri = linphone_address_as_string_uri_only(addr);
ms_free(uri);
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:YES withRoundedRadius:YES];
_tabBar.hidden = linphone_call_params_video_enabled(linphone_call_get_remote_params(_call));
_tabVideoBar.hidden = !_tabBar.hidden;
}
#pragma mark - Property Functions
static void hideSpinner(LinphoneCall *call, void *user_data) {
CallIncomingView *thiz = (__bridge CallIncomingView *)user_data;
thiz.earlyMedia = TRUE;
thiz.earlyMediaView.hidden = NO;
linphone_core_set_native_video_window_id(LC, (__bridge void *)(thiz.earlyMediaView));
}
- (void)setCall:(LinphoneCall *)call {
_call = call;
_earlyMedia = FALSE;
if ([LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"] && linphone_core_get_calls_nb(LC) < 2) {
linphone_call_accept_early_media(_call);
// linphone_call_params_get_used_video_codec return 0 if no video stream enabled
if (linphone_call_params_get_used_video_codec(linphone_call_get_current_params(_call))) {
linphone_call_set_next_video_frame_decoded_callback(call, hideSpinner, (__bridge void *)(self));
}
} else {
_earlyMediaView.hidden = YES;
}
[self update];
[self callUpdate:_call state:linphone_call_get_state(call)];
}
#pragma mark - Action Functions
- (IBAction)onAcceptClick:(id)event {
[_delegate incomingCallAccepted:_call evenWithVideo:YES];
}
- (IBAction)onDeclineClick:(id)event {
[_delegate incomingCallDeclined:_call];
}
- (IBAction)onAcceptAudioOnlyClick:(id)sender {
[_delegate incomingCallAccepted:_call evenWithVideo:NO];
}
@end

View file

@ -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/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

View file

@ -1,268 +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 "linphoneapp-Swift.h"
#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: [CallManager.instance isBluetoothAvailable]];
[_speakerButton update];
[_microButton update];
[_routesButton update];
[self hidePad:TRUE animated:FALSE];
[self callUpdate:call state:linphone_call_get_state(call) animated:true];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// if there is no call (for whatever reason), we must wait viewDidAppear method
// before popping current view, because UICompositeView cannot handle view change
// directly in viewWillAppear (this would lead to crash in deallocated memory - easily
// reproductible on iPad mini).
if (!linphone_core_get_current_call(LC)) {
[PhoneMainView.instance popCurrentView];
}
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self];
}
- (IBAction)onRoutesBluetoothClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[CallManager.instance changeRouteToBluetooth];
}
- (IBAction)onRoutesEarpieceClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[CallManager.instance changeRouteToDefault];
}
- (IBAction)onRoutesSpeakerClick:(id)sender {
[self hideRoutes:TRUE animated:TRUE];
[CallManager.instance changeRouteToSpeaker];
}
- (IBAction)onRoutesClick:(id)sender {
if ([_routesView isHidden]) {
[self hideRoutes:FALSE animated:ANIMATED];
} else {
[self hideRoutes:TRUE animated:ANIMATED];
}
}
- (IBAction)onNumpadClick:(id)sender {
if ([_numpadView isHidden]) {
[self hidePad:FALSE animated:ANIMATED];
} else {
[self hidePad:TRUE animated:ANIMATED];
}
}
- (IBAction)onDeclineClick:(id)sender {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) {
[CallManager.instance terminateCallWithCall:call];
}
}
- (void)hidePad:(BOOL)hidden animated:(BOOL)animated {
if (hidden) {
[_numpadButton setOff];
} else {
[_numpadButton setOn];
}
if (hidden != _numpadView.hidden) {
if (animated) {
[self hideAnimation:hidden forView:_numpadView completion:nil];
} else {
[_numpadView setHidden:hidden];
}
}
}
- (void)hideRoutes:(BOOL)hidden animated:(BOOL)animated {
if (hidden) {
[_routesButton setOff];
} else {
[_routesButton setOn];
}
_routesBluetoothButton.selected = [CallManager.instance isBluetoothEnabled];
_routesSpeakerButton.selected = [CallManager.instance isSpeakerEnabled];
_routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected;
if (hidden != _routesView.hidden) {
[_routesView setHidden:hidden];
}
}
- (void)hideSpeaker:(BOOL)hidden {
_speakerButton.hidden = hidden;
_routesButton.hidden = !hidden;
}
#pragma mark - Event Functions
- (void)bluetoothAvailabilityUpdateEvent:(NSNotification *)notif {
bool available = [[notif.userInfo objectForKey:@"available"] intValue];
dispatch_async(dispatch_get_main_queue(), ^{
[self hideSpeaker:available];
});
}
- (void)callUpdateEvent:(NSNotification *)notif {
LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue];
LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue];
[self callUpdate:call state:state animated:TRUE];
}
- (void)callUpdate:(LinphoneCall *)call state:(LinphoneCallState)state animated:(BOOL)animated {
_declineButton_earlyMedia.hidden = linphone_call_get_state(call) != LinphoneCallStateOutgoingEarlyMedia;
_declineButton.hidden = !_declineButton_earlyMedia.hidden;
_numpadButton.hidden = _declineButton_earlyMedia.hidden;
}
#pragma mark - Animation
- (void)hideAnimation:(BOOL)hidden forView:(UIView *)target completion:(void (^)(BOOL finished))completion {
if (hidden) {
int original_y = target.frame.origin.y;
CGRect newFrame = target.frame;
newFrame.origin.y = self.view.frame.size.height;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
target.frame = newFrame;
}
completion:^(BOOL finished) {
CGRect originFrame = target.frame;
originFrame.origin.y = original_y;
target.hidden = YES;
target.frame = originFrame;
if (completion)
completion(finished);
}];
} else {
CGRect frame = target.frame;
int original_y = frame.origin.y;
frame.origin.y = self.view.frame.size.height;
target.frame = frame;
frame.origin.y = original_y;
target.hidden = NO;
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
target.frame = frame;
}
completion:^(BOOL finished) {
target.frame = frame; // in case application did not finish
if (completion)
completion(finished);
}];
}
}
@end

View file

@ -1,26 +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/UIKit.h>
@interface CallPausedTableView : UITableViewController
- (void)update;
@end

View file

@ -1,104 +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 "CallPausedTableView.h"
#import "UICallPausedCell.h"
#import "LinphoneManager.h"
#import "Utils.h"
@implementation CallPausedTableView
#pragma mark - UI change
- (void)update {
[self.tableView reloadData];
CGRect newOrigin = self.tableView.frame;
newOrigin.size.height =
[self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] *
[self tableView:self.tableView numberOfRowsInSection:0];
newOrigin.origin.y += self.tableView.frame.size.height - newOrigin.size.height;
self.tableView.frame = newOrigin;
}
#pragma mark - UITableViewDataSource Functions
- (LinphoneCall *)conferenceCallForRow:(NSInteger)row {
const MSList *calls = linphone_core_get_calls(LC);
int i = -1;
while (calls) {
if (linphone_call_get_state(calls->data) == LinphoneCallPaused) {
i++;
if (i == row)
break;
}
calls = calls->next;
}
// we should reach this only when we are querying for conference
return (calls ? calls->data : NULL);
}
#pragma mark - UITableViewDataSource Functions
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *kCellId = NSStringFromClass(UICallPausedCell.class);
UICallPausedCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
if (cell == nil) {
cell = [[UICallPausedCell alloc] initWithIdentifier:kCellId];
}
[cell setCall:[self conferenceCallForRow:indexPath.row]];
cell.contentView.userInteractionEnabled = false;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
const MSList *calls = linphone_core_get_calls(LC);
int count = 0;
int conference_in_pause = 0;
int call_in_conference = 0;
while (calls) {
LinphoneCall *call = calls->data;
BOOL callInConference = linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
if (linphone_call_get_state(call) == LinphoneCallPaused) {
count++;
}
if(callInConference) {
call_in_conference++;
if (!linphone_core_is_in_conference(LC)) {
conference_in_pause = 1;
}
}
calls = calls->next;
}
if(call_in_conference == 1) {
conference_in_pause = 0;
}
return count + conference_in_pause;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 1e-5;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 1e-5;
}
@end

View file

@ -1,31 +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/UIKit.h>
#import "SideMenuTableView.h"
#import "PhoneMainView.h"
@interface CallSideMenuView : UIViewController
@property(weak, nonatomic) IBOutlet UILabel *statsLabel;
- (IBAction)onLateralSwipe:(id)sender;
@end

View file

@ -1,230 +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 "CallSideMenuView.h"
#import "LinphoneManager.h"
#import "PhoneMainView.h"
@implementation CallSideMenuView {
NSTimer *updateTimer;
}
#pragma mark - ViewController Functions
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (updateTimer != nil) {
[updateTimer invalidate];
}
updateTimer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(updateStats:)
userInfo:nil
repeats:YES];
[self updateStats:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (updateTimer != nil) {
[updateTimer invalidate];
updateTimer = nil;
}
}
- (IBAction)onLateralSwipe:(id)sender {
[PhoneMainView.instance.mainViewController hideSideMenu:YES];
}
+ (NSString *)iceToString:(LinphoneIceState)state {
switch (state) {
case LinphoneIceStateNotActivated:
return NSLocalizedString(@"Not activated", @"ICE has not been activated for this call");
break;
case LinphoneIceStateFailed:
return NSLocalizedString(@"Failed", @"ICE processing has failed");
break;
case LinphoneIceStateInProgress:
return NSLocalizedString(@"In progress", @"ICE process is in progress");
break;
case LinphoneIceStateHostConnection:
return NSLocalizedString(@"Direct connection",
@"ICE has established a direct connection to the remote host");
break;
case LinphoneIceStateReflexiveConnection:
return NSLocalizedString(
@"NAT(s) connection",
@"ICE has established a connection to the remote host through one or several NATs");
break;
case LinphoneIceStateRelayConnection:
return NSLocalizedString(@"Relay connection", @"ICE has established a connection through a relay");
break;
}
}
+ (NSString*)afinetToString:(int)remote_family {
return (remote_family == LinphoneAddressFamilyUnspec) ? @"Unspecified":(remote_family == LinphoneAddressFamilyInet) ? @"IPv4" : @"IPv6";
}
+ (NSString *)mediaEncryptionToString:(LinphoneMediaEncryption)enc {
switch (enc) {
case LinphoneMediaEncryptionDTLS:
return @"DTLS";
case LinphoneMediaEncryptionSRTP:
return @"SRTP";
case LinphoneMediaEncryptionZRTP:
return @"ZRTP";
case LinphoneMediaEncryptionNone:
break;
}
return NSLocalizedString(@"None", nil);
}
- (NSString *)updateStatsForCall:(LinphoneCall *)call stream:(LinphoneStreamType)stream {
NSMutableString *result = [[NSMutableString alloc] init];
const PayloadType *payload = NULL;
const LinphoneCallStats *stats;
const LinphoneCallParams *params = linphone_call_get_current_params(call);
NSString *name;
switch (stream) {
case LinphoneStreamTypeAudio:
name = @"Audio";
payload = linphone_call_params_get_used_audio_codec(params);
stats = linphone_call_get_audio_stats(call);
break;
case LinphoneStreamTypeText:
name = @"Text";
payload = linphone_call_params_get_used_text_codec(params);
stats = linphone_call_get_text_stats(call);
break;
case LinphoneStreamTypeVideo:
name = @"Video";
payload = linphone_call_params_get_used_video_codec(params);
stats = linphone_call_get_video_stats(call);
break;
case LinphoneStreamTypeUnknown:
break;
}
if (payload == NULL) {
return result;
}
[result appendString:@"\n"];
[result appendString:name];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Codec: %s/%iHz", payload->mime_type, payload->clock_rate]];
if (stream == LinphoneStreamTypeAudio) {
[result appendString:[NSString stringWithFormat:@"/%i channels", payload->channels]];
}
[result appendString:@"\n"];
// Encoder & decoder descriptions
const char *enc_desc = ms_factory_get_encoder(linphone_core_get_ms_factory(LC), payload->mime_type)->text;
const char *dec_desc = ms_factory_get_decoder(linphone_core_get_ms_factory(LC), payload->mime_type)->text;
if (strcmp(enc_desc, dec_desc) == 0) {
[result appendString:[NSString stringWithFormat:@"Encoder/Decoder: %s", enc_desc]];
[result appendString:@"\n"];
} else {
[result appendString:[NSString stringWithFormat:@"Encoder: %s", enc_desc]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Decoder: %s", dec_desc]];
[result appendString:@"\n"];
}
if (stats != NULL) {
[result appendString:[NSString stringWithFormat:@"Download bandwidth: %1.1f kbits/s",
linphone_call_stats_get_download_bandwidth(stats)]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Upload bandwidth: %1.1f kbits/s",
linphone_call_stats_get_upload_bandwidth(stats)]];
[result appendString:@"\n"];
if (stream == LinphoneStreamTypeVideo) {
/*[result appendString:[NSString stringWithFormat:@"Estimated download bandwidth: %1.1f kbits/s",
linphone_call_stats_get_estimated_download_bandwidth(stats)]];
[result appendString:@"\n"];*/
}
[result
appendString:[NSString stringWithFormat:@"ICE state: %@",
[self.class iceToString:linphone_call_stats_get_ice_state(stats)]]];
[result appendString:@"\n"];
[result
appendString:[NSString
stringWithFormat:@"Afinet: %@",
[self.class afinetToString:linphone_call_stats_get_ip_family_of_remote(
stats)]]];
[result appendString:@"\n"];
// RTP stats section (packet loss count, etc)
const rtp_stats_t rtp_stats = *linphone_call_stats_get_rtp_stats(stats);
[result
appendString:[NSString stringWithFormat:
@"RTP packets: %llu total, %lld cum loss, %llu discarded, %llu OOT, %llu bad",
rtp_stats.packet_recv, rtp_stats.cum_packet_loss, rtp_stats.discarded,
rtp_stats.outoftime, rtp_stats.bad]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Sender loss rate: %.2f%%",
linphone_call_stats_get_sender_loss_rate(stats)]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Receiver loss rate: %.2f%%",
linphone_call_stats_get_receiver_loss_rate(stats)]];
[result appendString:@"\n"];
if (stream == LinphoneStreamTypeVideo) {
const LinphoneVideoDefinition *recv_definition = linphone_call_params_get_received_video_definition(params);
const LinphoneVideoDefinition *sent_definition = linphone_call_params_get_sent_video_definition(params);
float sentFPS = linphone_call_params_get_sent_framerate(params);
float recvFPS = linphone_call_params_get_received_framerate(params);
[result appendString:[NSString stringWithFormat:@"Sent video resolution: %dx%d (%.1fFPS)", linphone_video_definition_get_width(sent_definition),
linphone_video_definition_get_height(sent_definition), sentFPS]];
[result appendString:@"\n"];
[result appendString:[NSString stringWithFormat:@"Received video resolution: %dx%d (%.1fFPS)",
linphone_video_definition_get_width(recv_definition),
linphone_video_definition_get_height(recv_definition), recvFPS]];
[result appendString:@"\n"];
}
}
return result;
}
- (void)updateStats:(NSTimer *)timer {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (!call) {
_statsLabel.text = NSLocalizedString(@"No call in progress", nil);
return;
}
NSMutableString *stats = [[NSMutableString alloc] init];
LinphoneMediaEncryption enc = linphone_call_params_get_media_encryption(linphone_call_get_current_params(call));
if (enc != LinphoneMediaEncryptionNone) {
[stats appendString:[NSString
stringWithFormat:@"Call encrypted using %@", [self.class mediaEncryptionToString:enc]]];
}
[stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeAudio]];
[stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeVideo]];
[stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeText]];
_statsLabel.text = stats;
}
@end

View file

@ -1,49 +0,0 @@
<?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>

View file

@ -1,117 +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/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

File diff suppressed because it is too large Load diff

View file

@ -45,6 +45,11 @@
@property(nonatomic) Boolean isGroupChat; @property(nonatomic) Boolean isGroupChat;
@property(nonatomic) Boolean isEncrypted; @property(nonatomic) Boolean isEncrypted;
@property(nonatomic) Boolean isForVoipConference;
@property(nonatomic) Boolean isForOngoingVoipConference;
@property (weak, nonatomic) IBOutlet UILabel *voipTitle;
- (IBAction)onBackClick:(id)sender; - (IBAction)onBackClick:(id)sender;
- (IBAction)onNextClick:(id)sender; - (IBAction)onNextClick:(id)sender;
- (IBAction)onChiffreClick:(id)sender; - (IBAction)onChiffreClick:(id)sender;

View file

@ -20,6 +20,7 @@
#import "ChatConversationCreateView.h" #import "ChatConversationCreateView.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "UIChatCreateCollectionViewCell.h" #import "UIChatCreateCollectionViewCell.h"
#import "linphoneapp-Swift.h"
@implementation ChatConversationCreateView @implementation ChatConversationCreateView
@ -81,6 +82,22 @@ static UICompositeViewDescription *compositeDescription = nil;
frame.origin.x = _linphoneButton.frame.origin.x; frame.origin.x = _linphoneButton.frame.origin.x;
_allButton.frame = frame; _allButton.frame = frame;
} }
if (_isForVoipConference) {
_switchView.hidden = true;
_chiffreOptionView.hidden = true;
_voipTitle.hidden = false;
if (_isForOngoingVoipConference) {
[_nextButton setImage:[UIImage imageNamed:@"valid_default"] forState:UIControlStateNormal];
} else {
[_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal];
}
} else {
_voipTitle.hidden = true;
[_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal];
}
} }
- (void)viewUpdateEvent:(NSNotification *)notif { - (void)viewUpdateEvent:(NSNotification *)notif {
@ -153,11 +170,21 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (IBAction)onNextClick:(id)sender { - (IBAction)onNextClick:(id)sender {
ChatConversationInfoView *view = VIEW(ChatConversationInfoView); if (_isForVoipConference) {
view.contacts = _tableController.contactsGroup; if (_isForOngoingVoipConference) {
view.create = !_isForEditing; [PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription];
view.encrypted = _isEncrypted; [ConferenceViewModelBridge updateParticipantsListWithAddresses:_tableController.contactsGroup];
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; } else {
[PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription];
[VIEW(ConferenceSchedulingSummaryView) setParticipantsWithAddresses:_tableController.contactsGroup];
}
} else {
ChatConversationInfoView *view = VIEW(ChatConversationInfoView);
view.contacts = _tableController.contactsGroup;
view.create = !_isForEditing;
view.encrypted = _isEncrypted;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
} }
- (IBAction)onChiffreClick:(id)sender { - (IBAction)onChiffreClick:(id)sender {

View file

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

View file

@ -24,6 +24,7 @@
#import "UIChatBubblePhotoCell.h" #import "UIChatBubblePhotoCell.h"
#import "UIChatNotifiedEventCell.h" #import "UIChatNotifiedEventCell.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
@implementation ChatConversationTableView @implementation ChatConversationTableView
@ -335,7 +336,8 @@ static const int BASIC_EVENT_LIST=15;
LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue]; LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue];
if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) { if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) {
LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event); LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event);
if (linphone_chat_message_get_file_transfer_information(chat) || linphone_chat_message_get_external_body_url(chat)) BOOL isConferenceIcs = [ICSBubbleView isConferenceInvitationMessageWithCmessage:chat];
if (!isConferenceIcs && (linphone_chat_message_get_file_transfer_information(chat) || linphone_chat_message_get_external_body_url(chat)))
kCellId = NSStringFromClass(UIChatBubblePhotoCell.class); kCellId = NSStringFromClass(UIChatBubblePhotoCell.class);
else else
kCellId = NSStringFromClass(UIChatBubbleTextCell.class); kCellId = NSStringFromClass(UIChatBubbleTextCell.class);
@ -382,14 +384,12 @@ static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f;
LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue]; LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue];
if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) { if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) {
LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event); LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event);
//If the message is followed by another one that is not from the same address, we add a little space under it
//If the message is followed by another one that is not from the same address, we add a little space under it CGFloat height = 0;
CGFloat height = 0; if ([self isLastIndexInTableView:indexPath chat:chat])
if ([self isLastIndexInTableView:indexPath chat:chat]) height += tableView.frame.size.height * MESSAGE_SPACING_PERCENTAGE / 100;
height += tableView.frame.size.height * MESSAGE_SPACING_PERCENTAGE / 100; if (![self isFirstIndexInTableView:indexPath chat:chat])
if (![self isFirstIndexInTableView:indexPath chat:chat]) height -= 20;
height -= 20;
return [UIChatBubbleTextCell ViewHeightForMessage:chat withWidth:self.view.frame.size.width].height + height; return [UIChatBubbleTextCell ViewHeightForMessage:chat withWidth:self.view.frame.size.width].height + height;
} }
return [UIChatNotifiedEventCell height]; return [UIChatNotifiedEventCell height];

View file

@ -81,7 +81,6 @@
@property(nonatomic, strong) IBOutlet UIButton *pictureButton; @property(nonatomic, strong) IBOutlet UIButton *pictureButton;
@property(weak, nonatomic) IBOutlet UIButton *callButton; @property(weak, nonatomic) IBOutlet UIButton *callButton;
@property(weak, nonatomic) IBOutlet UIBackToCallButton *backToCallButton; @property(weak, nonatomic) IBOutlet UIBackToCallButton *backToCallButton;
@property (weak, nonatomic) IBOutlet UIButton *infoButton;
@property (weak, nonatomic) IBOutlet UILabel *particpantsLabel; @property (weak, nonatomic) IBOutlet UILabel *particpantsLabel;
@property NSMutableArray <NSNumber *> *qualitySettingsArray; @property NSMutableArray <NSNumber *> *qualitySettingsArray;
@property (weak, nonatomic) IBOutlet UICollectionView *imagesCollectionView; @property (weak, nonatomic) IBOutlet UICollectionView *imagesCollectionView;
@ -161,4 +160,6 @@
-(void) initiateReplyViewForMessage:(LinphoneChatMessage *)message; -(void) initiateReplyViewForMessage:(LinphoneChatMessage *)message;
-(void) stopVoiceRecording;
@end @end

View file

@ -659,11 +659,32 @@ static UICompositeViewDescription *compositeDescription = nil;
}]; }];
} }
- (BOOL) groupCallAvailable {
if (isOneToOne || !_backToCallButton.hidden || _tableController.tableView.isEditing)
return false;
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (!account)
return false;
const LinphoneAccountParams *params = linphone_account_get_params(account);
if (!params)
return false;
return linphone_account_params_get_audio_video_conference_factory_address(params) != nil || linphone_account_params_get_conference_factory_uri(params) != nil;
}
- (void)updateSuperposedButtons { - (void)updateSuperposedButtons {
[_backToCallButton update]; [_backToCallButton update];
_infoButton.hidden = (isOneToOne|| !_backToCallButton.hidden || _tableController.tableView.isEditing); _callButton.hidden = !_backToCallButton.hidden || _tableController.tableView.isEditing;
_callButton.hidden = !_backToCallButton.hidden || !_infoButton.hidden || _tableController.tableView.isEditing;
_toggleMenuButton.hidden = _tableController.isEditing; _toggleMenuButton.hidden = _tableController.isEditing;
// Group call :
if (self.groupCallAvailable ) {
[_callButton setImage: [LinphoneUtils resizeImage:[UIImage imageNamed:@"voip_conference_new"] newSize:CGSizeMake(50, 50)] forState:UIControlStateNormal];
_callButton.hidden = false;
} else {
[_callButton setImage:[UIImage imageNamed:@"call_alt_start_default"] forState:UIControlStateNormal];
}
} }
- (void)updateParticipantLabel { - (void)updateParticipantLabel {
@ -861,7 +882,20 @@ static UICompositeViewDescription *compositeDescription = nil;
bctbx_list_t *participants = linphone_chat_room_get_participants(_chatRoom); bctbx_list_t *participants = linphone_chat_room_get_participants(_chatRoom);
LinphoneParticipant *firstParticipant = participants ? (LinphoneParticipant *)participants->data : NULL; LinphoneParticipant *firstParticipant = participants ? (LinphoneParticipant *)participants->data : NULL;
const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(_chatRoom); const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(_chatRoom);
[LinphoneManager.instance call:addr]; if (self.groupCallAvailable) {
UIConfirmationDialog *d = [UIConfirmationDialog ShowWithMessage:VoipTexts.conference_start_group_call_dialog_message
cancelMessage:nil
confirmMessage:VoipTexts.conference_start_group_call_dialog_ok_button
onCancelClick:^() {}
onConfirmationClick:^() {
[ConferenceViewModelBridge startGroupCallWithCChatRoom:_chatRoom];
}];
d.groupCallImage.hidden = NO;
[d.groupCallImage setImageNamed:@"voip_conference_new" tintColor:UIColor.whiteColor];
[d setSpecialColor];
[d setWhiteCancel];
} else
[LinphoneManager.instance call:addr];
} }
- (IBAction)onListSwipe:(id)sender { - (IBAction)onListSwipe:(id)sender {
@ -1814,7 +1848,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
cell.textLabel.text = NSLocalizedString(@"Go to contact",nil); cell.textLabel.text = NSLocalizedString(@"Go to contact",nil);
} }
} else { } else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(25, 25)];
cell.textLabel.text = NSLocalizedString(@"Group infos",nil); cell.textLabel.text = NSLocalizedString(@"Group infos",nil);
} }
} }
@ -1847,7 +1881,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
if ((isEncrypted && ((!canEphemeral && indexPath.row == 4)||(canEphemeral && indexPath.row == 5))) if ((isEncrypted && ((!canEphemeral && indexPath.row == 4)||(canEphemeral && indexPath.row == 5)))
|| (!isEncrypted && indexPath.row == 3)) { || (!isEncrypted && indexPath.row == 3)) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(25, 25)];
cell.textLabel.text = NSLocalizedString(@"Show address and identity",nil); cell.textLabel.text = NSLocalizedString(@"Show address and identity",nil);
} }

View file

@ -150,7 +150,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[_tableView reloadData]; [_tableView reloadData];
} else { } else {
const LinphoneAddress *addr = linphone_participant_device_get_address(entry->device); const LinphoneAddress *addr = linphone_participant_device_get_address(entry->device);
[CallManager.instance startCallWithAddr:(LinphoneAddress *)addr isSas:TRUE]; [CallManager.instance startCallWithAddr:(LinphoneAddress *)addr isSas:TRUE isVideo:false isConference:false];
} }
} else { } else {
bctbx_list_t *devices = linphone_participant_get_devices(entry->participant); bctbx_list_t *devices = linphone_participant_get_devices(entry->participant);

View file

@ -21,6 +21,8 @@
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
@implementation DialerView @implementation DialerView
@ -143,6 +145,9 @@ static UICompositeViewDescription *compositeDescription = nil;
[_videoCameraSwitch setHidden:FALSE]; [_videoCameraSwitch setHidden:FALSE];
} }
} }
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
} }
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
@ -170,11 +175,10 @@ static UICompositeViewDescription *compositeDescription = nil;
_padView.hidden = !IPAD && UIInterfaceOrientationIsLandscape(toInterfaceOrientation); _padView.hidden = !IPAD && UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
if (linphone_core_get_calls_nb(LC)) { if (linphone_core_get_calls_nb(LC)) {
_backButton.hidden = FALSE; _backButton.hidden = FALSE;
_addContactButton.hidden = TRUE;
} else { } else {
_backButton.hidden = TRUE; _backButton.hidden = TRUE;
_addContactButton.hidden = FALSE;
} }
_addContactButton.hidden = FALSE;
} }
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
@ -388,24 +392,19 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions #pragma mark - Action Functions
- (IBAction)onAddContactClick:(id)event { - (IBAction)onAddContactClick:(id)event {
[ContactSelection setSelectionMode:ContactSelectionModeEdit]; ConferenceSchedulingView *view = VIEW(ConferenceSchedulingView);
[ContactSelection setAddAddress:[_addressField text]]; [view resetViewModel];
[ContactSelection enableSipFilter:FALSE]; [PhoneMainView.instance changeCurrentView:ConferenceSchedulingView.compositeViewDescription];
[PhoneMainView.instance changeCurrentView:ContactsListView.compositeViewDescription];
} }
- (IBAction)onBackClick:(id)event { - (IBAction)onBackClick:(id)event {
[PhoneMainView.instance popToView:CallView.compositeViewDescription]; [PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
} }
- (IBAction)onAddressChange:(id)sender { - (IBAction)onAddressChange:(id)sender {
if ([self displayDebugPopup:_addressField.text]) { if ([self displayDebugPopup:_addressField.text]) {
_addressField.text = @""; _addressField.text = @"";
} }
_addContactButton.enabled = _backspaceButton.enabled = ([[_addressField text] length] > 0);
if ([_addressField.text length] == 0) {
[self.view endEditing:YES];
}
} }
- (IBAction)onBackspaceClick:(id)sender { - (IBAction)onBackspaceClick:(id)sender {

View file

@ -138,6 +138,7 @@ static UICompositeViewDescription *compositeDescription = nil;
_addContactButton.hidden = YES; _addContactButton.hidden = YES;
return; return;
} }
_emptyLabel.hidden = YES; _emptyLabel.hidden = YES;
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog); const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);

View file

@ -25,7 +25,11 @@
} }
@property(nonatomic, assign) BOOL missedFilter; @property(nonatomic, assign) BOOL missedFilter;
@property(nonatomic, assign) BOOL confFilter;
@property(strong, nonatomic) NSMutableDictionary *sections; @property(strong, nonatomic) NSMutableDictionary *sections;
@property(strong, nonatomic) NSMutableArray *sortedDays; @property(strong, nonatomic) NSMutableArray *sortedDays;
- (void)removeFIlters;
@end @end

View file

@ -22,15 +22,18 @@
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "Utils.h" #import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation HistoryListTableView @implementation HistoryListTableView
@synthesize missedFilter; @synthesize missedFilter,confFilter;
#pragma mark - Lifecycle Functions #pragma mark - Lifecycle Functions
- (void)initHistoryTableViewController { - (void)initHistoryTableViewController {
missedFilter = false; missedFilter = false;
confFilter = false;
} }
- (id)init { - (id)init {
@ -102,9 +105,30 @@
return; return;
} }
missedFilter = amissedFilter; missedFilter = amissedFilter;
if (missedFilter) {
confFilter = false;
}
[self loadData]; [self loadData];
} }
- (void)setConfFilter:(BOOL)aconfFilter {
if (confFilter == aconfFilter) {
return;
}
confFilter = aconfFilter;
if (confFilter) {
missedFilter = false;
}
[self loadData];
}
- (void)removeFIlters {
confFilter = false;
missedFilter = false;
[self loadData];
}
#pragma mark - UITableViewDataSource Functions #pragma mark - UITableViewDataSource Functions
- (NSDate *)dateAtBeginningOfDayForDate:(NSDate *)inputDate { - (NSDate *)dateAtBeginningOfDayForDate:(NSDate *)inputDate {
@ -129,7 +153,8 @@
self.sections = [NSMutableDictionary dictionary]; self.sections = [NSMutableDictionary dictionary];
while (logs != NULL) { while (logs != NULL) {
LinphoneCallLog *log = (LinphoneCallLog *)logs->data; LinphoneCallLog *log = (LinphoneCallLog *)logs->data;
if (!missedFilter || linphone_call_log_get_status(log) == LinphoneCallMissed) { BOOL keepIt = (!missedFilter || linphone_call_log_get_status(log) == LinphoneCallMissed) && (!confFilter||linphone_call_log_was_conference(log)) ;
if (keepIt) {
NSDate *startDate = [self NSDate *startDate = [self
dateAtBeginningOfDayForDate:[NSDate dateAtBeginningOfDayForDate:[NSDate
dateWithTimeIntervalSince1970:linphone_call_log_get_start_date(log)]]; dateWithTimeIntervalSince1970:linphone_call_log_get_start_date(log)]];
@ -143,7 +168,7 @@
// if this contact was already the previous entry, do not add it twice // if this contact was already the previous entry, do not add it twice
LinphoneCallLog *prev = [eventsOnThisDay lastObject] ? [[eventsOnThisDay lastObject] pointerValue] : NULL; LinphoneCallLog *prev = [eventsOnThisDay lastObject] ? [[eventsOnThisDay lastObject] pointerValue] : NULL;
if (prev && linphone_address_weak_equal(linphone_call_log_get_remote_address(prev), if (!linphone_call_log_was_conference(log) && prev && linphone_address_weak_equal(linphone_call_log_get_remote_address(prev),
linphone_call_log_get_remote_address(log))) { linphone_call_log_get_remote_address(log))) {
bctbx_list_t *list = linphone_call_log_get_user_data(prev); bctbx_list_t *list = linphone_call_log_get_user_data(prev);
list = bctbx_list_append(list, linphone_call_log_ref(log)); list = bctbx_list_append(list, linphone_call_log_ref(log));
@ -241,8 +266,15 @@
UIHistoryCell *cell = (UIHistoryCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath]; UIHistoryCell *cell = (UIHistoryCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
[cell onDetails:self]; [cell onDetails:self];
} else { } else {
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog); if (linphone_call_log_was_conference(callLog)) {
[LinphoneManager.instance call:addr]; LinphoneConferenceInfo *confInfo = linphone_call_log_get_conference_info(callLog);
ConferenceWaitingRoomFragment *view = VIEW(ConferenceWaitingRoomFragment);
[view setDetailsWithSubject:[NSString stringWithUTF8String:linphone_conference_info_get_subject(confInfo)] url:[NSString stringWithUTF8String:linphone_address_as_string(linphone_conference_info_get_uri(confInfo))]];
[PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomFragment.compositeViewDescription];
} else {
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);
[LinphoneManager.instance call:addr];
}
} }
} }
} }

View file

@ -31,6 +31,7 @@
@property(nonatomic, strong) IBOutlet UIButton *allButton; @property(nonatomic, strong) IBOutlet UIButton *allButton;
@property(nonatomic, strong) IBOutlet UIButton *missedButton; @property(nonatomic, strong) IBOutlet UIButton *missedButton;
@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *conferenceButton;
@property(weak, nonatomic) IBOutlet UIImageView *selectedButtonImage; @property(weak, nonatomic) IBOutlet UIImageView *selectedButtonImage;
@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton; @property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton;

View file

@ -23,7 +23,7 @@
@implementation HistoryListView @implementation HistoryListView
typedef enum _HistoryView { History_All, History_Missed, History_MAX } HistoryView; typedef enum _HistoryView { History_All, History_Missed, History_Conference, History_MAX } HistoryView;
#pragma mark - UICompositeViewDelegate Functions #pragma mark - UICompositeViewDelegate Functions
@ -48,6 +48,11 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - ViewController Functions #pragma mark - ViewController Functions
-(void) viewDidLoad {
[super viewDidLoad];
_conferenceButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
}
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
@ -70,18 +75,27 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - #pragma mark -
- (void)changeView:(HistoryView)view { - (void)changeView:(HistoryView)view {
CGRect frame = _selectedButtonImage.frame; CGRect frame = _selectedButtonImage.frame;
if (view == History_All) { if (view == History_All) {
frame.origin.x = _allButton.frame.origin.x; frame.origin.x = _allButton.frame.origin.x;
_allButton.selected = TRUE; _allButton.selected = TRUE;
[_tableController setMissedFilter:FALSE]; [_tableController removeFIlters];
_missedButton.selected = FALSE; _missedButton.selected = FALSE;
_conferenceButton.selected = false;
} else if (view == History_Conference) {
frame.origin.x = _conferenceButton.frame.origin.x;
_conferenceButton.selected = TRUE;
[_tableController setConfFilter:true];
_missedButton.selected = FALSE;
_allButton.selected = FALSE;
} else { } else {
frame.origin.x = _missedButton.frame.origin.x; frame.origin.x = _missedButton.frame.origin.x;
_missedButton.selected = TRUE; _missedButton.selected = TRUE;
[_tableController setMissedFilter:TRUE]; [_tableController setMissedFilter:TRUE];
_allButton.selected = FALSE; _allButton.selected = FALSE;
_conferenceButton.selected = false;
} }
_selectedButtonImage.frame = frame; _selectedButtonImage.frame = frame;
} }
@ -96,6 +110,10 @@ static UICompositeViewDescription *compositeDescription = nil;
[self changeView:History_Missed]; [self changeView:History_Missed];
} }
- (IBAction)onConferenceClick:(id)sender {
[self changeView:History_Conference];
}
- (IBAction)onDeleteClick:(id)event { - (IBAction)onDeleteClick:(id)event {
NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"Do you want to delete selected logs?", nil)]; NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"Do you want to delete selected logs?", nil)];
[UIConfirmationDialog ShowWithMessage:msg [UIConfirmationDialog ShowWithMessage:msg

View file

@ -17,7 +17,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#import "linphoneapp-Swift.h"
#import "LinphoneAppDelegate.h" #import "LinphoneAppDelegate.h"
#import "ContactDetailsView.h" #import "ContactDetailsView.h"
#import "ContactsListView.h" #import "ContactsListView.h"
@ -138,7 +137,7 @@
if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) { if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) { if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) {
linphone_call_accept(call); linphone_call_accept(call);
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
} else { } else {
[PhoneMainView.instance displayIncomingCall:call]; [PhoneMainView.instance displayIncomingCall:call];
} }
@ -332,6 +331,8 @@
return NO; return NO;
} }
[PhoneMainView.instance.mainViewController getCachedController:ActiveCallOrConferenceView.compositeViewDescription.name]; // This will create the single instance of the ActiveCallOrConferenceView including listeneres
return YES; return YES;
} }
@ -422,13 +423,6 @@
// used for callkit. Called when active video. // 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:@"INStartVideoCallIntent"]) {
LOGI(@"CallKit: satrt video.");
CallView *view = VIEW(CallView);
[view.videoButton setOn];
}
if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) { // tel URI handler. if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) { // tel URI handler.
INInteraction *interaction = userActivity.interaction; INInteraction *interaction = userActivity.interaction;
INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent; INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent;
@ -552,7 +546,7 @@
if ([response.actionIdentifier isEqual:@"Answer"]) { if ([response.actionIdentifier isEqual:@"Answer"]) {
// use the standard handler // use the standard handler
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription]; [CallManager.instance acceptCallWithCall:call hasVideo:NO];
linphone_call_accept(call); linphone_call_accept(call);
} else if ([response.actionIdentifier isEqual:@"Decline"]) { } else if ([response.actionIdentifier isEqual:@"Decline"]) {
linphone_call_decline(call, LinphoneReasonDeclined); linphone_call_decline(call, LinphoneReasonDeclined);
@ -590,7 +584,6 @@
return; return;
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications]; [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
[CallManager.instance acceptVideoWithCall:call confirm:TRUE]; [CallManager.instance acceptVideoWithCall:call confirm:TRUE];
} else if ([response.actionIdentifier isEqual:@"Confirm"]) { } else if ([response.actionIdentifier isEqual:@"Confirm"]) {
if (linphone_core_get_current_call(LC) == call) if (linphone_core_get_current_call(LC) == call)
@ -623,7 +616,7 @@
} }
} else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) { } else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) {
if (!call) return; if (!call) return;
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
NSTimer *videoDismissTimer = nil; NSTimer *videoDismissTimer = nil;
UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body
cancelMessage:nil cancelMessage:nil
@ -707,8 +700,7 @@
if ([notification.category isEqualToString:@"incoming_call"]) { if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) { if ([identifier isEqualToString:@"answer"]) {
// use the standard handler // use the standard handler
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription]; [CallManager.instance acceptCallWithCall:call hasVideo:NO];
linphone_call_accept(call);
} else if ([identifier isEqualToString:@"decline"]) { } else if ([identifier isEqualToString:@"decline"]) {
LinphoneCall *call = linphone_core_get_current_call(LC); LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) if (call)
@ -745,8 +737,7 @@
if ([notification.category isEqualToString:@"incoming_call"]) { if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) { if ([identifier isEqualToString:@"answer"]) {
// use the standard handler // use the standard handler
[PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription]; [CallManager.instance acceptCallWithCall:call hasVideo:NO];
linphone_call_accept(call);
} else if ([identifier isEqualToString:@"decline"]) { } else if ([identifier isEqualToString:@"decline"]) {
LinphoneCall *call = linphone_core_get_current_call(LC); LinphoneCall *call = linphone_core_get_current_call(LC);
if (call) if (call)

View file

@ -407,6 +407,8 @@
{ {
[self setBool:[lm lpConfigBoolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"]; [self setBool:[lm lpConfigBoolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"];
[self setBool:linphone_core_is_record_aware_enabled(LC) forKey:@"record_aware"];
[self setBool:linphone_core_get_use_info_for_dtmf(LC) forKey:@"sipinfo_dtmf_preference"]; [self setBool:linphone_core_get_use_info_for_dtmf(LC) forKey:@"sipinfo_dtmf_preference"];
[self setBool:linphone_core_get_use_rfc2833_for_dtmf(LC) forKey:@"rfc_dtmf_preference"]; [self setBool:linphone_core_get_use_rfc2833_for_dtmf(LC) forKey:@"rfc_dtmf_preference"];
@ -931,7 +933,9 @@
linphone_core_set_use_rfc2833_for_dtmf(LC, [self boolForKey:@"rfc_dtmf_preference"]); linphone_core_set_use_rfc2833_for_dtmf(LC, [self boolForKey:@"rfc_dtmf_preference"]);
[lm lpConfigSetBool:[self boolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"]; [lm lpConfigSetBool:[self boolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"];
[ProviderDelegate resetSharedProviderConfiguration]; [ProviderDelegate resetSharedProviderConfiguration];
linphone_core_set_record_aware_enabled(LC, [self boolForKey:@"record_aware"]);
linphone_core_set_use_info_for_dtmf(LC, [self boolForKey:@"sipinfo_dtmf_preference"]); linphone_core_set_use_info_for_dtmf(LC, [self boolForKey:@"sipinfo_dtmf_preference"]);
linphone_core_set_inc_timeout(LC, [self integerForKey:@"incoming_call_timeout_preference"]); linphone_core_set_inc_timeout(LC, [self integerForKey:@"incoming_call_timeout_preference"]);
linphone_core_set_in_call_timeout(LC, [self integerForKey:@"in_call_timeout_preference"]); linphone_core_set_in_call_timeout(LC, [self integerForKey:@"in_call_timeout_preference"]);

View file

@ -32,7 +32,6 @@
#import "LinphoneCoreSettingsStore.h" #import "LinphoneCoreSettingsStore.h"
#import "LinphoneAppDelegate.h" #import "LinphoneAppDelegate.h"
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "Utils/AudioHelper.h"
#import "Utils/FileTransferDelegate.h" #import "Utils/FileTransferDelegate.h"
#include "linphone/factory.h" #include "linphone/factory.h"
@ -482,6 +481,20 @@ static int check_should_migrate_images(void *data, int argc, char **argv, char *
linphone_account_set_params(account, newAccountParams); linphone_account_set_params(account, newAccountParams);
} }
} }
if (!linphone_account_params_get_audio_video_conference_factory_address(newAccountParams) && strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0) {
NSString *uri = [self lpConfigStringForKey:@"default_audio_video_conference_factory_uri" withDefault:@"sip:videoconference-factory2@sip.linphone.org"];
LinphoneAddress *a = linphone_factory_create_address(linphone_factory_get(), uri.UTF8String);
if (a) {
linphone_account_params_set_audio_video_conference_factory_address(newAccountParams, a);
linphone_account_set_params(account, newAccountParams);
}
}
if (strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0 && !linphone_account_params_rtp_bundle_enabled(newAccountParams)) {
linphone_account_params_enable_rtp_bundle(newAccountParams, true);
linphone_account_set_params(account,newAccountParams);
}
linphone_account_params_unref(newAccountParams); linphone_account_params_unref(newAccountParams);
accounts = accounts->next; accounts = accounts->next;
} }
@ -848,7 +861,7 @@ static void linphone_iphone_popup_password_request(LinphoneCore *lc, LinphoneAut
if ((linphone_core_get_max_size_for_auto_download_incoming_files(LC) > -1) && linphone_chat_message_get_file_transfer_information(msg)) if ((linphone_core_get_max_size_for_auto_download_incoming_files(LC) > -1) && linphone_chat_message_get_file_transfer_information(msg))
hasFile = TRUE; hasFile = TRUE;
if (!linphone_chat_message_is_file_transfer(msg) && !linphone_chat_message_is_text(msg) && !hasFile) if (!linphone_chat_message_is_file_transfer(msg) && !linphone_chat_message_is_text(msg) && !hasFile && ![ICSBubbleView isConferenceInvitationMessageWithCmessage:msg])
return; return;
if (hasFile) { if (hasFile) {
@ -1183,6 +1196,8 @@ static void linphone_iphone_is_composing_received(LinphoneCore *lc, LinphoneChat
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCoreUpdate [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCoreUpdate
object:LinphoneManager.instance object:LinphoneManager.instance
userInfo:dict]; userInfo:dict];
} }
static BOOL libStarted = FALSE; static BOOL libStarted = FALSE;
@ -1869,7 +1884,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
} }
[self checkLocalNetworkPermission]; [self checkLocalNetworkPermission];
// For OutgoingCall, show CallOutgoingView // For OutgoingCall, show CallOutgoingView
[CallManager.instance startCallWithAddr:iaddr isSas:FALSE]; [CallManager.instance startCallWithAddr:iaddr isSas:FALSE isVideo:false isConference:false];
} }
#pragma mark - Misc Functions #pragma mark - Misc Functions

View file

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

View file

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

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?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"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -14,6 +14,7 @@
<outlet property="cancelButton" destination="B1K-CB-3of" id="KKi-Xc-ldA"/> <outlet property="cancelButton" destination="B1K-CB-3of" id="KKi-Xc-ldA"/>
<outlet property="confirmationButton" destination="SbQ-re-fGQ" id="yiv-a9-o8E"/> <outlet property="confirmationButton" destination="SbQ-re-fGQ" id="yiv-a9-o8E"/>
<outlet property="forwardImage" destination="1Wh-Yi-cUe" id="YQq-bt-pk1"/> <outlet property="forwardImage" destination="1Wh-Yi-cUe" id="YQq-bt-pk1"/>
<outlet property="groupCallImage" destination="SVn-4k-9yc" id="sAP-8V-ttn"/>
<outlet property="securityImage" destination="bbo-g3-bGy" id="qZa-li-yrl"/> <outlet property="securityImage" destination="bbo-g3-bGy" id="qZa-li-yrl"/>
<outlet property="titleLabel" destination="jLz-g1-cTe" id="qaj-OB-2r1"/> <outlet property="titleLabel" destination="jLz-g1-cTe" id="qaj-OB-2r1"/>
<outlet property="view" destination="2Vb-Xy-rci" id="nNw-EJ-AY3"/> <outlet property="view" destination="2Vb-Xy-rci" id="nNw-EJ-AY3"/>
@ -93,6 +94,10 @@
<rect key="frame" x="89" y="50" width="138" height="54"/> <rect key="frame" x="89" y="50" width="138" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="voip_conference_new.png" translatesAutoresizingMaskIntoConstraints="NO" id="SVn-4k-9yc">
<rect key="frame" x="89" y="50" width="138" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView>
</subviews> </subviews>
</view> </view>
</subviews> </subviews>
@ -111,5 +116,6 @@
<image name="color_M.png" width="2" height="2"/> <image name="color_M.png" width="2" height="2"/>
<image name="forward_message_default.png" width="187" height="148"/> <image name="forward_message_default.png" width="187" height="148"/>
<image name="security_2_indicator.png" width="27.5" height="32.5"/> <image name="security_2_indicator.png" width="27.5" height="32.5"/>
<image name="voip_conference_new.png" width="97.599998474121094" height="97.599998474121094"/>
</resources> </resources>
</document> </document>

View file

@ -19,6 +19,7 @@
#import "TabBarView.h" #import "TabBarView.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
@implementation TabBarView @implementation TabBarView
@ -99,7 +100,8 @@
- (void)updateSelectedButton:(UICompositeViewDescription *)view { - (void)updateSelectedButton:(UICompositeViewDescription *)view {
_historyButton.selected = [view equal:HistoryListView.compositeViewDescription] || _historyButton.selected = [view equal:HistoryListView.compositeViewDescription] ||
[view equal:HistoryDetailsView.compositeViewDescription]; [view equal:HistoryDetailsView.compositeViewDescription] ||
[view equal:ConferenceHistoryDetailsView.compositeViewDescription];
_contactsButton.selected = [view equal:ContactsListView.compositeViewDescription] || _contactsButton.selected = [view equal:ContactsListView.compositeViewDescription] ||
[view equal:ContactDetailsView.compositeViewDescription]; [view equal:ContactDetailsView.compositeViewDescription];
_dialerButton.selected = [view equal:DialerView.compositeViewDescription]; _dialerButton.selected = [view equal:DialerView.compositeViewDescription];

View file

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

View file

@ -1,37 +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 "UIRoundedImageView.h"
#import "LinphoneManager.h"
#import "UIInterfaceStyleButton.h"
#define CONFERENCE_CELL_HEIGHT 60
@interface UICallConferenceCell : UITableViewCell
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *kickButton;
@property(nonatomic, setter=setParticipant:) LinphoneParticipant *participant;
- (id)initWithIdentifier:(NSString *)identifier;
- (IBAction)onKickClick:(id)sender;
@end

View file

@ -1,70 +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 "linphoneapp-Swift.h"
#import "UICallConferenceCell.h"
#import "Utils.h"
#import "PhoneMainView.h"
@implementation UICallConferenceCell
- (id)initWithIdentifier:(NSString *)identifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
if (self != nil) {
NSArray *arrayOfViews =
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
if ([arrayOfViews count] >= 1) {
// resize cell to match .nib size. It is needed when resized the cell to
// correctly adapt its height too
UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:0]);
[self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
[self addSubview:sub];
}
}
return self;
}
- (void)setParticipant:(LinphoneParticipant *)p {
_participant = p;
if (!p) {
return;
}
const LinphoneAddress *addr = linphone_participant_get_address(p);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
_durationLabel.text = [LinphoneUtils durationToString:[NSDate date].timeIntervalSince1970 - linphone_participant_get_creation_time(p)];
_kickButton.hidden = CallManager.instance.isInConferenceAsGuest;
}
- (IBAction)onKickClick:(id)sender {
if (!_participant) {
return;
}
if ([CallManager callKitEnabled]) {
LinphoneCall *call = [CallManager.instance getCallForParticipant:_participant];
if (call) {
[CallManager.instance setHeldWithCall:call hold:true];
}
}
linphone_conference_remove_participant_2([CallManager.instance getConference], _participant);
}
@end

View file

@ -1,34 +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 "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;
- (id)initWithIdentifier:(NSString *)identifier;
- (void)setCall:(LinphoneCall *)call;
@end

View file

@ -1,60 +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 "UICallPausedCell.h"
#import "Utils.h"
@implementation UICallPausedCell
- (id)initWithIdentifier:(NSString *)identifier {
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
if (self != nil) {
NSArray *arrayOfViews =
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
if ([arrayOfViews count] >= 1) {
// resize cell to match .nib size. It is needed when resized the cell to
// correctly adapt its height too
UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:0]);
[self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
[self addSubview:sub];
}
}
return self;
}
- (void)setCall:(LinphoneCall *)call {
// if no call is provided, we assume that this is a conference
if (!call || linphone_call_get_conference(call)) {
[_pauseButton setType:UIPauseButtonType_Conference call:call];
_nameLabel.text = NSLocalizedString(@"Conference", nil);
[_avatarImage setImage:[UIImage imageNamed:@"options_start_conference_default.png"]
bordered:NO
withRoundedRadius:YES];
_durationLabel.text = @"";
} else {
[_pauseButton setType:UIPauseButtonType_Call call:call];
const LinphoneAddress *addr = linphone_call_get_remote_address(call);
[ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
_durationLabel.text = [LinphoneUtils durationToString:linphone_call_get_duration(call)];
}
[_pauseButton update];
}
@end

View file

@ -24,5 +24,6 @@
@interface UICamSwitch : UIIconButton @interface UICamSwitch : UIIconButton
@property(nonatomic, weak) IBOutlet UIView *preview; @property(nonatomic, weak) IBOutlet UIView *preview;
+ (void) switchCamera;
@end @end

View file

@ -34,6 +34,10 @@ INIT_WITH_COMMON_CF {
#pragma mark - #pragma mark -
- (void)touchUp:(id)sender { - (void)touchUp:(id)sender {
[UICamSwitch switchCamera];
}
+ (void) switchCamera {
const char *currentCamId = (char *)linphone_core_get_video_device(LC); const char *currentCamId = (char *)linphone_core_get_video_device(LC);
const char **cameras = linphone_core_get_video_devices(LC); const char **cameras = linphone_core_get_video_devices(LC);
const char *newCamId = NULL; const char *newCamId = NULL;
@ -50,10 +54,6 @@ INIT_WITH_COMMON_CF {
if (newCamId) { if (newCamId) {
LOGI(@"Switching from [%s] to [%s]", currentCamId, newCamId); LOGI(@"Switching from [%s] to [%s]", currentCamId, newCamId);
linphone_core_set_video_device(LC, newCamId); linphone_core_set_video_device(LC, newCamId);
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call != NULL) {
linphone_call_update(call, NULL);
}
} }
} }

View file

@ -29,6 +29,9 @@
#define IMAGE_DEFAULT_MARGIN 5 #define IMAGE_DEFAULT_MARGIN 5
#define VOICE_RECORDING_PLAYER_HEIGHT 60 #define VOICE_RECORDING_PLAYER_HEIGHT 60
#define VOICE_RECORDING_PLAYER_WIDTH 300 #define VOICE_RECORDING_PLAYER_WIDTH 300
#define CONFERENCE_INVITATION_HEIGHT 210
#define CONFERENCE_INVITATION_WIDTH 300
@interface UIChatBubbleTextCell : UITableViewCell <UIDocumentPickerDelegate, UITableViewDataSource,UITableViewDelegate> @interface UIChatBubbleTextCell : UITableViewCell <UIDocumentPickerDelegate, UITableViewDataSource,UITableViewDelegate>
@ -66,6 +69,7 @@
@property (weak, nonatomic) IBOutlet UIImageView *replyTransferIcon; @property (weak, nonatomic) IBOutlet UIImageView *replyTransferIcon;
@property (weak, nonatomic) IBOutlet UILabel *replyTransferLabel; @property (weak, nonatomic) IBOutlet UILabel *replyTransferLabel;
@property (weak, nonatomic) IBOutlet UIView *photoCellContentView; @property (weak, nonatomic) IBOutlet UIView *photoCellContentView;
@property UIView *icsBubbleView;
@property(nonatomic) BOOL isFirst; @property(nonatomic) BOOL isFirst;

View file

@ -29,6 +29,8 @@
@implementation UIChatBubbleTextCell @implementation UIChatBubbleTextCell
#pragma mark - Lifecycle Functions #pragma mark - Lifecycle Functions
@ -43,6 +45,11 @@
UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:arrayOfViews.count - 1]); UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:arrayOfViews.count - 1]);
[self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)]; [self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
[self addSubview:sub]; [self addSubview:sub];
self.icsBubbleView = [[ICSBubbleView alloc] init];
self.icsBubbleView.frame = CGRectMake(_messageText.frame.origin.x, _messageText.frame.origin.y+25, CONFERENCE_INVITATION_WIDTH-80, CONFERENCE_INVITATION_HEIGHT-20);
[self.innerView addSubview:self.icsBubbleView];
[(ICSBubbleView*)self.icsBubbleView setLayoutConstraintsWithView:self.backgroundColorImage];
} }
} }
@ -275,6 +282,18 @@
_replyView.view.hidden = true; _replyView.view.hidden = true;
} }
// ICS for conference invitations
if ([ICSBubbleView isConferenceInvitationMessageWithCmessage:self.message]) {
[(ICSBubbleView*)self.icsBubbleView setFromChatMessageWithCmessage:self.message];
self.icsBubbleView.hidden = false;
_messageText.hidden = true;
} else {
self.icsBubbleView.hidden = true;
_messageText.hidden = false;
}
} }
- (void)setEditing:(BOOL)editing { - (void)setEditing:(BOOL)editing {
@ -470,6 +489,11 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
} }
+ (CGSize)ViewHeightForMessageText:(LinphoneChatMessage *)chat withWidth:(int)width textForImdn:(NSString *)imdnText { + (CGSize)ViewHeightForMessageText:(LinphoneChatMessage *)chat withWidth:(int)width textForImdn:(NSString *)imdnText {
if ([ICSBubbleView isConferenceInvitationMessageWithCmessage:chat]) {
return CGSizeMake(CONFERENCE_INVITATION_WIDTH, CONFERENCE_INVITATION_HEIGHT);
}
NSString *messageText = [UIChatBubbleTextCell TextMessageForChat:chat]; NSString *messageText = [UIChatBubbleTextCell TextMessageForChat:chat];
static UIFont *messageFont = nil; static UIFont *messageFont = nil;

View file

@ -86,4 +86,5 @@
- (void)clearCache:(NSArray *)exclude; - (void)clearCache:(NSArray *)exclude;
- (IBAction)onRightSwipe:(id)sender; - (IBAction)onRightSwipe:(id)sender;
@end @end

View file

@ -22,6 +22,7 @@
#import "LinphoneAppDelegate.h" #import "LinphoneAppDelegate.h"
#import "Utils.h" #import "Utils.h"
#import "SideMenuView.h" #import "SideMenuView.h"
#import "linphoneapp-Swift.h"
@implementation UICompositeViewDescription @implementation UICompositeViewDescription
@ -304,12 +305,15 @@
return nil; return nil;
} }
- (void)clearCache:(NSArray *)exclude { - (void)clearCache:(NSArray *)exclude {
for (NSString *key in [viewControllerCache allKeys]) { for (NSString *key in [viewControllerCache allKeys]) {
bool remove = true; bool remove = true;
/*ImagePickerView can be used as popover and we do NOT want to free it*/; /*ImagePickerView can be used as popover and we do NOT want to free it*/;
if ([key isEqualToString:ImagePickerView.compositeViewDescription.name]) { if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:ActiveCallOrConferenceView.compositeViewDescription.name]) {
remove = false; remove = false;
} else if (exclude != nil) { } else if (exclude != nil) {
for (UICompositeViewDescription *description in exclude) { for (UICompositeViewDescription *description in exclude) {

View file

@ -46,12 +46,14 @@ typedef void (^UIConfirmationBlock)(void);
@property(weak, nonatomic) IBOutlet UIRoundBorderedButton *cancelButton; @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *cancelButton;
@property (weak, nonatomic) IBOutlet UIImageView *securityImage; @property (weak, nonatomic) IBOutlet UIImageView *securityImage;
@property (weak, nonatomic) IBOutlet UIImageView *forwardImage; @property (weak, nonatomic) IBOutlet UIImageView *forwardImage;
@property (weak, nonatomic) IBOutlet UIImageView *groupCallImage;
@property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton; @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton;
@property (weak, nonatomic) IBOutlet UIView *authView; @property (weak, nonatomic) IBOutlet UIView *authView;
@property(weak, nonatomic) IBOutlet UILabel *titleLabel; @property(weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UIButton *authButton; @property (weak, nonatomic) IBOutlet UIButton *authButton;
- (void)setSpecialColor; - (void)setSpecialColor;
-(void) setWhiteCancel;
- (IBAction)onCancelClick:(id)sender; - (IBAction)onCancelClick:(id)sender;
- (IBAction)onConfirmationClick:(id)sender; - (IBAction)onConfirmationClick:(id)sender;
- (IBAction)onAuthClick:(id)sender; - (IBAction)onAuthClick:(id)sender;

View file

@ -19,6 +19,7 @@
#import "UIConfirmationDialog.h" #import "UIConfirmationDialog.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "linphoneapp-Swift.h""
@implementation UIConfirmationDialog @implementation UIConfirmationDialog
+ (UIConfirmationDialog *)initDialog:(NSString *)cancel + (UIConfirmationDialog *)initDialog:(NSString *)cancel
@ -97,6 +98,13 @@
[[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_A.png"]] CGColor]; [[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_A.png"]] CGColor];
} }
-(void) setWhiteCancel {
[_cancelButton setBackgroundImage:nil forState:UIControlStateNormal];
[_cancelButton setBackgroundColor:UIColor.whiteColor];
[_cancelButton setTitleColor:VoipTheme.voip_dark_gray forState:UIControlStateNormal];
_cancelButton.layer.borderColor = UIColor.whiteColor.CGColor;
}
- (IBAction)onCancelClick:(id)sender { - (IBAction)onCancelClick:(id)sender {
[self.view removeFromSuperview]; [self.view removeFromSuperview];
[self removeFromParentViewController]; [self removeFromParentViewController];

View file

@ -1,112 +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 "linphoneapp-Swift.h"
#import "UIHangUpButton.h"
#import "LinphoneManager.h"
#import "linphoneapp-Swift.h"
@implementation UIHangUpButton
#pragma mark - Static Functions
+ (bool)isInConference:(LinphoneCall *)call {
if (!call)
return false;
return linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
}
+ (int)callCount {
int count = 0;
const MSList *calls = linphone_core_get_calls(LC);
while (calls != 0) {
if (![UIHangUpButton isInConference:((LinphoneCall *)calls->data)]) {
count++;
}
calls = calls->next;
}
return count;
}
#pragma mark - Lifecycle Functions
- (void)initUIHangUpButton {
[self addTarget:self action:@selector(touchUp:) forControlEvents:UIControlEventTouchUpInside];
}
- (id)init {
self = [super init];
if (self) {
[self initUIHangUpButton];
}
return self;
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
[self initUIHangUpButton];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initUIHangUpButton];
}
return self;
}
#pragma mark -
- (void)update {
if (linphone_core_get_calls_nb(LC) == 1 || // One call
linphone_core_get_current_call(LC) != NULL || // In call
linphone_core_is_in_conference(LC) || // In conference
(linphone_core_get_conference_size(LC) > 0 && [UIHangUpButton callCount] == 0) // Only one conf
) {
[self setEnabled:true];
return;
}
[self setEnabled:false];
}
#pragma mark - Action Functions
- (void)touchUp:(id)sender {
LinphoneCall *currentcall = linphone_core_get_current_call(LC);
if (linphone_core_is_in_conference(LC) || // In conference
(linphone_core_get_conference_size(LC) > 0 && [UIHangUpButton callCount] == 0) // Only one conf
) {
LinphoneManager.instance.conf = TRUE;
linphone_core_terminate_conference(LC);
} else if (currentcall != NULL) {
[CallManager.instance terminateCallWithCall:currentcall];
} else {
const MSList *calls = linphone_core_get_calls(LC);
if (bctbx_list_size(calls) == 1) { // Only one call
[CallManager.instance terminateCallWithCall:(calls->data)];
}
}
}
@end

View file

@ -21,6 +21,7 @@
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "Utils.h" #import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation UIHistoryCell @implementation UIHistoryCell
@ -59,10 +60,16 @@
if (callLog != NULL) { if (callLog != NULL) {
HistoryDetailsView *view = VIEW(HistoryDetailsView); HistoryDetailsView *view = VIEW(HistoryDetailsView);
if (linphone_call_log_get_call_id(callLog) != NULL) { if (linphone_call_log_get_call_id(callLog) != NULL) {
// Go to History details view if (linphone_call_log_was_conference(callLog)) {
[view setCallLogId:[NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog)]]; ConferenceHistoryDetailsView *view = VIEW(ConferenceHistoryDetailsView);
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
[view setCallLogWithCallLog:callLog];
} else {
// Go to History details view
[view setCallLogId:[NSString stringWithUTF8String:linphone_call_log_get_call_id(callLog)]];
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
} }
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
} }
} }
@ -80,32 +87,39 @@
LOGW(@"Cannot update history cell: null callLog"); LOGW(@"Cannot update history cell: null callLog");
return; return;
} }
// Set up the cell... // Set up the cell...
const LinphoneAddress *addr; if (linphone_call_log_was_conference(callLog)) {
UIImage *image; const char *subject = linphone_conference_info_get_subject(linphone_call_log_get_conference_info(callLog));
if (linphone_call_log_get_dir(callLog) == LinphoneCallIncoming) { displayNameLabel.text = [NSString stringWithFormat:@"%s",subject];
if (linphone_call_log_get_status(callLog) != LinphoneCallMissed) { [_avatarImage setImage:[UIImage imageNamed:@"voip_multiple_contacts_avatar"]];
image = [UIImage imageNamed:@"call_status_incoming.png"]; _stateImage.hidden = true;
} else {
image = [UIImage imageNamed:@"call_status_missed.png"];
}
addr = linphone_call_log_get_from_address(callLog);
} else { } else {
image = [UIImage imageNamed:@"call_status_outgoing.png"]; _stateImage.hidden = false;
addr = linphone_call_log_get_to_address(callLog); const LinphoneAddress *addr;
} UIImage *image;
_stateImage.image = image; if (linphone_call_log_get_dir(callLog) == LinphoneCallIncoming) {
if (linphone_call_log_get_status(callLog) != LinphoneCallMissed) {
[ContactDisplay setDisplayNameLabel:displayNameLabel forAddress:addr]; image = [UIImage imageNamed:@"call_status_incoming.png"];
} else {
size_t count = bctbx_list_size(linphone_call_log_get_user_data(callLog)) + 1; image = [UIImage imageNamed:@"call_status_missed.png"];
if (count > 1) { }
displayNameLabel.text = addr = linphone_call_log_get_from_address(callLog);
} else {
image = [UIImage imageNamed:@"call_status_outgoing.png"];
addr = linphone_call_log_get_to_address(callLog);
}
_stateImage.image = image;
[ContactDisplay setDisplayNameLabel:displayNameLabel forAddress:addr];
size_t count = bctbx_list_size(linphone_call_log_get_user_data(callLog)) + 1;
if (count > 1) {
displayNameLabel.text =
[displayNameLabel.text stringByAppendingString:[NSString stringWithFormat:@" (%lu)", count]]; [displayNameLabel.text stringByAppendingString:[NSString stringWithFormat:@" (%lu)", count]];
}
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
} }
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
} }
- (void)setEditing:(BOOL)editing { - (void)setEditing:(BOOL)editing {

View file

@ -1,38 +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 "UIToggleButton.h"
#include "linphone/linphonecore.h"
typedef enum _UIPauseButtonType {
UIPauseButtonType_CurrentCall,
UIPauseButtonType_Call,
UIPauseButtonType_Conference
} UIPauseButtonType;
@interface UIPauseButton : UIToggleButton<UIToggleButtonDelegate> {
@private
UIPauseButtonType type;
LinphoneCall* call;
}
- (void)setType:(UIPauseButtonType) type call:(LinphoneCall*)call;
@end

View file

@ -1,181 +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 "linphoneapp-Swift.h"
#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_conference_leave(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_conference_enter(CallManager.instance.getConference);
// Fake event
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
break;
}
case UIPauseButtonType_CurrentCall: {
LinphoneCall *currentCall = [UIPauseButton getCall];
if ([CallManager callKitEnabled]) {
[CallManager.instance setHeldWithCall:currentCall hold:false];
} else {
linphone_call_resume(currentCall);
}
break;
}
}
}
- (bool)onUpdate {
bool ret = false;
LinphoneCall *c = call;
switch (type) {
case UIPauseButtonType_Conference: {
self.enabled = CallManager.instance.getConference && (linphone_conference_get_participant_count(CallManager.instance.getConference)> 0);
if (self.enabled) {
ret = (!CallManager.instance.isInConference);
}
break;
}
case UIPauseButtonType_CurrentCall:
c = [UIPauseButton getCall];
case UIPauseButtonType_Call: {
if (c != nil) {
LinphoneCallState state = linphone_call_get_state(c);
ret = (state == LinphoneCallPaused || state == LinphoneCallPausing);
self.enabled = !linphone_core_sound_resources_locked(LC) &&
(state == LinphoneCallPaused || state == LinphoneCallPausing ||
state == LinphoneCallStreamsRunning);
} else {
self.enabled = FALSE;
}
break;
}
}
return ret;
}
@end

View file

@ -1,62 +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 "linphoneapp-Swift.h"
#import <AudioToolbox/AudioToolbox.h>
#import "UISpeakerButton.h"
#import "Utils.h"
#import "LinphoneManager.h"
#include "linphone/linphonecore.h"
@implementation UISpeakerButton
INIT_WITH_COMMON_CF {
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(audioRouteChangeListenerCallback:)
name:AVAudioSessionRouteChangeNotification
object:nil];
return self;
}
- (void)onOn {
[CallManager.instance changeRouteToSpeaker];
}
- (void)onOff {
[CallManager.instance changeRouteToDefault];
}
- (void)dealloc {
[NSNotificationCenter.defaultCenter removeObserver:self];
}
#pragma mark - UIToggleButtonDelegate Functions
- (void)audioRouteChangeListenerCallback:(NSNotification *)notif {
dispatch_async(dispatch_get_main_queue(), ^{
[self update];});
}
- (bool)onUpdate {
return [CallManager.instance isSpeakerEnabled];
}
@end

View file

@ -1,31 +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/Foundation.h>
#import <UIKit/UIKit.h>
@interface VideoZoomHandler : NSObject {
float zoomLevel, cx, cy;
UIView* videoView;
}
- (void) setup: (UIView*) videoView;
- (void) resetZoom;
@end

View file

@ -1,111 +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 "VideoZoomHandler.h"
#include "linphone/linphonecore.h"
#import "LinphoneManager.h"
@implementation VideoZoomHandler
- (void)zoomInOut:(UITapGestureRecognizer *)reco {
if (zoomLevel != 1)
zoomLevel = 1;
else
zoomLevel = 2;
if (zoomLevel != 1) {
CGPoint point = [reco locationInView:videoView];
cx = point.x / videoView.frame.size.width;
cy = 1 - point.y / videoView.frame.size.height;
} else {
cx = cy = 0.5;
}
linphone_call_zoom_video(linphone_core_get_current_call(LC), zoomLevel, &cx, &cy);
}
- (void)videoPan:(UIPanGestureRecognizer *)reco {
if (zoomLevel <= 1.0)
return;
float x, y;
CGPoint translation = [reco translationInView:videoView];
if ([reco state] == UIGestureRecognizerStateEnded) {
cx -= translation.x / videoView.frame.size.width;
cy += translation.y / videoView.frame.size.height;
x = cx;
y = cy;
} else if ([reco state] == UIGestureRecognizerStateChanged) {
x = cx - translation.x / videoView.frame.size.width;
y = cy + translation.y / videoView.frame.size.height;
[reco setTranslation:CGPointMake(0, 0) inView:videoView];
} else {
return;
}
linphone_call_zoom_video(linphone_core_get_current_call(LC), zoomLevel, &x, &y);
cx = x;
cy = y;
}
- (void)pinch:(UIPinchGestureRecognizer *)reco {
float s = zoomLevel;
// CGPoint point = [reco locationInView:videoGroup];
// float ccx = cx + (point.x / videoGroup.frame.size.width - 0.5) / s;
// float ccy = cy - (point.y / videoGroup.frame.size.height - 0.5) / s;
if ([reco state] == UIGestureRecognizerStateEnded) {
zoomLevel = MAX(MIN(zoomLevel * reco.scale, 3.0), 1.0);
s = zoomLevel;
// cx = ccx;
// cy = ccy;
} else if ([reco state] == UIGestureRecognizerStateChanged) {
s = zoomLevel * reco.scale;
s = MAX(MIN(s, 3.0), 1.0);
} else if ([reco state] == UIGestureRecognizerStateBegan) {
} else {
return;
}
linphone_call_zoom_video(linphone_core_get_current_call(LC), s, &cx, &cy);
}
- (void)resetZoom {
zoomLevel = 1;
cx = cy = 0.5;
}
- (void)setup:(UIView *)view {
videoView = view;
UITapGestureRecognizer *doubleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(zoomInOut:)];
[doubleFingerTap setNumberOfTapsRequired:2];
[doubleFingerTap setNumberOfTouchesRequired:1];
[videoView addGestureRecognizer:doubleFingerTap];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(videoPan:)];
[videoView addGestureRecognizer:pan];
UIPinchGestureRecognizer *pinchReco =
[[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
[videoView addGestureRecognizer:pinchReco];
[self resetZoom];
}
@end

View file

@ -32,6 +32,11 @@
+ (void)log:(OrtpLogLevel)severity file:(const char *)file line:(int)line format:(NSString *)format, ...; + (void)log:(OrtpLogLevel)severity file:(const char *)file line:(int)line format:(NSString *)format, ...;
+ (void)enableLogs:(OrtpLogLevel)level; + (void)enableLogs:(OrtpLogLevel)level;
+ (void)directLog:(OrtpLogLevel)level text:(NSString *)text; + (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); void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args);
@end @end

View file

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

View file

@ -17,12 +17,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#import "linphoneapp-Swift.h"
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#import <AudioToolbox/AudioServices.h> #import <AudioToolbox/AudioServices.h>
#import "LinphoneAppDelegate.h" #import "LinphoneAppDelegate.h"
#import "Log.h" #import "Log.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "linphoneapp-Swift.h"
static RootViewManager *rootViewManagerInstance = nil; static RootViewManager *rootViewManagerInstance = nil;
@ -373,8 +374,16 @@ static RootViewManager *rootViewManagerInstance = nil;
} }
break; break;
} }
case LinphoneCallOutgoingInit: { case LinphoneCallOutgoingInit:
[self changeCurrentView:CallOutgoingView.compositeViewDescription]; case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallOutgoingProgress:
case LinphoneCallOutgoingRinging: {
CallAppData *data = [CallManager getAppDataWithCall:call];
if (!data.isConference) {
OutgoingCallView *v = VIEW(OutgoingCallView);
[self changeCurrentView:OutgoingCallView.compositeViewDescription];
[v setCallWithCall:call];
}
break; break;
} }
case LinphoneCallPausedByRemote: case LinphoneCallPausedByRemote:
@ -382,47 +391,16 @@ static RootViewManager *rootViewManagerInstance = nil;
if (![LinphoneManager.instance isCTCallCenterExist]) { if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/ /*only register CT call center CB for connected call*/
[LinphoneManager.instance setupGSMInteraction]; [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; break;
} }
case LinphoneCallError: { case LinphoneCallError: {
[self displayCallError:call message:message]; [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 LinphoneCallEarlyUpdatedByRemote:
case LinphoneCallEarlyUpdating: case LinphoneCallEarlyUpdating:
case LinphoneCallIdle: case LinphoneCallIdle:
break; break;
case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallOutgoingProgress: {
break;
}
case LinphoneCallReleased: case LinphoneCallReleased:
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@ -431,7 +409,6 @@ static RootViewManager *rootViewManagerInstance = nil;
}); });
} }
break; break;
case LinphoneCallOutgoingRinging:
case LinphoneCallPaused: case LinphoneCallPaused:
case LinphoneCallPausing: case LinphoneCallPausing:
case LinphoneCallRefered: case LinphoneCallRefered:
@ -634,6 +611,15 @@ static RootViewManager *rootViewManagerInstance = nil;
return [mainViewController getCurrentViewController]; 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 { - (void)changeCurrentView:(UICompositeViewDescription *)view {
[self _changeCurrentView:view transition:nil animated:ANIMATED]; [self _changeCurrentView:view transition:nil animated:ANIMATED];
} }
@ -765,10 +751,10 @@ static RootViewManager *rootViewManagerInstance = nil;
[CallManager.instance acceptCallWithCall:call hasVideo:YES]; [CallManager.instance acceptCallWithCall:call hasVideo:YES];
} else { } else {
AudioServicesPlaySystemSound(lm.sounds.vibrate); AudioServicesPlaySystemSound(lm.sounds.vibrate);
CallIncomingView *view = VIEW(CallIncomingView); IncomingCallView *view = VIEW(IncomingCallView);
[self changeCurrentView:view.compositeViewDescription]; [self changeCurrentView:view.compositeViewDescription];
[view setCall:call]; [view setCallWithCall:call];
[view setDelegate:self]; //CDFIX [view setDelegate:self];
} }
} }
} }

View file

@ -27,6 +27,7 @@
#import "ShopView.h" #import "ShopView.h"
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "RecordingsListView.h" #import "RecordingsListView.h"
#import "linphoneapp-Swift.h"
@implementation SideMenuEntry @implementation SideMenuEntry
@ -101,6 +102,15 @@
changeCurrentView:ShopView.compositeViewDescription]; changeCurrentView:ShopView.compositeViewDescription];
}]]; }]];
} }
[_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:VoipTexts.conference_scheduled
image:[UIImage imageNamed:@"voip_conference_new.png"]
tapBlock:^() {
[PhoneMainView.instance
changeCurrentView:ScheduledConferencesView.compositeViewDescription];
}]];
[_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"About", nil) [_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"About", nil)
image:[UIImage imageNamed:@"menu_about.png"] image:[UIImage imageNamed:@"menu_about.png"]
tapBlock:^() { tapBlock:^() {

View file

@ -27,6 +27,8 @@ import AVFoundation
@objc class CallAppData: NSObject { @objc class CallAppData: NSObject {
@objc var batteryWarningShown = false @objc var batteryWarningShown = false
@objc var videoRequested = false /*set when user has requested for video*/ @objc var videoRequested = false /*set when user has requested for video*/
@objc var isConference = true
} }
/* /*
@ -245,6 +247,19 @@ import AVFoundation
callParams.recordFile = writablePath callParams.recordFile = writablePath
if let chatView : ChatConversationView = PhoneMainView.instance().VIEW(ChatConversationView.compositeViewDescription()), chatView.isVoiceRecording {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.")
chatView.stopVoiceRecording()
}
if (call.callLog?.wasConference() == true) {
// Prevent incoming group call to start in audio only layout
// Do the same as the conference waiting room
callParams.videoEnabled = true
callParams.videoDirection = Core.get().videoActivationPolicy?.automaticallyInitiate == true ? .SendRecv : .RecvOnly
Log.i("[Context] Enabling video on call params to prevent audio-only layout when answering")
}
try call.acceptWithParams(params: callParams) try call.acceptWithParams(params: callParams)
} catch { } catch {
Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)") Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)")
@ -252,32 +267,41 @@ import AVFoundation
} }
// for outgoing call. There is not yet callId // for outgoing call. There is not yet callId
@objc func startCall(addr: OpaquePointer?, isSas: Bool) { @objc func startCall(addr: OpaquePointer?, isSas: Bool, isVideo: Bool, isConference: Bool = false) {
if (addr == nil) { if (addr == nil) {
print("Can not start a call with null address!") print("Can not start a call with null address!")
return return
} }
let sAddr = Address.getSwiftObject(cObject: addr!) let sAddr = Address.getSwiftObject(cObject: addr!)
if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && !isInConference()) { if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && lc?.conference?.isIn != true) {
let uuid = UUID() let uuid = UUID()
let name = FastAddressBook.displayName(for: addr) ?? "unknow" let name = FastAddressBook.displayName(for: addr) ?? "unknow"
let handle = CXHandle(type: .generic, value: sAddr.asStringUriOnly()) let handle = CXHandle(type: .generic, value: sAddr.asStringUriOnly())
let startCallAction = CXStartCallAction(call: uuid, handle: handle) let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction) let transaction = CXTransaction(action: startCallAction)
let callInfo = CallInfo.newOutgoingCallInfo(addr: sAddr, isSas: isSas, displayName: name) let callInfo = CallInfo.newOutgoingCallInfo(addr: sAddr, isSas: isSas, displayName: name, isVideo: isVideo, isConference:isConference)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid) providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "") providerDelegate.uuids.updateValue(uuid, forKey: "")
setHeldOtherCalls(exceptCallid: "") setHeldOtherCalls(exceptCallid: "")
requestTransaction(transaction, action: "startCall") requestTransaction(transaction, action: "startCall")
}else { }else {
try? doCall(addr: sAddr, isSas: isSas) try? doCall(addr: sAddr, isSas: isSas, isVideo:isVideo, isConference:isConference)
}
}
func startCall(addr:String, isSas: Bool = false, isVideo: Bool, isConference: Bool = false) {
do {
let address = try Factory.Instance.createAddress(addr: addr)
startCall(addr: address.getCobject,isSas: isSas, isVideo: isVideo, isConference:isConference)
} catch {
Log.e("[CallManager] unable to create address for a new outgoing call : \(addr) \(error) ")
} }
} }
func doCall(addr: Address, isSas: Bool) throws { func doCall(addr: Address, isSas: Bool, isVideo: Bool, isConference:Bool = false) throws {
let displayName = FastAddressBook.displayName(for: addr.getCobject) let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil) let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil)
@ -306,6 +330,18 @@ import AVFoundation
if (isSas) { if (isSas) {
lcallParams.mediaEncryption = .ZRTP lcallParams.mediaEncryption = .ZRTP
} }
if (isConference) {
if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
lcallParams.videoEnabled = true
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
} else {
lcallParams.videoEnabled = false
}
} else {
lcallParams.videoEnabled = isVideo
}
let call = CallManager.instance().lc!.inviteAddressWithParams(addr: addr, params: lcallParams) let call = CallManager.instance().lc!.inviteAddressWithParams(addr: addr, params: lcallParams)
if (call != nil) { if (call != nil) {
// The LinphoneCallAppData object should be set on call creation with callback // The LinphoneCallAppData object should be set on call creation with callback
@ -316,6 +352,7 @@ import AVFoundation
Log.directLog(BCTBX_LOG_ERROR, text: "New call instanciated but app data was not set. Expect it to crash.") Log.directLog(BCTBX_LOG_ERROR, text: "New call instanciated but app data was not set. Expect it to crash.")
/* will be used later to notify user if video was not activated because of the linphone core*/ /* will be used later to notify user if video was not activated because of the linphone core*/
} else { } else {
data!.isConference = isConference
data!.videoRequested = lcallParams.videoEnabled data!.videoRequested = lcallParams.videoEnabled
CallManager.setAppData(sCall: call!, appData: data) CallManager.setAppData(sCall: call!, appData: data)
} }
@ -396,6 +433,14 @@ import AVFoundation
} }
func setHeld(call: Call, hold: Bool) { func setHeld(call: Call, hold: Bool) {
#if targetEnvironment(simulator)
if (hold) {
try?call.pause()
} else {
try?call.resume()
}
#else
let callid = call.callLog?.callId ?? "" let callid = call.callLog?.callId ?? ""
let uuid = providerDelegate.uuids["\(callid)"] let uuid = providerDelegate.uuids["\(callid)"]
if (uuid == nil) { if (uuid == nil) {
@ -405,6 +450,7 @@ import AVFoundation
let setHeldAction = CXSetHeldCallAction(call: uuid!, onHold: hold) let setHeldAction = CXSetHeldCallAction(call: uuid!, onHold: hold)
let transaction = CXTransaction(action: setHeldAction) let transaction = CXTransaction(action: setHeldAction)
requestTransaction(transaction, action: "setHeld") requestTransaction(transaction, action: "setHeld")
#endif
} }
@objc func setHeldOtherCalls(exceptCallid: String) { @objc func setHeldOtherCalls(exceptCallid: String) {
@ -469,19 +515,9 @@ import AVFoundation
} }
} }
func onConferenceStateChanged(core: Core, conference: Conference, state: Conference.State) { func isConferenceCall(call:Call) -> Bool {
if (state == .Terminated) { let remoteAddress = call.remoteAddress?.asStringUriOnly()
CallManager.instance().conference = nil return remoteAddress?.contains("focus") == true || remoteAddress?.contains("audiovideo") == true
}
}
func onAudioDevicesListUpdated(core: Core) {
let bluetoothAvailable = isBluetoothAvailable();
var dict = Dictionary<String, Bool>()
dict["available"] = bluetoothAvailable
NotificationCenter.default.post(name: Notification.Name("LinphoneBluetoothAvailabilityUpdate"), object: self, userInfo: dict)
} }
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) { func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
@ -496,12 +532,40 @@ import AVFoundation
let appData = CallAppData() let appData = CallAppData()
CallManager.setAppData(sCall: call, appData: appData) CallManager.setAppData(sCall: call, appData: appData)
} }
if let conference = call.conference, ConferenceViewModel.shared.conference.value == nil {
Log.i("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it")
ConferenceViewModel.shared.initConference(conference)
ConferenceViewModel.shared.configureConference(conference)
}
switch cstate { switch cstate {
case .IncomingReceived: case .IncomingReceived:
let addr = call.remoteAddress; let addr = call.remoteAddress
let displayName = FastAddressBook.displayName(for: addr?.getCobject) ?? "Unknown" var displayName = ""
let isConference = isConferenceCall(call: call)
let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
if (isConference) {
if (isEarlyConference) {
displayName = VoipTexts.conference_incoming_title
} else {
displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))"
}
} else {
displayName = FastAddressBook.displayName(for: addr?.getCobject) ?? "Unknown"
}
if (CallManager.callKitEnabled()) { if (CallManager.callKitEnabled()) {
if (isEarlyConference) {
CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
if (uuid != nil) {
displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))"
CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
}
}
}
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"] let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
if (uuid != nil) { if (uuid != nil) {
// Tha app is now registered, updated the call already existed. // Tha app is now registered, updated the call already existed.
@ -672,116 +736,46 @@ import AVFoundation
return speakerCard != nil ? speakerCard : earpieceCard return speakerCard != nil ? speakerCard : earpieceCard
} }
// Local Conference
// Conference
@objc func hostConference() -> Bool {
return conference != nil
}
func addAllToConference() { @objc func startLocalConference() {
if (conference == nil) { if (CallManager.callKitEnabled()) {
guard let cp = try?lc?.createConferenceParams(conference: conference) else { let calls = lc?.calls
Log.directLog(BCTBX_LOG_ERROR, text: "Unable to create conference parameters") if (calls == nil || calls!.isEmpty) {
return return
} }
if let currentCall = lc?.currentCall, let currentParams = currentCall.currentParams { let firstCall = calls!.first?.callLog?.callId ?? ""
cp.videoEnabled = currentParams.videoEnabled let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
}
conference = try?lc?.createConferenceWithParams(params: cp) let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
} if (currentUuid == nil) {
lc?.calls.forEach { call in Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
if (call.conference == nil || call.conference?.participantCount == 1) {
try?conference?.addParticipant(call: call)
}
}
}
@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
} }
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()
} }
return found
} }
@objc func getCallFor(participant : OpaquePointer) -> OpaquePointer? { func addAllToLocalConference() {
let p = Participant.getSwiftObject(cObject: participant) do {
guard let core = lc else { if let core = lc, let params = try? core.createConferenceParams(conference: nil) {
return nil params.videoEnabled = false // We disable video for local conferencing (cf Android)
} let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params)
var call:Call? = nil try conference?.addParticipants(calls: core.calls)
core.calls.forEach { (callIt) in
let c = callIt.conference
c?.participantList.forEach { (p2) in
if (p2.address?.asStringUriOnly() == p.address?.asStringUriOnly()) {
call = callIt
return
}
} }
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)")
} }
return call?.getCobject
} }
@objc func inVideoConf() -> Bool {
guard let core = lc else {
return false
}
let result = isInConference() && (getConference()?.currentParams?.isVideoEnabled == true || core.currentCall?.currentParams?.videoEnabled == true)
NSLog("cdes \(result) \(core.currentCall?.currentParams?.videoEnabled)")
return result
}
@objc func inAudioConf() -> Bool {
guard let core = lc else {
return false
}
return core.conference?.isIn == true && core.conference != nil && core.currentCall?.conference?.currentParams?.isVideoEnabled == false
}
} }

View file

@ -0,0 +1,31 @@
/*
* 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
struct Duration : Comparable {
static func < (lhs: Duration, rhs: Duration) -> Bool {
return lhs.value < rhs.value
}
let value: Int
let display: String
}

View file

@ -0,0 +1,84 @@
/*
* 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 linphonesw
class ScheduledConferenceData {
let conferenceInfo: ConferenceInfo
let expanded = MutableLiveData<Bool>()
let address = MutableLiveData<String>()
let subject = MutableLiveData<String>()
let description = MutableLiveData<String>()
let time = MutableLiveData<String>()
let date = MutableLiveData<String>()
let duration = MutableLiveData<String>()
let organizer = MutableLiveData<String>()
let participantsShort = MutableLiveData<String>()
let participantsExpanded = MutableLiveData<String>()
let rawDate : Date
init (conferenceInfo: ConferenceInfo) {
self.conferenceInfo = conferenceInfo
expanded.value = false
address.value = conferenceInfo.uri?.asStringUriOnly()
subject.value = conferenceInfo.subject
description.value = conferenceInfo.description
time.value = TimestampUtils.timeToString(unixTimestamp: Double(conferenceInfo.dateTime))
date.value = TimestampUtils.toString(unixTimestamp:Double(conferenceInfo.dateTime), onlyDate:true, shortDate:false)
rawDate = Date(timeIntervalSince1970:TimeInterval(conferenceInfo.dateTime))
let durationFormatter = DateComponentsFormatter()
durationFormatter.unitsStyle = .positional
durationFormatter.allowedUnits = [.minute, .second ]
durationFormatter.zeroFormattingBehavior = [ .pad ]
duration.value = conferenceInfo.duration > 0 ? durationFormatter.string(from: TimeInterval(conferenceInfo.duration)) : nil
organizer.value = conferenceInfo.organizer?.addressBookEnhancedDisplayName()
computeParticipantsLists()
}
func destroy() {
}
func toggleExpand() {
expanded.value = expanded.value == false
}
private func computeParticipantsLists() {
participantsShort.value = conferenceInfo.participants.map {(participant) in
String(describing: participant.addressBookEnhancedDisplayName())
}.joined(separator: ", ")
participantsExpanded.value = conferenceInfo.participants.map {(participant) in
String(describing: participant.addressBookEnhancedDisplayName())+" ("+String(describing: participant.asStringUriOnly())+")"
}.joined(separator: "\n")
}
func gotoAssociatedChat() {
}
}

View file

@ -0,0 +1,60 @@
/*
* 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 linphonesw
struct TimeZoneData : Comparable {
let timeZone: TimeZone
static func == (lhs: TimeZoneData, rhs: TimeZoneData) -> Bool {
return lhs.timeZone.identifier == rhs.timeZone.identifier
}
static func < (lhs: TimeZoneData, rhs: TimeZoneData) -> Bool {
return lhs.timeZone.secondsFromGMT() < rhs.timeZone.secondsFromGMT()
}
func descWithOffset() -> String {
return "\(timeZone.identifier) - GMT\(timeZone.offsetInHours())"
}
}
extension TimeZone {
func offsetFromUTC() -> String
{
let localTimeZoneFormatter = DateFormatter()
localTimeZoneFormatter.timeZone = self
localTimeZoneFormatter.dateFormat = "Z"
return localTimeZoneFormatter.string(from: Date())
}
func offsetInHours() -> String
{
let hours = secondsFromGMT()/3600
let minutes = abs(secondsFromGMT()/60) % 60
let tz_hr = String(format: "%+.2d:%.2d", hours, minutes) // "+hh:mm"
return tz_hr
}
}

View file

@ -0,0 +1,249 @@
/*
* 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
* aDouble with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
class ConferenceSchedulingViewModel {
var core : Core { get { Core.get() } }
static let shared = ConferenceSchedulingViewModel()
let subject = MutableLiveData<String>()
let description = MutableLiveData<String>()
let scheduleForLater = MutableLiveData<Bool>()
let scheduledDate = MutableLiveData<Date>()
let scheduledTime = MutableLiveData<Date>()
var scheduledTimeZone = MutableLiveData<Int>()
static let timeZones: [TimeZoneData] = computeTimeZonesList()
var scheduledDuration = MutableLiveData<Int>()
static let durationList: [Duration] = computeDurationList()
let isEncrypted = MutableLiveData<Bool>()
let sendInviteViaChat = MutableLiveData<Bool>()
let sendInviteViaEmail = MutableLiveData<Bool>()
let address = MutableLiveData<Address>()
let conferenceCreationInProgress = MutableLiveData<Bool>()
let conferenceCreationCompletedEvent: MutableLiveData<Pair<String?,String?>> = MutableLiveData()
let onErrorEvent = MutableLiveData<String>()
let continueEnabled: MutableLiveData<Bool> = MutableLiveData()
let selectedAddresses = MutableLiveData<[Address]>([])
private var conferenceScheduler: ConferenceScheduler? = nil
private var hour: Int = 0
private var minutes: Int = 0
private var chatRooomDelegate : ChatRoomDelegate? = nil
private var conferenceSchedulerDelegate : ConferenceSchedulerDelegateStub? = nil
var existingConfInfo:ConferenceInfo? = nil
init () {
conferenceSchedulerDelegate = ConferenceSchedulerDelegateStub(
onStateChanged: { scheduler, state in
Log.i("[Conference Creation] Conference scheduler state is \(state)")
if (state == .Ready) {
Log.i("[Conference Creation] Conference info created, address will be \(scheduler.info?.uri?.asStringUriOnly())")
guard let conferenceAddress = scheduler.info?.uri else {
Log.e("[Conference Creation] conference address is null")
return
}
self.address.value = conferenceAddress
if (self.sendInviteViaChat.value == true) {
// Send conference info even when conf is not scheduled for later
// as the conference server doesn't invite participants automatically
if let chatRoomParams = try?self.core.createDefaultChatRoomParams() {
chatRoomParams.backend = ChatRoomBackend.FlexisipChat
chatRoomParams.groupEnabled = false
chatRoomParams.encryptionEnabled = true
chatRoomParams.subject = self.subject.value!
scheduler.sendInvitations(chatRoomParams: chatRoomParams)
}
} else {
self.conferenceCreationInProgress.value = false
self.conferenceCreationCompletedEvent.value = Pair(conferenceAddress.asStringUriOnly(),self.conferenceScheduler?.info?.subject)
}
}
}, onInvitationsSent: { conferenceScheduler, failedInvitations in
Log.i("[Conference Creation] Conference information successfully sent to all participants")
self.conferenceCreationInProgress.value = false
if (failedInvitations.count > 0) {
failedInvitations.forEach { address in
Log.e("[Conference Creation] Conference information wasn't sent to participant \(address.asStringUriOnly())")
self.onErrorEvent.value = VoipTexts.conference_schedule_info_not_sent_to_participant+" (\(address.username))"
}
}
guard let conferenceAddress = conferenceScheduler.info?.uri else {
Log.e("[Conference Creation] conference address is null")
return
}
self.conferenceCreationCompletedEvent.value = Pair(conferenceAddress.asStringUriOnly(),self.conferenceScheduler?.info?.subject)
}
)
chatRooomDelegate = ChatRoomDelegateStub(
onStateChanged : { (room: ChatRoom, state: ChatRoom.State) -> Void in
if (state == ChatRoom.State.Created) {
Log.i("[Conference Creation] Chat room created")
room.removeDelegate(delegate: self.chatRooomDelegate!)
} else if (state == ChatRoom.State.CreationFailed) {
Log.e("[Conference Creation] Group chat room creation has failed !")
room.removeDelegate(delegate: self.chatRooomDelegate!)
}
}
)
reset()
subject.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
scheduleForLater.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
scheduledDate.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
scheduledTime.observe { _ in
self.continueEnabled.value = self.allMandatoryFieldsFilled()
}
}
func reset() {
subject.value = ""
scheduleForLater.value = false
isEncrypted.value = false
sendInviteViaChat.value = true
sendInviteViaEmail.value = false
let now = Date()
scheduledTime.value = Calendar.current.date(from: Calendar.current.dateComponents([.hour, .minute, .second], from: now))
scheduledDate.value = Calendar.current.date(from: Calendar.current.dateComponents([.year, .month, .day], from: now))
scheduledTimeZone.value = ConferenceSchedulingViewModel.timeZones.indices.filter {
ConferenceSchedulingViewModel.timeZones[$0].timeZone.identifier == NSTimeZone.default.identifier
}.first
scheduledDuration.value = ConferenceSchedulingViewModel.durationList.indices.filter {
ConferenceSchedulingViewModel.durationList[$0].value == 60
}.first
continueEnabled.value = false
selectedAddresses.value = []
existingConfInfo = nil
description.value = ""
}
func destroy() {
conferenceScheduler?.removeDelegate(delegate: conferenceSchedulerDelegate!)
}
func gotoChatRoom() {
}
func createConference() {
if (selectedAddresses.value?.count == 0) {
Log.e("[Conference Creation] Couldn't create conference without any participant!")
return
}
do {
conferenceCreationInProgress.value = true
guard let localAccount = core.defaultAccount, let localAddress = localAccount.params?.identityAddress else {
Log.e("[Conference Creation] Couldn't get local address from default account!")
return
}
conferenceScheduler = try? Core.get().createConferenceScheduler()
conferenceScheduler?.addDelegate(delegate: conferenceSchedulerDelegate!)
guard let conferenceInfo = existingConfInfo != nil ? existingConfInfo : try Factory.Instance.createConferenceInfo() else {
Log.e("[Conference Creation/Update] Failed, unable to get conf info.")
return
}
conferenceInfo.organizer = localAddress
subject.value.map { conferenceInfo.subject = $0}
description.value.map { conferenceInfo.description = $0}
conferenceInfo.participants = selectedAddresses.value!
if (scheduleForLater.value == true) {
let timestamp = getConferenceStartTimestamp()
conferenceInfo.dateTime = time_t(timestamp)
scheduledDuration.value.map { conferenceInfo.duration = UInt(ConferenceSchedulingViewModel.durationList[$0].value) }
}
conferenceScheduler?.account = localAccount
conferenceScheduler?.info = conferenceInfo // Will trigger the conference creation automatically
existingConfInfo = conferenceInfo
} catch {
Log.e("[Conference Creation] Failed \(error)")
}
}
private func allMandatoryFieldsFilled() -> Bool {
return subject.value != nil && subject.value!.count > 0 && (scheduleForLater.value != true || (scheduledDate.value != nil && scheduledTime.value != nil));
}
private func getConferenceStartTimestamp() -> Double {
return scheduleForLater.value == true ?
scheduledDate.value!.timeIntervalSince1970 +
scheduledTime.value!.timeIntervalSince1970 - Calendar.current.startOfDay(for: scheduledTime.value!).timeIntervalSince1970 +
Double(ConferenceSchedulingViewModel.timeZones[scheduledTimeZone.value!].timeZone.secondsFromGMT()-TimeZone.current.secondsFromGMT())
: Date().timeIntervalSince1970
}
private static func computeTimeZonesList() -> [TimeZoneData] {
return TimeZone.knownTimeZoneIdentifiers.map {
(ident) in TimeZoneData(timeZone:TimeZone(identifier:ident)!)
}.sorted()
}
private static func computeDurationList() -> [Duration] {
return [Duration(value: 30, display: "30min"), Duration(value: 60, display: "1h"), Duration(value: 120, display: "2h")]
}
}

View file

@ -0,0 +1,75 @@
/*
* 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
* aDouble with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import linphonesw
class ConferenceWaitingRoomViewModel: ControlsViewModel {
static let sharedModel = ConferenceWaitingRoomViewModel()
let joinLayout = MutableLiveData<ConferenceDisplayMode>()
let joinInProgress = MutableLiveData<Bool>(false)
let showLayoutPicker = MutableLiveData<Bool>()
override init() {
super.init()
self.reset()
}
func reset() {
joinLayout.value = Core.get().defaultConferenceLayout == .Grid ? .Grid : .ActiveSpeaker
joinInProgress.value = false
isMicrophoneMuted.value = !micAuthorized()
isMuteMicrophoneEnabled.value = true
isSpeakerSelected.value = true
isVideoEnabled.value = false
isVideoAvailable.value = core.videoCaptureEnabled
showLayoutPicker.value = false
}
override func toggleMuteMicrophone() {
if (!micAuthorized()) {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
self.isMicrophoneMuted.value = self.isMicrophoneMuted.value != true
}
}
}
self.isMicrophoneMuted.value = self.isMicrophoneMuted.value != true
}
override func toggleSpeaker() {
isSpeakerSelected.value = isSpeakerSelected.value != true
}
override func toggleVideo() {
isVideoEnabled.value = isVideoEnabled.value != true
}
override func updateUI() {
}
}

View file

@ -0,0 +1,76 @@
/*
* 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 linphonesw
class ScheduledConferencesViewModel {
var core : Core { get { Core.get() } }
static let shared = ScheduledConferencesViewModel()
var conferences : MutableLiveData<[ScheduledConferenceData]> = MutableLiveData([])
var daySplitted : [Date : [ScheduledConferenceData]] = [:]
var coreDelegate: CoreDelegateStub?
init () {
coreDelegate = CoreDelegateStub(
onConferenceInfoReceived: { (core, conferenceInfo) in
Log.i("[Scheduled Conferences] New conference info received")
self.conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo))
self.conferences.notifyValue()
}
)
computeConferenceInfoList()
}
func computeConferenceInfoList() {
conferences.value!.removeAll()
let now = Date().timeIntervalSince1970 // Linphone uses time_t in seconds
let oneHourAgo = now - 3600 // Show all conferences from 1 hour ago and forward
core.getConferenceInformationListAfterTime(time: time_t(oneHourAgo)).filter{$0.duration != 0}.forEach { conferenceInfo in
conferences.value!.append(ScheduledConferenceData(conferenceInfo: conferenceInfo))
}
daySplitted = [:]
conferences.value!.forEach { (conferenceInfo) in
let startDateDay = dateAtBeginningOfDay(for: conferenceInfo.rawDate)
if (daySplitted[startDateDay] == nil) {
daySplitted[startDateDay] = []
}
daySplitted[startDateDay]!.append(conferenceInfo)
}
}
func dateAtBeginningOfDay(for inputDate: Date) -> Date {
var calendar = Calendar.current
let timeZone = NSTimeZone.system as NSTimeZone
calendar.timeZone = timeZone as TimeZone
return calendar.date(from: calendar.dateComponents([.year, .month, .day], from: inputDate))!
}
}

View file

@ -0,0 +1,188 @@
/*
* 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 ConferenceHistoryDetailsView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource {
let participantsListTableView = UITableView()
let organizerTableView = UITableView()
let conectionsListTableView = UITableView()
let participantsLabel = StyledLabel(VoipTheme.conference_scheduling_font, " "+VoipTexts.conference_schedule_participants_list)
let organiserLabel = StyledLabel(VoipTheme.conference_scheduling_font, " "+VoipTexts.conference_schedule_organizer)
let datePicker = StyledDatePicker(pickerMode: .date, readOnly:true)
let timePicker = StyledDatePicker(pickerMode: .time, readOnly:true)
var conferenceData : ScheduledConferenceData? {
didSet {
if let data = conferenceData {
super.titleLabel.text = data.subject.value!
self.participantsListTableView.reloadData()
self.participantsListTableView.removeConstraints().done()
self.participantsListTableView.matchParentSideBorders().alignUnder(view: participantsLabel,withMargin: self.form_margin).done()
self.participantsListTableView.height(Double(data.conferenceInfo.participants.count) * VoipParticipantCell.cell_height).alignParentBottom().done()
datePicker.liveValue = MutableLiveData(conferenceData!.rawDate)
timePicker.liveValue = MutableLiveData(conferenceData!.rawDate)
}
}
}
static let compositeDescription = UICompositeViewDescription(ConferenceHistoryDetailsView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, 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(
backAction: {
PhoneMainView.instance().popView(self.compositeViewDescription())
},nextAction: {
},
nextActionEnableCondition: MutableLiveData(false),
title:"")
super.nextButton.isHidden = true
let schedulingStack = UIStackView()
schedulingStack.axis = .vertical
contentView.addSubview(schedulingStack)
schedulingStack.alignParentTop(withMargin: 2*form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
let scheduleForm = UIView()
schedulingStack.addArrangedSubview(scheduleForm)
scheduleForm.matchParentSideBorders().done()
// Left column (Date)
let leftColumn = UIView()
scheduleForm.addSubview(leftColumn)
leftColumn.matchParentWidthDividedBy(2.2).alignParentLeft(withMargin: form_margin).alignParentTop(withMargin: form_margin).done()
let dateLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_date)
leftColumn.addSubview(dateLabel)
dateLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
leftColumn.addSubview(datePicker)
datePicker.alignParentLeft().alignUnder(view: dateLabel,withMargin: form_margin).matchParentSideBorders().done()
leftColumn.wrapContentY().done()
// Right column (Time)
let rightColumn = UIView()
scheduleForm.addSubview(rightColumn)
rightColumn.matchParentWidthDividedBy(2.2).alignParentRight(withMargin: form_margin).alignParentTop().done()
let timeLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_time)
rightColumn.addSubview(timeLabel)
timeLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
rightColumn.addSubview(timePicker)
timePicker.alignParentLeft().alignUnder(view: timeLabel,withMargin: form_margin).matchParentSideBorders().done()
rightColumn.wrapContentY().done()
scheduleForm.wrapContentY().done()
// Organiser
organiserLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(organiserLabel)
organiserLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: schedulingStack,withMargin: form_margin*2).done()
organiserLabel.textAlignment = .left
contentView.addSubview(organizerTableView)
organizerTableView.isScrollEnabled = false
organizerTableView.dataSource = self
organizerTableView.register(VoipParticipantCell.self, forCellReuseIdentifier: "VoipParticipantCellSSchedule")
organizerTableView.allowsSelection = false
if #available(iOS 15.0, *) {
organizerTableView.allowsFocus = false
}
organizerTableView.separatorStyle = .singleLine
organizerTableView.separatorColor = VoipTheme.light_grey_color
organizerTableView.tag = 1;
organizerTableView.matchParentSideBorders().height(VoipParticipantCell.cell_height).alignUnder(view: organiserLabel,withMargin: form_margin).done()
// Participants
participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(participantsLabel)
participantsLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: organizerTableView,withMargin: form_margin).done()
participantsLabel.textAlignment = .left
contentView.addSubview(participantsListTableView)
participantsListTableView.isScrollEnabled = false
participantsListTableView.dataSource = self
participantsListTableView.register(VoipParticipantCell.self, forCellReuseIdentifier: "VoipParticipantCellSSchedule")
participantsListTableView.allowsSelection = false
if #available(iOS 15.0, *) {
participantsListTableView.allowsFocus = false
}
participantsListTableView.separatorStyle = .singleLine
participantsListTableView.separatorColor = VoipTheme.light_grey_color
// Goto chat - v2
/*
let chatButton = FormButton(title: VoipTexts.conference_go_to_chat.uppercased(), backgroundStateColors: VoipTheme.primary_colors_background)
contentView.addSubview(chatButton)
chatButton.onClick {
//let chatRoom = ChatRoom()
//PhoneMainView.instance().go(to: chatRoom?.getCobject)
}
chatButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done()
*/
}
// Objc - bridge, as can't access easily to the view model.
@objc func setCallLog(callLog:OpaquePointer) {
let log = CallLog.getSwiftObject(cObject: callLog)
if let conferenceInfo = log.conferenceInfo {
self.conferenceData = ScheduledConferenceData(conferenceInfo: conferenceInfo)
}
}
// TableView datasource delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let data = conferenceData else {
return 0
}
return tableView.tag == 1 ? 1 : data.conferenceInfo.participants.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:VoipParticipantCell = tableView.dequeueReusableCell(withIdentifier: "VoipParticipantCellSSchedule") as! VoipParticipantCell
guard let data = conferenceData else {
return cell
}
cell.selectionStyle = .none
cell.scheduleConfParticipantAddress = tableView.tag == 1 ? data.conferenceInfo.participants.filter {$0.weakEqual(address2: data.conferenceInfo.organizer!)}.first : data.conferenceInfo.participants[indexPath.row]
cell.limeBadge.isHidden = true
return cell
}
}

View file

@ -0,0 +1,274 @@
/*
* 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
import SVProgressHUD
@objc class ConferenceSchedulingSummaryView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource {
let CONFERENCE_CREATION_TIME_OUT_SEC = 15.0
let participantsListTableView = UITableView()
let datePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDate,pickerMode: .date, readOnly:true)
let timeZoneValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledTimeZone,options: ConferenceSchedulingViewModel.timeZones.map({ (tzd: TimeZoneData) -> String in tzd.descWithOffset()}), readOnly:true)
let durationValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledDuration,options: ConferenceSchedulingViewModel.durationList.map({ (duration: Duration) -> String in duration.display}), readOnly:true)
let timePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledTime,pickerMode: .time, readOnly:true)
let descriptionInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_description_hint,liveValue: ConferenceSchedulingViewModel.shared.description, readOnly:true)
let createButton = FormButton(backgroundStateColors: VoipTheme.primary_colors_background)
static let compositeDescription = UICompositeViewDescription(ConferenceSchedulingSummaryView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, 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(
backAction: {
self.goBackParticipantsListSelection()
},nextAction: {
},
nextActionEnableCondition: ConferenceSchedulingViewModel.shared.continueEnabled,
title:VoipTexts.conference_schedule_summary)
super.nextButton.isHidden = true
let subjectLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_subject_title)
contentView.addSubview(subjectLabel)
subjectLabel.alignParentLeft(withMargin: form_margin).alignParentTop().done()
let encryptedIcon = UIImageView(image: UIImage(named: "security_2_indicator"))
encryptedIcon.contentMode = .scaleAspectFit
contentView.addSubview(encryptedIcon)
encryptedIcon.height(form_input_height).alignParentTop().alignParentTop().alignParentRight(withMargin: form_margin).alignHorizontalCenterWith(subjectLabel).done()
ConferenceSchedulingViewModel.shared.isEncrypted.readCurrentAndObserve { (encrypt) in
encryptedIcon.isHidden = encrypt != true
}
let subjectInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_subject_hint, liveValue: ConferenceSchedulingViewModel.shared.subject, readOnly:true)
contentView.addSubview(subjectInput)
subjectInput.alignUnder(view: subjectLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(form_input_height).done()
let schedulingStack = UIStackView()
schedulingStack.axis = .vertical
contentView.addSubview(schedulingStack)
schedulingStack.alignUnder(view: subjectInput,withMargin: 3*form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
let scheduleForm = UIView()
schedulingStack.addArrangedSubview(scheduleForm)
scheduleForm.matchParentSideBorders().done()
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (forLater) in scheduleForm.isHidden = forLater != true }
// Left column (Date & Time)
let leftColumn = UIView()
scheduleForm.addSubview(leftColumn)
leftColumn.matchParentWidthDividedBy(2.2).alignParentLeft(withMargin: form_margin).alignParentTop(withMargin: form_margin).done()
let dateLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_date)
leftColumn.addSubview(dateLabel)
dateLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
leftColumn.addSubview(datePicker)
datePicker.alignParentLeft().alignUnder(view: dateLabel,withMargin: form_margin).matchParentSideBorders().done()
let timeLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_time)
leftColumn.addSubview(timeLabel)
timeLabel.alignParentLeft().alignUnder(view: datePicker,withMargin: form_margin).done()
leftColumn.addSubview(timePicker)
timePicker.alignParentLeft().alignUnder(view: timeLabel,withMargin: form_margin).matchParentSideBorders().done()
leftColumn.wrapContentY().done()
// Right column (Duration & Timezone)
let rightColumn = UIView()
scheduleForm.addSubview(rightColumn)
rightColumn.matchParentWidthDividedBy(2.2).alignParentRight(withMargin: form_margin).alignParentTop().done()
let durationLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_duration)
rightColumn.addSubview(durationLabel)
durationLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
rightColumn.addSubview(durationValue)
durationValue.alignParentLeft().alignUnder(view: durationLabel,withMargin: form_margin).matchParentSideBorders().done()
let timeZoneLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_timezone)
rightColumn.addSubview(timeZoneLabel)
timeZoneLabel.alignParentLeft().alignUnder(view: durationValue,withMargin: form_margin).done()
rightColumn.addSubview(timeZoneValue)
timeZoneValue.alignParentLeft().alignUnder(view: timeZoneLabel,withMargin: form_margin).matchParentSideBorders().done()
rightColumn.wrapContentY().done()
// Description
let descriptionLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_description_title)
scheduleForm.addSubview(descriptionLabel)
descriptionLabel.alignUnder(view: leftColumn,withMargin: form_margin).alignUnder(view: rightColumn,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
descriptionInput.textContainer.maximumNumberOfLines = 5
descriptionInput.textContainer.lineBreakMode = .byWordWrapping
scheduleForm.addSubview(descriptionInput)
descriptionInput.alignUnder(view: descriptionLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(description_height).alignParentBottom(withMargin: form_margin*2).done()
scheduleForm.wrapContentY().done()
// Sending method
let viaChatLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_send_invite_chat_summary)
contentView.addSubview(viaChatLabel)
viaChatLabel.matchParentSideBorders(insetedByDx: form_margin).alignUnder(view: schedulingStack,withMargin: 2*form_margin).done()
ConferenceSchedulingViewModel.shared.sendInviteViaChat.readCurrentAndObserve { (sendChat) in
viaChatLabel.isHidden = sendChat != true || ConferenceSchedulingViewModel.shared.scheduleForLater.value != true
}
// Participants
let participantsLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_participants_list)
participantsLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(participantsLabel)
participantsLabel.matchParentSideBorders().height(form_input_height).alignUnder(view: viaChatLabel,withMargin: form_margin*2).done()
participantsLabel.textAlignment = .center
contentView.addSubview(participantsListTableView)
participantsListTableView.isScrollEnabled = false
participantsListTableView.dataSource = self
participantsListTableView.register(VoipParticipantCell.self, forCellReuseIdentifier: "VoipParticipantCellSSchedule")
participantsListTableView.allowsSelection = false
if #available(iOS 15.0, *) {
participantsListTableView.allowsFocus = false
}
participantsListTableView.separatorStyle = .singleLine
participantsListTableView.separatorColor = VoipTheme.light_grey_color
ConferenceSchedulingViewModel.shared.selectedAddresses.readCurrentAndObserve { (addresses) in
self.participantsListTableView.reloadData()
self.participantsListTableView.removeConstraints().done()
self.participantsListTableView.matchParentSideBorders().alignUnder(view: participantsLabel,withMargin: self.form_margin).done()
self.participantsListTableView.height(Double(addresses!.count) * VoipParticipantCell.cell_height).done()
}
// Create / Schedule
contentView.addSubview(createButton)
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { _ in
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
self.createButton.addSidePadding()
}
ConferenceSchedulingViewModel.shared.conferenceCreationInProgress.observe { progress in
if (progress == true) {
SVProgressHUD.show()
} else {
SVProgressHUD.dismiss()
}
}
var enableCreationTimeOut = false
ConferenceSchedulingViewModel.shared.conferenceCreationCompletedEvent.observe { pair in
enableCreationTimeOut = false
if (ConferenceSchedulingViewModel.shared.scheduleForLater.value == true) {
PhoneMainView.instance().pop(toView:ScheduledConferencesView.compositeDescription)
VoipDialog.toast(message: VoipTexts.conference_schedule_info_created)
}
}
ConferenceSchedulingViewModel.shared.onErrorEvent.observe { error in
VoipDialog.init(message: error!).show()
}
createButton.onClick {
enableCreationTimeOut = true
ConferenceSchedulingViewModel.shared.createConference()
DispatchQueue.main.asyncAfter(deadline: .now() + self.CONFERENCE_CREATION_TIME_OUT_SEC) {
if (enableCreationTimeOut) {
enableCreationTimeOut = false
ConferenceSchedulingViewModel.shared.conferenceCreationInProgress.value = false
ConferenceSchedulingViewModel.shared.onErrorEvent.value = VoipTexts.call_error_server_timeout
}
}
}
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (later) in
self.createButton.title = ConferenceSchedulingViewModel.shared.scheduleForLater.value == true ? VoipTexts.conference_schedule_start.uppercased() : VoipTexts.conference_group_call_create.uppercased()
viaChatLabel.isHidden = later != true || ConferenceSchedulingViewModel.shared.sendInviteViaChat.value != true
self.createButton.addSidePadding()
}
createButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done()
}
override func viewWillAppear(_ animated: Bool) {
datePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDate
timeZoneValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledTimeZone.value!)
durationValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledDuration.value!)
timePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledTime
descriptionInput.text = ConferenceSchedulingViewModel.shared.description.value
createButton.addSidePadding()
super.viewWillAppear(animated)
}
func goBackParticipantsListSelection() {
let view: ChatConversationCreateView = VIEW(ChatConversationCreateView.compositeViewDescription())
let addresses = ConferenceSchedulingViewModel.shared.selectedAddresses.value!.map { (address) in String(address.asStringUriOnly()) }
view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray
view.tableController.notFirstTime = true
view.isForEditing = false
view.isForVoipConference = true
PhoneMainView.instance().pop(toView: view.compositeViewDescription())
}
// Objc - bridge, as can't access easily to the view model.
@objc func setParticipants(addresses:[String]) {
ConferenceSchedulingViewModel.shared.selectedAddresses.value = []
return addresses.forEach { (address) in
if let address = try?Factory.Instance.createAddress(addr: address) {
ConferenceSchedulingViewModel.shared.selectedAddresses.value?.append(address)
}
}
}
// TableView datasource delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let participants = ConferenceSchedulingViewModel.shared.selectedAddresses.value else {
return 0
}
return participants.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:VoipParticipantCell = tableView.dequeueReusableCell(withIdentifier: "VoipParticipantCellSSchedule") as! VoipParticipantCell
guard let participant = ConferenceSchedulingViewModel.shared.selectedAddresses.value?[indexPath.row] else {
return cell
}
cell.selectionStyle = .none
cell.scheduleConfParticipantAddress = participant
cell.limeBadge.isHidden = ConferenceSchedulingViewModel.shared.isEncrypted.value != true
return cell
}
}

View file

@ -0,0 +1,227 @@
/*
* 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 ConferenceSchedulingView: BackNextNavigationView, UICompositeViewDelegate {
static let compositeDescription = UICompositeViewDescription(ConferenceSchedulingView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, fullscreen: false, isLeftFragment: false,fragmentWith: nil)
static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
let datePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledDate,pickerMode: .date)
let timeZoneValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledTimeZone,options: ConferenceSchedulingViewModel.timeZones.map({ (tzd: TimeZoneData) -> String in tzd.descWithOffset()}))
let durationValue = StyledValuePicker(liveIndex: ConferenceSchedulingViewModel.shared.scheduledDuration,options: ConferenceSchedulingViewModel.durationList.map({ (duration: Duration) -> String in duration.display}))
let timePicker = StyledDatePicker(liveValue: ConferenceSchedulingViewModel.shared.scheduledTime,pickerMode: .time)
let descriptionInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_description_hint,liveValue: ConferenceSchedulingViewModel.shared.description)
override func viewDidLoad() {
super.viewDidLoad(
backAction: {
PhoneMainView.instance().popView(self.compositeViewDescription())
},nextAction: {
self.gotoParticipantsListSelection()
},
nextActionEnableCondition: ConferenceSchedulingViewModel.shared.continueEnabled,
title:VoipTexts.conference_group_call_title)
let subjectLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_subject_title)
subjectLabel.addIndicatorIcon(iconName: "voip_mandatory")
contentView.addSubview(subjectLabel)
subjectLabel.alignParentLeft(withMargin: form_margin).alignParentTop().done()
let subjectInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_subject_hint, liveValue: ConferenceSchedulingViewModel.shared.subject,maxLines:1)
contentView.addSubview(subjectInput)
subjectInput.alignUnder(view: subjectLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(form_input_height).done()
let schedulingStack = UIStackView()
schedulingStack.axis = .vertical
schedulingStack.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
contentView.addSubview(schedulingStack)
schedulingStack.alignUnder(view: subjectInput,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
let scheduleForLater = UIView()
schedulingStack.addArrangedSubview(scheduleForLater)
scheduleForLater.matchParentSideBorders().height(schdule_for_later_height).done()
let laterSwitch = StyledSwitch(liveValue: ConferenceSchedulingViewModel.shared.scheduleForLater)
scheduleForLater.addSubview(laterSwitch)
laterSwitch.alignParentTop(withMargin: form_margin*2.5).alignParentLeft(withMargin: form_margin).centerY().done()
let laterLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_later)
laterLabel.numberOfLines = 2
scheduleForLater.addSubview(laterLabel)
laterLabel.alignParentTop(withMargin: form_margin*2).toRightOf(laterSwitch, withLeftMargin: form_margin*2).alignParentRight(withMargin: form_margin).done()
let scheduleForm = UIView()
schedulingStack.addArrangedSubview(scheduleForm)
scheduleForm.matchParentSideBorders().done()
// Left column (Date & Time)
let leftColumn = UIView()
scheduleForm.addSubview(leftColumn)
leftColumn.matchParentWidthDividedBy(2.2).alignParentLeft(withMargin: form_margin).alignParentTop(withMargin: form_margin).done()
let dateLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_date)
dateLabel.addIndicatorIcon(iconName: "voip_mandatory")
leftColumn.addSubview(dateLabel)
dateLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
leftColumn.addSubview(datePicker)
datePicker.alignParentLeft().alignUnder(view: dateLabel,withMargin: form_margin).matchParentSideBorders().done()
let timeLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_time)
timeLabel.addIndicatorIcon(iconName: "voip_mandatory")
leftColumn.addSubview(timeLabel)
timeLabel.alignParentLeft().alignUnder(view: datePicker,withMargin: form_margin).done()
leftColumn.addSubview(timePicker)
timePicker.alignParentLeft().alignUnder(view: timeLabel,withMargin: form_margin).matchParentSideBorders().done()
leftColumn.wrapContentY().done()
// Right column (Duration & Timezone)
let rightColumn = UIView()
scheduleForm.addSubview(rightColumn)
rightColumn.matchParentWidthDividedBy(2.2).alignParentRight(withMargin: form_margin).alignParentTop().done()
let durationLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_duration)
rightColumn.addSubview(durationLabel)
durationLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
rightColumn.addSubview(durationValue)
durationValue.alignParentLeft().alignUnder(view: durationLabel,withMargin: form_margin).matchParentSideBorders().done()
let timeZoneLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_timezone)
rightColumn.addSubview(timeZoneLabel)
timeZoneLabel.alignParentLeft().alignUnder(view: durationValue,withMargin: form_margin).done()
rightColumn.addSubview(timeZoneValue)
timeZoneValue.alignParentLeft().alignUnder(view: timeZoneLabel,withMargin: form_margin).matchParentSideBorders().done()
rightColumn.wrapContentY().done()
// Description
let descriptionLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_description_title)
scheduleForm.addSubview(descriptionLabel)
descriptionLabel.alignUnder(view: leftColumn,withMargin: form_margin).alignUnder(view: rightColumn,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).done()
descriptionInput.textContainer.maximumNumberOfLines = 5
descriptionInput.textContainer.lineBreakMode = .byWordWrapping
scheduleForm.addSubview(descriptionInput)
descriptionInput.alignUnder(view: descriptionLabel,withMargin: form_margin).matchParentSideBorders(insetedByDx: form_margin).height(description_height).alignParentBottom(withMargin: form_margin*2).done()
scheduleForm.wrapContentY().done()
// Sending methods
let viaChatSwitch = StyledCheckBox(liveValue: ConferenceSchedulingViewModel.shared.sendInviteViaChat)
contentView.addSubview(viaChatSwitch)
viaChatSwitch.alignParentLeft(withMargin: form_margin).alignUnder(view: schedulingStack,withMargin: 2*form_margin).done()
let viaChatLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_send_invite_chat)
contentView.addSubview(viaChatLabel)
viaChatLabel.toRightOf(viaChatSwitch,withLeftMargin: form_margin).alignUnder(view: schedulingStack,withMargin: 2*form_margin).alignHorizontalCenterWith(viaChatSwitch).done()
/* Hidden as in Android 9.6.2022
let viaMailSwitch = StyledCheckBox(liveValue: ConferenceSchedulingViewModel.shared.sendInviteViaEmail)
contentView.addSubview(viaMailSwitch)
viaMailSwitch.alignParentLeft(withMargin: form_margin).alignUnder(view: viaChatSwitch,withMargin: 2*form_margin).done()
let viaMailLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_send_invite_email)
contentView.addSubview(viaMailLabel)
viaMailLabel.toRightOf(viaMailSwitch,withLeftMargin: form_margin).alignUnder(view: viaChatLabel,withMargin: 2*form_margin).alignHorizontalCenterWith(viaMailSwitch).done()
// Encryption
let encryptLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_encryption)
contentView.addSubview(encryptLabel)
encryptLabel.alignUnder(view: viaMailLabel,withMargin: 4*form_margin).centerX().done()
let encryptCombo = UIStackView()
contentView.addSubview(encryptCombo)
encryptCombo.alignUnder(view: encryptLabel,withMargin: form_margin).centerX().height(form_input_height).done()
let unencryptedIcon = UIImageView(image: UIImage(named: "security_toggle_icon_grey"))
unencryptedIcon.contentMode = .scaleAspectFit
encryptCombo.addArrangedSubview(unencryptedIcon)
let encryptSwitch = StyledSwitch(liveValue: ConferenceSchedulingViewModel.shared.isEncrypted)
encryptCombo.addArrangedSubview(encryptSwitch)
encryptSwitch.centerY().alignParentTop(withMargin: form_margin).done()
let encryptedIcon = UIImageView(image: UIImage(named: "security_toggle_icon_green"))
encryptedIcon.contentMode = .scaleAspectFit
encryptCombo.addArrangedSubview(encryptedIcon)
*/
// Mandatory label
let mandatoryLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_mandatory_field)
mandatoryLabel.addIndicatorIcon(iconName: "voip_mandatory", trailing: false)
contentView.addSubview(mandatoryLabel)
mandatoryLabel.alignUnder(view: viaChatSwitch,withMargin: 2*form_margin).centerX().matchParentSideBorders().done()
mandatoryLabel.textAlignment = .center
mandatoryLabel.alignParentBottom().done()
// Schedule for later observer
ConferenceSchedulingViewModel.shared.scheduleForLater.readCurrentAndObserve { (forLater) in
scheduleForm.isHidden = forLater != true
super.titleLabel.text = forLater == true ? VoipTexts.conference_schedule_title : VoipTexts.conference_group_call_title
viaChatSwitch.isHidden = forLater != true
viaChatLabel.isHidden = forLater != true
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
datePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledDate
timeZoneValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledTimeZone.value!)
durationValue.setIndex(index: ConferenceSchedulingViewModel.shared.scheduledDuration.value!)
timePicker.liveValue = ConferenceSchedulingViewModel.shared.scheduledTime
descriptionInput.text = ConferenceSchedulingViewModel.shared.description.value
}
func gotoParticipantsListSelection() {
let view: ChatConversationCreateView = self.VIEW(ChatConversationCreateView.compositeViewDescription());
let addresses = ConferenceSchedulingViewModel.shared.selectedAddresses.value!.map { (address) in String(address.asStringUriOnly()) }
view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray
view.isForEditing = false
view.isForVoipConference = true
view.isForOngoingVoipConference = false
view.tableController.notFirstTime = true
view.isGroupChat = true
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
}
@objc func resetViewModel() {
ConferenceSchedulingViewModel.shared.reset()
}
}

View file

@ -0,0 +1,209 @@
/*
* 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
@objc class ConferenceWaitingRoomFragment: UIViewController, UICompositeViewDelegate { // Replaces CallView
// Layout constants
let common_margin = 17.0
let switch_camera_button_size = 50
let switch_camera_button_margins = 7.0
let content_inset = 12.0
let button_spacing = 15.0
let center_view_corner_radius = 20.0
let button_width = 150
var audioRoutesView : AudioRoutesView? = nil
let subject = StyledLabel(VoipTheme.conference_preview_subject_font)
let localVideo = UIView()
let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
let noVideoLabel = StyledLabel(VoipTheme.conference_waiting_room_no_video_font, VoipTexts.conference_waiting_room_video_disabled)
let buttonsView = UIStackView()
let cancel = FormButton(title: VoipTexts.cancel.uppercased(), backgroundStateColors: VoipTheme.primary_colors_background_gray, bold:false)
let start = FormButton(title: VoipTexts.conference_waiting_room_start_call.uppercased(), backgroundStateColors: VoipTheme.primary_colors_background)
let conferenceJoinSpinner = RotatingSpinner()
var conferenceUrl : String? = nil
let conferenceSubject = MutableLiveData<String>()
static let compositeDescription = UICompositeViewDescription(ConferenceWaitingRoomFragment.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()
view.addSubview(subject)
subject.centerX().alignParentTop(withMargin: common_margin).done()
conferenceSubject.observe { subject in
self.subject.text = subject
}
// Controls
let controlsView = ControlsView(showVideo: true, controlsViewModel: ConferenceWaitingRoomViewModel.sharedModel)
view.addSubview(controlsView)
controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
// Layoout picker
let layoutPicker = CallControlButton(imageInset : UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8),buttonTheme: VoipTheme.conf_waiting_room_layout_picker, onClickAction: {
ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value = ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.value != true
})
view.addSubview(layoutPicker)
layoutPicker.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).alignParentRight(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
ConferenceWaitingRoomViewModel.sharedModel.joinLayout.readCurrentAndObserve { layout in
var icon = ""
switch (layout!) {
case .Grid: icon = "voip_conference_mosaic"; break
case .ActiveSpeaker: icon = "voip_conference_active_speaker"; break
case .AudioOnly:
icon = "voip_conference_audio_only"
ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value = false
break
}
layoutPicker.applyTintedIcons(tintedIcons: [UIButton.State.normal.rawValue : TintableIcon(name: icon ,tintColor: LightDarkColor(.white,.white))])
}
let layoutPickerView = ConferenceLayoutPickerView()
view.addSubview(layoutPickerView)
layoutPickerView.alignAbove(view:layoutPicker,withMargin:button_spacing).alignVerticalCenterWith(layoutPicker).done()
ConferenceWaitingRoomViewModel.sharedModel.showLayoutPicker.readCurrentAndObserve { show in
layoutPicker.isSelected = show == true
layoutPickerView.isHidden = show != true
if (show == true) {
self.view.bringSubviewToFront(layoutPickerView)
}
}
// Form buttons
buttonsView.axis = .horizontal
buttonsView.spacing = button_spacing
view.addSubview(buttonsView)
buttonsView.alignAbove(view: controlsView,withMargin: SharedLayoutConstants.buttons_bottom_margin).centerX().done()
start.width(button_width).done()
cancel.width(button_width).done()
buttonsView.addArrangedSubview(cancel)
buttonsView.addArrangedSubview(start)
cancel.onClick {
Core.get().calls.forEach { call in
if ([Call.State.OutgoingInit, Call.State.OutgoingRinging, Call.State.OutgoingProgress].contains(call.state)) {
CallManager.instance().terminateCall(call: call.getCobject)
}
}
ConferenceWaitingRoomViewModel.sharedModel.joinInProgress.value = false
PhoneMainView.instance().popView(self.compositeViewDescription())
}
start.onClick {
ConferenceWaitingRoomViewModel.sharedModel.joinInProgress.value = true
self.conferenceUrl.map{ CallManager.instance().startCall(addr: $0, isSas: false, isVideo: ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value!, isConference: true) }
}
ConferenceWaitingRoomViewModel.sharedModel.joinInProgress.readCurrentAndObserve { joining in
self.start.isEnabled = joining != true
//self.localVideo.isHidden = joining == true (UX question as video window goes black by the core, better black or hidden ?)
self.noVideoLabel.isHidden = joining == true
layoutPicker.isHidden = joining == true
if (joining == true) {
self.view.addSubview(self.conferenceJoinSpinner)
self.conferenceJoinSpinner.square(IncomingOutgoingCommonView.spinner_size).center().done()
self.conferenceJoinSpinner.startRotation()
controlsView.isHidden = true
} else {
self.conferenceJoinSpinner.stopRotation()
self.conferenceJoinSpinner.removeFromSuperview()
controlsView.isHidden = false
}
}
// localVideo view
localVideo.layer.cornerRadius = center_view_corner_radius
localVideo.clipsToBounds = true
localVideo.contentMode = .scaleAspectFill
localVideo.backgroundColor = .black
self.view.addSubview(localVideo)
localVideo.matchParentSideBorders(insetedByDx: content_inset).alignAbove(view:buttonsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).alignUnder(view: subject,withMargin: common_margin).done()
localVideo.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().videoPreviewEnabled = false
Core.get().toggleCamera()
Core.get().nativePreviewWindow = self.localVideo
Core.get().videoPreviewEnabled = true
}
self.view.addSubview(noVideoLabel)
noVideoLabel.center().done()
ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.readCurrentAndObserve { videoEnabled in
Core.get().videoPreviewEnabled = videoEnabled == true
self.localVideo.isHidden = videoEnabled != true
self.switchCamera.isHidden = videoEnabled != true
self.noVideoLabel.isHidden = videoEnabled == true
}
// Audio Routes
audioRoutesView = AudioRoutesView()
view.addSubview(audioRoutesView!)
audioRoutesView!.alignBottomWith(otherView: controlsView).done()
ConferenceWaitingRoomViewModel.sharedModel.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)
ConferenceWaitingRoomViewModel.sharedModel.audioRoutesSelected.value = false
ConferenceWaitingRoomViewModel.sharedModel.reset()
Core.get().nativePreviewWindow = localVideo
Core.get().videoPreviewEnabled = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true
}
override func viewWillDisappear(_ animated: Bool) {
ControlsViewModel.shared.fullScreenMode.value = false
Core.get().nativePreviewWindow = nil
Core.get().videoPreviewEnabled = false
ConferenceWaitingRoomViewModel.sharedModel.joinInProgress.value = false
super.viewWillDisappear(animated)
}
@objc func setDetails(subject:String, url:String) {
self.conferenceSubject.value = subject
self.conferenceUrl = url
}
}

View file

@ -0,0 +1,138 @@
/*
* 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 ICSBubbleView: UIView {
let corner_radius = 7.0
let border_width = 2.0
let rows_spacing = 6.0
let inner_padding = 8.0
let indicator_y = 3.0
let share_size = 25
let join_share_width = 150.0
let inviteTitle = StyledLabel(VoipTheme.conference_invite_title_font, VoipTexts.conference_invite_title)
let subject = StyledLabel(VoipTheme.conference_invite_subject_font)
let participants = StyledLabel(VoipTheme.conference_invite_desc_font)
let date = StyledLabel(VoipTheme.conference_invite_desc_font)
let timeDuration = StyledLabel(VoipTheme.conference_invite_desc_font)
let descriptionTitle = StyledLabel(VoipTheme.conference_invite_desc_title_font, VoipTexts.conference_description_title)
let descriptionValue = StyledLabel(VoipTheme.conference_invite_desc_font)
let joinShare = UIStackView()
let join = FormButton(title:VoipTexts.conference_invite_join.uppercased(), backgroundStateColors: VoipTheme.button_green_background)
let share = UIImageView(image:UIImage(named:"voip_export")?.tinted(with: VoipTheme.primaryTextColor.get()))
var icsFile : String? = nil
var conferenceData: ScheduledConferenceData? = nil {
didSet {
if let data = conferenceData {
subject.text = data.subject.value
participants.text = VoipTexts.conference_invite_participants_count.replacingOccurrences(of: "%d", with: String(data.conferenceInfo.participants.count+1))
participants.addIndicatorIcon(iconName: "conference_schedule_participants_default",padding : 0.0, y: -indicator_y, trailing: false)
date.text = " "+TimestampUtils.dateToString(date: data.rawDate)
date.addIndicatorIcon(iconName: "conference_schedule_calendar_default", padding: 0.0, y:-indicator_y, trailing:false)
timeDuration.text = " \(data.time.value) ( \(data.duration.value) )"
timeDuration.addIndicatorIcon(iconName: "conference_schedule_time_default",padding : 0.0, y: -indicator_y, trailing: false)
descriptionTitle.isHidden = data.description.value == nil || data.description.value!.count == 0
descriptionValue.isHidden = descriptionTitle.isHidden
descriptionValue.text = data.description.value
}
}
}
init() {
super.init(frame:.zero)
layer.cornerRadius = corner_radius
clipsToBounds = true
backgroundColor = VoipTheme.voip_light_gray
let rows = UIStackView()
rows.axis = .vertical
rows.spacing = rows_spacing
addSubview(rows)
rows.addArrangedSubview(inviteTitle)
rows.addArrangedSubview(subject)
rows.addArrangedSubview(participants)
rows.addArrangedSubview(date)
rows.addArrangedSubview(timeDuration)
rows.addArrangedSubview(descriptionTitle)
rows.addArrangedSubview(descriptionValue)
addSubview(joinShare)
joinShare.axis = .horizontal
joinShare.spacing = rows_spacing
joinShare.addArrangedSubview(share)
share.square(share_size).done()
joinShare.addArrangedSubview(join)
rows.matchParentSideBorders(insetedByDx: inner_padding).alignParentTop(withMargin: inner_padding).done()
joinShare.alignParentBottom(withMargin: inner_padding).width(join_share_width).alignParentRight(withMargin: inner_padding).done()
join.onClick {
let view : ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription())
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
view.setDetails(subject: (self.conferenceData?.subject.value)!, url: (self.conferenceData?.address.value)!)
}
share.onClick {
let ics = URL(string: "file://"+self.icsFile!)
UIApplication.shared.open(ics!)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func setFromChatMessage(cmessage: OpaquePointer) {
let message = ChatMessage.getSwiftObject(cObject: cmessage)
message.contents.forEach { content in
if (content.isIcalendar) {
if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: content) {
self.conferenceData = ScheduledConferenceData(conferenceInfo: conferenceInfo)
self.icsFile = content.filePath
}
}
}
}
@objc static func isConferenceInvitationMessage(cmessage: OpaquePointer) -> Bool {
var isConferenceInvitationMessage = false
let message = ChatMessage.getSwiftObject(cObject: cmessage)
message.contents.forEach { content in
if (content.isIcalendar) {
isConferenceInvitationMessage = true
}
}
return isConferenceInvitationMessage
}
@objc func setLayoutConstraints(view:UIView) {
matchDimensionsWith(view: view, insetedByDx: inner_padding).done()
}
}

View file

@ -0,0 +1,205 @@
/*
* 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
class ScheduledConferencesCell: UITableViewCell {
let corner_radius = 7.0
let border_width = 2.0
static let button_size = 40
let clockIcon = UIImageView(image: UIImage(named: "conference_schedule_time_default"))
let timeDuration = StyledLabel(VoipTheme.conference_invite_desc_font)
let organiser = StyledLabel(VoipTheme.conference_invite_desc_font)
let subject = StyledLabel(VoipTheme.conference_invite_subject_font)
let participantsIcon = UIImageView(image: UIImage(named: "conference_schedule_participants_default"))
let participants = StyledLabel(VoipTheme.conference_invite_desc_font)
let infoConf = UIButton()
let descriptionTitle = StyledLabel(VoipTheme.conference_invite_desc_font, VoipTexts.conference_description_title)
let descriptionValue = StyledLabel(VoipTheme.conference_invite_desc_font)
let urlTitle = StyledLabel(VoipTheme.conference_invite_desc_font, VoipTexts.conference_schedule_address_title)
let urlValue = StyledLabel(VoipTheme.conference_scheduling_font)
let copyLink = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_copy"))
let joinConf = FormButton(title:VoipTexts.conference_invite_join.uppercased(), backgroundStateColors: VoipTheme.button_green_background)
let deleteConf = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_delete"))
let editConf = CallControlButton(width:button_size,height:button_size,buttonTheme: VoipTheme.scheduled_conference_action("voip_edit"))
var owningTableView : UITableView? = nil
let joinEditDelete = UIStackView()
let expandedRows = UIStackView()
var conferenceData: ScheduledConferenceData? = nil {
didSet {
if let data = conferenceData {
timeDuration.text = "\(data.time.value)"+(data.duration.value != nil ? " ( \(data.duration.value) )" : "")
organiser.text = VoipTexts.conference_schedule_organizer+data.organizer.value!
subject.text = data.subject.value!
descriptionValue.text = data.description.value!
urlValue.text = data.address.value!
data.expanded.readCurrentAndObserve { expanded in
self.contentView.layer.borderWidth = expanded == true ? 2.0 : 0.0
self.descriptionTitle.isHidden = expanded != true || self.descriptionValue.text?.count == 0
self.descriptionValue.isHidden = expanded != true || self.descriptionValue.text?.count == 0
self.infoConf.isSelected = expanded == true
self.participants.text = expanded == true ? data.participantsExpanded.value : data.participantsShort.value
self.participants.numberOfLines = expanded == true ? 6 : 2
self.expandedRows.isHidden = expanded != true
self.joinEditDelete.isHidden = expanded != true
if let myAddress = Core.get().defaultAccount?.params?.identityAddress {
self.editConf.isHidden = expanded != true || data.conferenceInfo.organizer?.weakEqual(address2: myAddress) != true
} else {
self.editConf.isHidden = true
}
self.participants.removeConstraints().alignUnder(view: self.subject,withMargin: 15).toRightOf(self.participantsIcon,withLeftMargin:10).toRightOf(self.participantsIcon,withLeftMargin:10).toLeftOf(self.infoConf,withRightMargin: 15).done()
self.joinEditDelete.removeConstraints().alignUnder(view: self.expandedRows,withMargin: 15).alignParentRight(withMargin: 10).done()
if (expanded == true) {
self.joinEditDelete.alignParentBottom(withMargin: 10).done()
} else {
self.participants.alignParentBottom(withMargin: 10).done()
}
}
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.layer.cornerRadius = corner_radius
contentView.clipsToBounds = true
contentView.backgroundColor = VoipTheme.header_background_color
contentView.layer.borderColor = VoipTheme.primary_color.cgColor
contentView.addSubview(clockIcon)
clockIcon.alignParentTop(withMargin: 15).square(15).alignParentLeft(withMargin: 10).done()
contentView.addSubview(timeDuration)
timeDuration.alignParentTop(withMargin: 15).toRightOf(clockIcon,withLeftMargin:10).alignHorizontalCenterWith(clockIcon).done()
contentView.addSubview(organiser)
organiser.alignParentTop(withMargin: 15).toRightOf(timeDuration, withLeftMargin:10).alignParentRight(withMargin:10).alignHorizontalCenterWith(clockIcon).done()
contentView.addSubview(subject)
subject.alignUnder(view: timeDuration,withMargin: 15).alignParentLeft(withMargin: 10).done()
contentView.addSubview(participantsIcon)
participantsIcon.alignUnder(view: subject,withMargin: 15).square(15).alignParentLeft(withMargin: 10).done()
//infoConf.onClick {
contentView.onClick {
self.conferenceData?.toggleExpand()
self.owningTableView?.reloadData()
}
contentView.addSubview(infoConf)
infoConf.imageView?.contentMode = .scaleAspectFit
infoConf.alignUnder(view: subject,withMargin: 15).square(30).alignParentRight(withMargin: 10).alignHorizontalCenterWith(participantsIcon).done()
infoConf.applyTintedIcons(tintedIcons: VoipTheme.conference_info_button)
contentView.addSubview(participants)
participants.alignUnder(view: subject,withMargin: 15).toRightOf(participantsIcon,withLeftMargin:10).toRightOf(participantsIcon,withLeftMargin:10).toLeftOf(infoConf,withRightMargin: 15).done()
expandedRows.axis = .vertical
expandedRows.spacing = 10
contentView.addSubview(expandedRows)
expandedRows.alignUnder(view: participants,withMargin: 15).matchParentSideBorders(insetedByDx:10).done()
expandedRows.addArrangedSubview(descriptionTitle)
expandedRows.addArrangedSubview(descriptionValue)
expandedRows.addArrangedSubview(urlTitle)
let urlAndCopy = UIStackView()
urlAndCopy.addArrangedSubview(urlValue)
urlValue.backgroundColor = .white
self.urlValue.isEnabled = false
urlValue.alignParentLeft().done()
urlAndCopy.addArrangedSubview(copyLink)
copyLink.toRightOf(urlValue,withLeftMargin: 10).done()
expandedRows.addArrangedSubview(urlAndCopy)
copyLink.onClick {
UIPasteboard.general.string = self.conferenceData?.address.value!
VoipDialog.toast(message: VoipTexts.conference_schedule_address_copied_to_clipboard)
}
joinEditDelete.axis = .horizontal
joinEditDelete.spacing = 10
joinEditDelete.distribution = .equalSpacing
contentView.addSubview(joinEditDelete)
joinEditDelete.alignUnder(view: expandedRows,withMargin: 15).alignParentRight(withMargin: 10).done()
joinEditDelete.addArrangedSubview(joinConf)
joinConf.width(150).done()
joinConf.onClick {
let view : ConferenceWaitingRoomFragment = self.VIEW(ConferenceWaitingRoomFragment.compositeViewDescription())
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
view.setDetails(subject: (self.conferenceData?.subject.value)!, url: (self.conferenceData?.address.value)!)
}
joinEditDelete.addArrangedSubview(editConf)
editConf.onClick {
guard let confData = self.conferenceData else {
Log.e("Invalid conference date, unable to edit")
VoipDialog.toast(message: VoipTexts.conference_edit_error)
return
}
let infoDate = Date(timeIntervalSince1970: Double(confData.conferenceInfo.dateTime))
ConferenceSchedulingViewModel.shared.reset()
ConferenceSchedulingViewModel.shared.scheduledDate.value = infoDate
ConferenceSchedulingViewModel.shared.scheduledTime.value = Date(timeIntervalSince1970: infoDate.timeIntervalSince1970 - Calendar.current.startOfDay(for: infoDate).timeIntervalSince1970)
ConferenceSchedulingViewModel.shared.description.value = confData.description.value
ConferenceSchedulingViewModel.shared.subject.value = confData.subject.value
ConferenceSchedulingViewModel.shared.scheduledDuration.value = ConferenceSchedulingViewModel.durationList.firstIndex(where: {$0.value == confData.conferenceInfo.duration})
ConferenceSchedulingViewModel.shared.scheduleForLater.value = true
ConferenceSchedulingViewModel.shared.selectedAddresses.value = []
confData.conferenceInfo.participants.forEach {
ConferenceSchedulingViewModel.shared.selectedAddresses.value?.append($0)
}
ConferenceSchedulingViewModel.shared.existingConfInfo = confData.conferenceInfo
// TOODO TimeZone (as Android 14.6.2022) ConferenceSchedulingViewModel.shared.scheduledTimeZone.value = self.conferenceData?.timezone
let view : ConferenceSchedulingView = self.VIEW(ConferenceSchedulingView.compositeViewDescription())
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
}
joinEditDelete.addArrangedSubview(deleteConf)
deleteConf.onClick {
let delete = ButtonAttributes(text:VoipTexts.conference_info_confirm_removal_delete, action: {
Core.get().deleteConferenceInformation(conferenceInfo: self.conferenceData!.conferenceInfo)
ScheduledConferencesViewModel.shared.computeConferenceInfoList()
self.owningTableView?.reloadData()
VoipDialog.toast(message: VoipTexts.conference_info_removed)
}, isDestructive:false)
let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {}, isDestructive:true)
VoipDialog(message:VoipTexts.conference_info_confirm_removal, givenButtons: [cancel,delete]).show()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

View file

@ -0,0 +1,124 @@
/*
* 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 ScheduledConferencesView: BackNextNavigationView, UICompositeViewDelegate, UITableViewDataSource, UITableViewDelegate {
let conferenceListView = UITableView()
let noConference = StyledLabel(VoipTheme.empty_list_font,VoipTexts.conference_no_schedule)
static let compositeDescription = UICompositeViewDescription(ScheduledConferencesView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: SideMenuView.self, 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(
backAction: {
PhoneMainView.instance().popView(self.compositeViewDescription())
},nextAction: {
ConferenceSchedulingViewModel.shared.reset()
PhoneMainView.instance().changeCurrentView(ConferenceSchedulingView.compositeDescription)
},
nextActionEnableCondition: MutableLiveData(),
title:VoipTexts.conference_scheduled)
super.nextButton.applyTintedIcons(tintedIcons: VoipTheme.conference_create_button)
self.view.addSubview(conferenceListView)
conferenceListView.isScrollEnabled = true
conferenceListView.dataSource = self
conferenceListView.delegate = self
conferenceListView.register(ScheduledConferencesCell.self, forCellReuseIdentifier: "ScheduledConferencesCell")
conferenceListView.allowsSelection = false
conferenceListView.rowHeight = UITableView.automaticDimension
if #available(iOS 15.0, *) {
conferenceListView.allowsFocus = false
}
conferenceListView.separatorStyle = .singleLine
conferenceListView.separatorColor = .white
view.addSubview(noConference)
noConference.center().done()
}
override func viewWillAppear(_ animated: Bool) {
ScheduledConferencesViewModel.shared.computeConferenceInfoList()
super.viewWillAppear(animated)
self.conferenceListView.reloadData()
self.conferenceListView.removeConstraints().done()
self.conferenceListView.matchParentSideBorders(insetedByDx: 10).alignUnder(view: super.topBar,withMargin: self.form_margin).alignParentBottom().done()
noConference.isHidden = !ScheduledConferencesViewModel.shared.daySplitted.isEmpty
super.nextButton.isEnabled = Core.get().defaultAccount != nil
}
// TableView datasource delegate
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed())
let day = daysArray[section]
return TimestampUtils.dateLongToString(date: day)
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard let header = view as? UITableViewHeaderFooterView else { return }
header.textLabel?.applyStyle(VoipTheme.conference_invite_title_font)
header.textLabel?.matchParentSideBorders().done()
}
func numberOfSections(in tableView: UITableView) -> Int {
return ScheduledConferencesViewModel.shared.daySplitted.keys.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed())
let day = daysArray[section]
return ScheduledConferencesViewModel.shared.daySplitted[day]!.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed())
let day = daysArray[indexPath.section]
guard let data = ScheduledConferencesViewModel.shared.daySplitted[day]?[indexPath.row] else {
return UITableView.automaticDimension
}
return data.expanded.value! ? UITableView.automaticDimension : 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:ScheduledConferencesCell = tableView.dequeueReusableCell(withIdentifier: "ScheduledConferencesCell") as! ScheduledConferencesCell
let daysArray = Array(ScheduledConferencesViewModel.shared.daySplitted.keys.sorted().reversed())
let day = daysArray[indexPath.section]
guard let data = ScheduledConferencesViewModel.shared.daySplitted[day]?[indexPath.row] else {
return cell
}
cell.conferenceData = data
cell.owningTableView = tableView
return cell
}
}

View file

@ -17,22 +17,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#import "UIMutedMicroButton.h"
#import "LinphoneManager.h" extension Optional {
var orNil: Any {
@implementation UIMutedMicroButton switch self {
case .none:
- (void)onOn { return "<nil>|⭕️"
linphone_core_enable_mic(LC, false); case let .some(value):
return value
}
}
} }
- (void)onOff { extension Optional: CustomStringConvertible {
linphone_core_enable_mic(LC, true);
public var description: String {
switch self {
case .some(let wrappedValue):
return "\(wrappedValue)"
default:
return "<nil>|⭕️"
}
}
} }
- (bool)onUpdate {
return (linphone_core_get_current_call(LC) || linphone_core_is_in_conference(LC)) && !linphone_core_mic_enabled(LC);
}
@end

View file

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

View file

@ -17,20 +17,24 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#import "UIBluetoothButton.h" import Foundation
#import "../Utils/AudioHelper.h" import SnapKit
#import "Utils.h" import UIKit
#import <AudioToolbox/AudioToolbox.h>
#include "linphone/linphonecore.h" extension UIButton {
func addSidePadding(p:CGFloat = 10) {
@implementation UIBluetoothButton if let w = titleLabel?.textWidth {
#define check_auresult(au, method) \ width(w+2*p).done()
if (au != 0) \ }
LOGE(@"UIBluetoothButton error for %s: ret=%ld", method, au) }
- (bool)onUpdate { func applyTintedIcons(tintedIcons: [UInt: TintableIcon]) {
return false; tintedIcons.keys.forEach { (stateRawValue) in
let tintedIcon = tintedIcons[stateRawValue]!
UIImage(named:tintedIcon.name).map {
setImage($0.tinted(with: tintedIcon.tintColor?.get()),for: UIButton.State(rawValue: stateRawValue))
}
}
}
} }
@end

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2010-2020 Belledonne Communications SARL. * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -17,11 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#import <UIKit/UIKit.h> import Foundation
#import "UIToggleButton.h"
@interface UIBluetoothButton : UIToggleButton<UIToggleButtonDelegate> {
extension UIImageView {
func tint(_ color:UIColor) {
self.image = self.image?.withRenderingMode(.alwaysTemplate)
tintColor = color
}
} }
@end

View file

@ -0,0 +1,32 @@
/*
* 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 UILabel {
var textWidth: CGFloat? {
guard let myText = self.text else { return nil }
guard let myFont = self.font else { return nil }
let rect = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: myFont], context: nil)
return ceil(labelSize.width)
}
}

View file

@ -1,7 +1,7 @@
/* /*
* Copyright (c) 2010-2020 Belledonne Communications SARL. * 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 * 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 * 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/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#import <UIKit/UIKit.h> import Foundation
import SnapKit
import UIKit
#import "UIToggleButton.h" extension UIViewController {
func VIEW<T>( _ desc: UICompositeViewDescription) -> T{
@interface UIMutedMicroButton : UIToggleButton <UIToggleButtonDelegate> { return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T
}
} }
@end

View file

@ -0,0 +1,418 @@
/*
* 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 makeHeightMatchWidth() -> UIView {
snp.makeConstraints { (make) in
make.height.equalTo(snp.width)
}
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 matchBordersOf(view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.left.right.equalTo(view)
}
return self
}
func matchParentDimmensions() -> UIView {
snp.makeConstraints { (make) in
make.left.right.top.bottom.equalToSuperview()
}
return self
}
func matchParentDimmensions(insetedByDx:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.left.top.equalToSuperview().offset(insetedByDx)
make.right.bottom.equalToSuperview().offset(-insetedByDx)
}
return self
}
func matchDimensionsWith(view:UIView, insetedByDx:CGFloat = 0) -> UIView {
snp.makeConstraints { (make) in
make.left.top.equalTo(view).offset(insetedByDx)
make.right.bottom.equalTo(view).offset(-insetedByDx)
}
return self
}
func matchParentEdges() -> UIView {
snp.makeConstraints { (make) in
make.edges.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 addRightMargin(margin:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.rightMargin.equalTo(margin)
}
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 alignHorizontalCenterWith(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.centerY.equalTo(view)
}
return self
}
func alignVerticalCenterWith(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.centerX.equalTo(view)
}
return self
}
func toLeftOf(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.right.equalTo(view.snp.left)
}
return self
}
func toLeftOf(_ view:UIView, withRightMargin:CGFloat) -> UIView {
snp.makeConstraints { (make) in
make.right.equalTo(view.snp.left).offset(-withRightMargin)
}
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
}
// Theming
func setFormInputBackground(readOnly:Bool) {
if (readOnly) {
backgroundColor = VoipTheme.voipFormDisabledFieldBackgroundColor.get()
} else {
layer.borderWidth = 1
layer.borderColor = VoipTheme.voipFormFieldBackgroundColor.get().cgColor
}
layer.cornerRadius = 3
clipsToBounds = true
}
@objc func toImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
context.saveGState()
layer.render(in: context)
context.restoreGState()
guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
UIGraphicsEndImageContext()
return image
}
}

View file

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

View file

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

View file

@ -0,0 +1,34 @@
/*
* 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 Conference : CustomStringConvertible {
public var description: String {
if let username = conferenceAddress?.username {
return "<\(username)>"
}
return "<pointer:\(Unmanaged.passUnretained(self).toOpaque())>"
}
}

View file

@ -0,0 +1,43 @@
/*
* 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().lc!
}
func showSwitchCameraButton() -> Bool {
return videoDevicesList.count > 2 // Count StaticImage camera
}
func toggleCamera() {
Log.i("[Core] Current camera device is \(videoDevice)")
var switched = false
videoDevicesList.forEach {
if (!switched && $0 != videoDevice && $0 != "StaticImage: Static picture") {
Log.i("[Core] New camera device will be \($0)")
try?setVideodevice(newValue: $0)
switched = true
}
}
}
}

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