Conference creating/scheduling

This commit is contained in:
Christophe Deschamps 2021-12-03 19:26:35 +01:00
parent 615f8f612d
commit 5f640551dd
45 changed files with 1894 additions and 66 deletions

View file

@ -1,7 +1,10 @@
<?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>
<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"/>
</dependencies>
<objects>
@ -19,6 +22,7 @@
<outlet property="switchView" destination="d5Q-XR-FNz" id="Xeo-ZG-6cr"/>
<outlet property="tableController" destination="4" id="18"/>
<outlet property="view" destination="5" id="14"/>
<outlet property="voipTitle" destination="U0U-ez-rgF" id="yEI-Gk-LdN"/>
<outlet property="waitView" destination="Ztm-hK-aBp" id="qYh-M5-heN"/>
</connections>
</placeholder>
@ -29,23 +33,23 @@
</connections>
</tapGestureRecognizer>
<view contentMode="scaleToFill" id="WKv-mw-S2B" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5">
<rect key="frame" x="0.0" y="42" width="375" height="559"/>
<rect key="frame" x="0.0" y="42" width="414" height="788"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view alpha="0.90000000000000002" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="d5Q-XR-FNz" userLabel="switchView">
<rect key="frame" x="225" y="0.0" width="150" height="66"/>
<rect key="frame" x="248" y="0.0" width="166" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="r3z-SM-lMq" userLabel="allButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="0.0" y="0.0" width="75" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="83" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="All contacts filter"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
@ -65,11 +69,11 @@
</connections>
</button>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="ibu-Ra-oZO" userLabel="selectedButtonImage">
<rect key="frame" x="0.0" y="63" width="75" height="3"/>
<rect key="frame" x="0.0" y="63" width="83" height="3"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8lQ-fv-INK" userLabel="sipButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="75" y="0.0" width="75" height="66"/>
<rect key="frame" x="83" y="0.0" width="83" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Linphone contacts filter"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
@ -89,7 +93,7 @@
</subviews>
</view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fNt-yb-wsf" userLabel="backButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="0.0" y="0.0" width="75" height="66"/>
<rect key="frame" x="0.0" y="0.0" width="82" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -142,8 +146,15 @@
</button>
</subviews>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="U0U-ez-rgF">
<rect key="frame" x="82" y="0.0" width="249" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="22"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rBc-dQ-eIj" userLabel="nextButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="300" y="0.0" width="75" height="66"/>
<rect key="frame" x="331" y="0.0" width="83" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back">
<bool key="isElement" value="YES"/>
@ -160,18 +171,18 @@
</connections>
</button>
</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 contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KRQ-Fm-3cQ" userLabel="addedContacts" customClass="UICollectionView">
<rect key="frame" x="8" y="110" width="359" height="70"/>
<rect key="frame" x="8" y="110" width="398" height="70"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<accessibility key="accessibilityConfiguration" label="addedContacts"/>
</view>
<tableView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="6">
<rect key="frame" x="5" y="178" width="365" height="381"/>
<rect key="frame" x="5" y="178" width="403" height="610"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="separatorColor" red="0.67030966281890869" green="0.71867996454238892" blue="0.75078284740447998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="dataSource" destination="4" id="11"/>
@ -179,7 +190,7 @@
</connections>
</tableView>
<searchBar contentMode="redraw" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Rd9-hK-nqR" userLabel="Contact address">
<rect key="frame" x="0.0" y="66" width="375" height="44"/>
<rect key="frame" x="0.0" y="66" width="414" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" spellCheckingType="no"/>
<connections>
@ -187,23 +198,23 @@
</connections>
</searchBar>
<view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ztm-hK-aBp" userLabel="waitView">
<rect key="frame" x="0.0" y="0.0" width="375" height="559"/>
<rect key="frame" x="0.0" y="0.0" width="414" height="788"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="nqH-qD-vgE" userLabel="activityIndicatorView">
<rect key="frame" x="179" y="267" width="20" height="20"/>
<rect key="frame" x="198" y="380" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<gestureRecognizers/>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<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="53.600000000000001" y="80.50974512743629"/>
<point key="canvasLocation" x="52.173913043478265" y="80.357142857142847"/>
</view>
<tableViewController autoresizesArchivedViewToFullSize="NO" id="4" userLabel="Suggested addresses" customClass="ChatConversationCreateTableView">
<extendedEdge key="edgesForExtendedLayout"/>
@ -233,5 +244,11 @@
<image name="security_toogle_button.png" width="21" height="21"/>
<image name="security_toogle_icon_green.png" width="33.599998474121094" height="38.400001525878906"/>
<image name="security_toogle_icon_grey.png" width="33.599998474121094" height="38.400001525878906"/>
<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>
</document>

View file

@ -270,7 +270,7 @@
<state key="disabled" image="contact_add_disabled.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
<connections>
<action selector="onAddContactClick:" destination="-1" eventType="touchUpInside" id="230"/>
<action selector="onVoipConferenceCreateClick:" destination="-1" eventType="touchUpInside" id="230"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="224" userLabel="callButton" customClass="UICallButton">

View file

@ -281,7 +281,7 @@
<state key="disabled" image="contact_add_disabled.png"/>
<state key="highlighted" backgroundImage="color_E.png"/>
<connections>
<action selector="onAddContactClick:" destination="-1" eventType="touchUpInside" id="230"/>
<action selector="onVoipConferenceCreateClick:" destination="-1" eventType="touchUpInside" id="230"/>
</connections>
</button>
<button opaque="NO" tag="25" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="224" userLabel="callButton" customClass="UICallButton">

View file

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

View file

@ -20,6 +20,7 @@
#import "ChatConversationCreateView.h"
#import "PhoneMainView.h"
#import "UIChatCreateCollectionViewCell.h"
#import "linphoneapp-Swift.h"
@implementation ChatConversationCreateView
@ -60,7 +61,9 @@ static UICompositeViewDescription *compositeDescription = nil;
[_collectionView setCollectionViewLayout:layout];
_tableController.collectionView = _collectionView;
_tableController.controllerNextButton = _nextButton;
_isForEditing = FALSE;
_isForEditing = FALSE;
_voipTitle.text = VoipTexts.call_action_participants_list;
}
- (void)viewWillAppear:(BOOL)animated {
@ -81,6 +84,16 @@ static UICompositeViewDescription *compositeDescription = nil;
frame.origin.x = _linphoneButton.frame.origin.x;
_allButton.frame = frame;
}
if (_isForVoipConference) {
_switchView.hidden = true;
_chiffreOptionView.hidden = true;
_voipTitle.hidden = false;
} else {
_voipTitle.hidden = true;
}
}
- (void)viewUpdateEvent:(NSNotification *)notif {
@ -146,18 +159,27 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onBackClick:(id)sender {
[_tableController.contactsGroup removeAllObjects];
if (_tableController.isForEditing)
[PhoneMainView.instance popToView:ChatConversationInfoView.compositeViewDescription];
else
[PhoneMainView.instance popToView:ChatsListView.compositeViewDescription];
if (_isForVoipConference) {
[PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription];
} else {
if (_tableController.isForEditing)
[PhoneMainView.instance popToView:ChatConversationInfoView.compositeViewDescription];
else
[PhoneMainView.instance popToView:ChatsListView.compositeViewDescription];
}
}
- (IBAction)onNextClick:(id)sender {
ChatConversationInfoView *view = VIEW(ChatConversationInfoView);
view.contacts = _tableController.contactsGroup;
view.create = !_isForEditing;
view.encrypted = _isEncrypted;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
if (_isForVoipConference) {
[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 {

View file

@ -255,6 +255,7 @@ static UICompositeViewDescription *compositeDescription = nil;
view.tableController.contactsGroup = [_contacts mutableCopy];
view.tableController.notFirstTime = TRUE;
view.isForEditing = FALSE;
view.isForVoipConference = FALSE;
[PhoneMainView.instance popToView:view.compositeViewDescription];
} else {
ChatConversationView *view = VIEW(ChatConversationView);
@ -280,6 +281,7 @@ static UICompositeViewDescription *compositeDescription = nil;
view.tableController.notFirstTime = TRUE;
view.isForEditing = !_create;
view.isGroupChat = TRUE;
view.isForVoipConference = FALSE;
view.tableController.contactsGroup = [_contacts mutableCopy];
[PhoneMainView.instance popToView:view.compositeViewDescription];
}

View file

@ -124,6 +124,7 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
} else if (![self selectFirstRow]) {
ChatConversationCreateView *view = VIEW(ChatConversationCreateView);
view.tableController.notFirstTime = FALSE;
view.isForVoipConference = FALSE;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
}

View file

@ -120,6 +120,7 @@ static UICompositeViewDescription *compositeDescription = nil;
view.isForEditing = false;
view.isGroupChat = isGroup;
view.tableController.notFirstTime = FALSE;
view.isForVoipConference = FALSE;
[view.tableController.contactsGroup removeAllObjects];
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}

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,79 @@
/*
* 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>()
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)
let durationFormatter = DateComponentsFormatter()
durationFormatter.unitsStyle = .positional
durationFormatter.allowedUnits = [.minute, .second ]
durationFormatter.zeroFormattingBehavior = [ .pad ]
duration.value = durationFormatter.string(from: TimeInterval(conferenceInfo.duration))
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")
}
}

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,238 @@
/*
* 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 {
let core = 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<Bool> = MediatorLiveData()
let onErrorEvent = MutableLiveData<String>()
let continueEnabled: MediatorLiveData<Bool> = MediatorLiveData()
let selectedAddresses = MutableLiveData<[Address]>([])
private var hour: Int = 0
private var minutes: Int = 0
private var coreDelegate : CoreDelegateStub? = nil
private var chatRooomDelegate : ChatRoomDelegate? = nil
init () {
coreDelegate = CoreDelegateStub(
onConferenceStateChanged : { (core: Core, conference: Conference, state: Conference.State?) -> Void in
Log.i("[Conference Creation] Conference state changed: \(state)")
if (state == .CreationPending) {
Log.i("[Conference Creation] Conference address will be \(conference.conferenceAddress?.asStringUriOnly())")
self.address.value = conference.conferenceAddress
if (self.scheduleForLater.value == true) {
self.sendConferenceInfo()
} else {
self.conferenceCreationInProgress.value = false
self.conferenceCreationCompletedEvent.value = true
}
}
},
onConferenceInfoOnSent : { (core: Core, conferenceInfo:ConferenceInfo) -> Void in
Log.i("[Conference Creation] Conference information successfully sent to all participants")
self.conferenceCreationInProgress.value = false
self.conferenceCreationCompletedEvent.value = true
},
onConferenceInfoOnParticipantError : { (core: Core, conferenceInfo: ConferenceInfo, participant: Address, error: ConferenceInfoError?) -> Void in
Log.e("[Conference Creation] Conference information wasn't sent to participant \(participant.asStringUriOnly())")
self.onErrorEvent.value = VoipTexts.conference_schedule_info_not_sent_to_participant
self.conferenceCreationInProgress.value = false
}
)
Core.get().addDelegate(delegate: coreDelegate!)
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
}
func destroy() {
core.removeDelegate(delegate: coreDelegate!)
}
func createConference() {
if (selectedAddresses.value?.count == 0) {
Log.e("[Conference Creation] Couldn't create conference without any participant!")
return
}
do {
conferenceCreationInProgress.value = true
let localAddress = core.defaultAccount?.params?.identityAddress
// TODO: Temporary workaround for chat room, to be removed once we can get matching chat room from conference
let chatRoomParams = try core.createDefaultChatRoomParams()
chatRoomParams.backend = ChatRoomBackend.FlexisipChat
chatRoomParams.groupEnabled = true
chatRoomParams.subject = subject.value!
let chatRoom = try core.createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: selectedAddresses.value!)
Log.i("[Conference Creation] Creating chat room with same subject [\(subject.value)] & participants as for conference")
chatRoom.addDelegate(delegate: chatRooomDelegate!)
let params = try core.createConferenceParams()
params.videoEnabled = true // TODO: Keep this to true ?
params.subject = subject.value!
let startTime = getConferenceStartTimestamp()
params.startTime = time_t(startTime)
scheduledDuration.value.map {
params.endTime = params.startTime + $0
}
try core.createConferenceOnServer(params: params, localAddr: localAddress, participants: selectedAddresses.value!)
} 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 sendConferenceInfo() {
let participants :[Address] = []
do {
let conferenceInfo = try Factory.Instance.createConferenceInfo()
conferenceInfo.uri = try Factory.Instance.createAddress(addr: "sip:video-conference-0@sip.linphone.org") // TODO: use address.value
conferenceInfo.participants = participants
conferenceInfo.organizer = core.defaultAccount?.params?.identityAddress
subject.value.map { conferenceInfo.subject = $0}
description.value.map { conferenceInfo.description = $0}
scheduledDuration.value.map {conferenceInfo.duration = $0 }
let timestamp = getConferenceStartTimestamp()
conferenceInfo.dateTime = time_t(timestamp)
Log.i("[Conference Creation] Conference date & time set to ${TimestampUtils.dateToString(timestamp)} ${TimestampUtils.timeToString(timestamp)}, duration = ${conferenceInfo.duration}")
core.sendConferenceInformation(conferenceInformation: conferenceInfo, text: "")
conferenceCreationInProgress.value = false
conferenceCreationCompletedEvent.value = true
} catch {
Log.e("[Conference Creation] unable to create conference \(error)")
}
}
private func getConferenceStartTimestamp() -> Double {
return scheduleForLater.value == true ? scheduledDate.value!.timeIntervalSince1970 + scheduledTime.value!.timeIntervalSince1970 : 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,223 @@
/*
* 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 ConferenceSchedulingSummaryView: NavigationView, UICompositeViewDelegate, UITableViewDataSource {
let viewModel = ConferenceSchedulingViewModel.shared
let participantsListTableView = UITableView()
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: viewModel.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()
viewModel.isEncrypted.readCurrentAndObserve { (encrypt) in
encryptedIcon.isHidden = encrypt != true
}
let subjectInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_subject_hint, liveValue: viewModel.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()
viewModel.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()
let datePicker = StyledDatePicker(liveValue: viewModel.scheduledDate,pickerMode: .date, readOnly:true)
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()
let timePicker = StyledDatePicker(liveValue: viewModel.scheduledTime,pickerMode: .time, readOnly:true)
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()
let durationValue = StyledValuePicker(liveIndex: viewModel.scheduledDuration,options: ConferenceSchedulingViewModel.durationList.map({ (duration: Duration) -> String in duration.display}), readOnly:true)
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()
let timeZoneValue = StyledValuePicker(liveIndex: viewModel.scheduledTimeZone,options: ConferenceSchedulingViewModel.timeZones.map({ (tzd: TimeZoneData) -> String in tzd.descWithOffset()}), readOnly:true)
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()
let descriptionInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_description_hint,liveValue: viewModel.description, readOnly:true)
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()
viewModel.sendInviteViaChat.readCurrentAndObserve { (sendChat) in
viaChatLabel.isHidden = sendChat != 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
viewModel.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
let createButton = FormButton()
contentView.addSubview(createButton)
createButton.onClick {
self.viewModel.createConference()
}
viewModel.scheduleForLater.readCurrentAndObserve { _ in
createButton.title = self.viewModel.scheduleForLater.value == true ? VoipTexts.conference_schedule.uppercased() : VoipTexts.conference_schedule_create.uppercased()
}
createButton.centerX().alignParentBottom(withMargin: 3*self.form_margin).alignUnder(view: participantsListTableView,withMargin: 3*self.form_margin).done()
}
func goBackParticipantsListSelection() {
let view: ChatConversationCreateView = VIEW(ChatConversationCreateView.compositeViewDescription())
let addresses = viewModel.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]) {
viewModel.selectedAddresses.value = []
return addresses.forEach { (address) in
if let address = try?Factory.Instance.createAddress(addr: address) {
viewModel.selectedAddresses.value?.append(address)
}
}
}
// TableView datasource delegate
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let participants = viewModel.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 = viewModel.selectedAddresses.value?[indexPath.row] else {
return cell
}
cell.selectionStyle = .none
cell.scheduleConfParticipantAddress = participant
cell.limeBadge.isHidden = viewModel.isEncrypted.value != true
return cell
}
}

View file

@ -0,0 +1,206 @@
/*
* 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: NavigationView, UICompositeViewDelegate {
let viewModel = ConferenceSchedulingViewModel.shared
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 }
override func viewDidLoad() {
super.viewDidLoad(
backAction: {
PhoneMainView.instance().popView(self.compositeViewDescription())
},nextAction: {
self.gotoParticipantsListSelection()
},
nextActionEnableCondition: viewModel.continueEnabled,
title:VoipTexts.conference_schedule_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: viewModel.subject)
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: viewModel.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()
viewModel.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)
dateLabel.addIndicatorIcon(iconName: "voip_mandatory")
leftColumn.addSubview(dateLabel)
dateLabel.alignParentLeft().alignParentTop(withMargin: form_margin).done()
let datePicker = StyledDatePicker(liveValue: viewModel.scheduledDate,pickerMode: .date)
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()
let timePicker = StyledDatePicker(liveValue: viewModel.scheduledTime,pickerMode: .time)
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()
let durationValue = StyledValuePicker(liveIndex: viewModel.scheduledDuration,options: ConferenceSchedulingViewModel.durationList.map({ (duration: Duration) -> String in duration.display}))
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()
let timeZoneValue = StyledValuePicker(liveIndex: viewModel.scheduledTimeZone,options: ConferenceSchedulingViewModel.timeZones.map({ (tzd: TimeZoneData) -> String in tzd.descWithOffset()}))
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()
let descriptionInput = StyledTextView(VoipTheme.conference_scheduling_font, placeHolder:VoipTexts.conference_schedule_description_hint,liveValue: viewModel.description)
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: viewModel.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()
let viaMailSwitch = StyledCheckBox(liveValue: viewModel.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: viewModel.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: encryptCombo,withMargin: 4*form_margin).centerX().matchParentSideBorders().done()
mandatoryLabel.textAlignment = .center
mandatoryLabel.alignParentBottom().done()
}
func gotoParticipantsListSelection() {
let view: ChatConversationCreateView = self.VIEW(ChatConversationCreateView.compositeViewDescription());
let addresses = viewModel.selectedAddresses.value!.map { (address) in String(address.asStringUriOnly()) }
view.tableController.contactsGroup = (addresses as NSArray).mutableCopy() as? NSMutableArray
view.isForEditing = false
view.isForVoipConference = true
view.tableController.notFirstTime = true
view.isGroupChat = true
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
}
@objc func resetViewModel() {
viewModel.reset()
}
}

View file

@ -53,7 +53,7 @@
@property(nonatomic, strong) IBOutlet UICamSwitch *videoCameraSwitch;
@property(weak, nonatomic) IBOutlet UIView *padView;
- (IBAction)onAddContactClick:(id)event;
- (IBAction)onVoipConferenceCreateClick:(id)event;
- (IBAction)onBackClick:(id)event;
- (IBAction)onAddressChange:(id)sender;
- (IBAction)onBackspaceClick:(id)sender;

View file

@ -144,6 +144,10 @@ static UICompositeViewDescription *compositeDescription = nil;
[_videoCameraSwitch setHidden:FALSE];
}
}
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
@ -388,13 +392,10 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions
- (IBAction)onAddContactClick:(id)event {
[ContactSelection setSelectionMode:ContactSelectionModeEdit];
[ContactSelection setAddAddress:[_addressField text]];
[ContactSelection setSipFilter:nil];
[ContactSelection setNameOrEmailFilter:nil];
[ContactSelection enableEmailFilter:FALSE];
[PhoneMainView.instance changeCurrentView:ContactsListView.compositeViewDescription];
- (IBAction)onVoipConferenceCreateClick:(id)event {
ConferenceSchedulingView *view = VIEW(ConferenceSchedulingView);
[view resetViewModel];
[PhoneMainView.instance changeCurrentView:ConferenceSchedulingView.compositeViewDescription];
}
- (IBAction)onBackClick:(id)event {
@ -405,7 +406,6 @@ static UICompositeViewDescription *compositeDescription = nil;
if ([self displayDebugPopup:_addressField.text]) {
_addressField.text = @"";
}
_addContactButton.enabled = _backspaceButton.enabled = ([[_addressField text] length] > 0);
if ([_addressField.text length] == 0) {
[self.view endEditing:YES];
}

View file

@ -1,9 +1,10 @@
<?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="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
<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"/>
</dependencies>
<objects>
@ -37,7 +38,7 @@
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" image="check_selected.png" translatesAutoresizingMaskIntoConstraints="NO" id="qMd-eD-DOW" userLabel="selectedImage">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="check_selected.png" translatesAutoresizingMaskIntoConstraints="NO" id="qMd-eD-DOW" userLabel="selectedImage">
<rect key="frame" x="347" y="21" width="21" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
@ -57,10 +58,10 @@
<view hidden="YES" alpha="0.65000000000000002" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="THU-mJ-O0r" userLabel="greyView">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="890.39999999999998" y="192.50374812593705"/>
@ -71,5 +72,8 @@
<image name="check_selected.png" width="75.199996948242188" height="51.200000762939453"/>
<image name="linphone_user.png" width="41.599998474121094" height="42.400001525878906"/>
<image name="security_toogle_icon_green.png" width="33.599998474121094" height="38.400001525878906"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>

View file

@ -77,6 +77,8 @@
[self accountUpdate:account];
[self updateUI:linphone_core_get_calls_nb(LC)];
[self updateVoicemail];
_callQualityButton.userInteractionEnabled = false;
}
- (void)viewWillDisappear:(BOOL)animated {

View file

@ -27,6 +27,17 @@ extension Optional {
return value
}
}
}
extension Optional: CustomStringConvertible {
public var description: String {
switch self {
case .some(let wrappedValue):
return "\(wrappedValue)"
default:
return "<nil>|⭕️"
}
}
}

View file

@ -0,0 +1,30 @@
/*
* 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 UIButton {
func addSidePadding(p:CGFloat = 10) {
if let w = titleLabel?.textWidth {
width(w+p).done()
}
}
}

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

@ -30,6 +30,7 @@ extension UIView {
return self
}
func square(_ size:Int) -> UIView {
snp.makeConstraints { (make) in
make.width.equalTo(size)
@ -91,6 +92,13 @@ extension UIView {
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()
@ -98,6 +106,13 @@ extension UIView {
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)
@ -112,6 +127,13 @@ extension UIView {
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)
@ -226,7 +248,7 @@ extension UIView {
func alignParentRight(withMargin:CGFloat) -> UIView {
return alignParentRight(withMargin:Int(withMargin))
}
func toRightOf(_ view:UIView, withLeftMargin:Int = 0) -> UIView {
snp.makeConstraints { (make) in
@ -239,6 +261,15 @@ extension UIView {
return toRightOf(view,withLeftMargin: Int(withLeftMargin))
}
func alignHorizontalCenterWith(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.centerY.equalTo(view)
}
return self
}
func toLeftOf(_ view:UIView) -> UIView {
snp.makeConstraints { (make) in
make.right.equalTo(view.snp.left)
@ -318,9 +349,33 @@ extension UIView {
@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,93 @@
/*
* 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 NavigationView: UIViewController {
// layout constants
let top_bar_height = 60.0
let navigation_buttons_padding = 18.0
let content_margin_top = 20
// User by subviews
let form_margin = 10.0
let form_input_height = 40.0
let schdule_for_later_height = 80.0
let description_height = 150.0
let titleLabel = StyledLabel(VoipTheme.calls_list_header_font)
let topBar = UIView()
let scrollView = UIScrollView()
let contentView = UIView()
var backAction : (() -> Void)? = nil
var nextAction : (() -> Void)? = nil
let backButton = CallControlButton(buttonTheme:VoipTheme.nav_button("back_default"))
let nextButton = CallControlButton(buttonTheme:VoipTheme.nav_button("next_default"))
func viewDidLoad(backAction : @escaping () -> Void,
nextAction : @escaping () -> Void,
nextActionEnableCondition: MutableLiveData<Bool>,
title:String) {
self.backAction = backAction
self.nextAction = nextAction
self.view.addSubview(topBar)
topBar.alignParentTop().height(top_bar_height).matchParentSideBorders().done()
topBar.addSubview(backButton)
backButton.alignParentLeft().matchParentHeight().done()
backButton.onClickAction = backAction
topBar.addSubview(nextButton)
nextButton.alignParentRight().matchParentHeight().done()
nextButton.onClickAction = nextAction
nextActionEnableCondition.readCurrentAndObserve { (enableNext) in
self.nextButton.isEnabled = enableNext == true
}
topBar.addSubview(titleLabel)
titleLabel.matchParentHeight().centerX().done()
titleLabel.text = title
super.viewDidLoad()
view.addSubview(scrollView)
scrollView.alignUnder(view: topBar, withMargin: content_margin_top).alignParentBottom().matchParentSideBorders().done()
scrollView.addSubview(contentView)
contentView.matchBordersOf(view: view).alignParentBottom().alignParentTop().done() // don't forget a bottom constraint b/w last element of contentview and contentview
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
topBar.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get()
}
}

View file

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2020 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/>.
*/
class TimestampUtils {
static func is24Hour() -> Bool {
let dateFormat = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)!
return dateFormat.firstIndex(of: "a") == nil
}
static func timeToString(unixTimestamp: Double, timestampInSecs: Bool = true) -> String {
let date = Date(timeIntervalSince1970: unixTimestamp)
let dateFormat = DateFormatter()
dateFormat.dateFormat = is24Hour() ? "HH'h'mm" : "h:mm a"
return dateFormat.string(from: date)
}
static func toString(
unixTimestamp: Double,
onlyDate: Bool = false,
timestampInSecs: Bool = true,
shortDate: Bool = true
) -> String {
let date = Date(timeIntervalSince1970: unixTimestamp)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = onlyDate ? .none : .long
dateFormatter.timeStyle = shortDate ? .short : .long
dateFormatter.doesRelativeDateFormatting = true
return dateFormatter.string(from: date)
}
static func dateToString(date:Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
return dateFormatter.string(from: date)
}
static func timeToString(date:Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
return dateFormatter.string(from: date)
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linhome
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class MediatorLiveData<T> : MutableLiveData<T> {
private var sources : [MutableLiveData<T>?] = []
override init(_ initial:T) {
super.init(initial)
}
override init () {
super.init()
}
func addSource(_ source: MutableLiveData<T>, _ onSourceChange:@escaping ()->Void) {
sources.append(source)
source.observe(onChange: { _ in
onSourceChange()
})
}
func destroy() {
sources.forEach { $0?.clearObservers() }
clearObservers()
}
}

View file

@ -121,7 +121,7 @@ class ControlsViewModel {
private func shouldProximitySensorBeEnabled() -> Bool {
return isVideoEnabled.value != true && nonEarpieceOutputAudioDevice.value != true
return core.callsNb > 0 && isVideoEnabled.value != true && nonEarpieceOutputAudioDevice.value != true
}

View file

@ -27,7 +27,7 @@ struct TextStyle {
var align:NSTextAlignment
var font:String
var size:Float
func boldEd() -> TextStyle {
return self.font.contains("Bold") ? self : TextStyle(fgColor: self.fgColor,bgColor: self.bgColor,allCaps: self.allCaps,align: self.align,font: self.font.replacingOccurrences(of: "Regular", with: "Bold"), size: self.size)
}
@ -46,6 +46,23 @@ extension UILabel {
let fontSizeMultiplier: Float = (UIDevice.ipad() ? 1.25 : UIDevice.is5SorSEGen1() ? 0.9 : 1.0)
font = UIFont.init(name: style.font, size: CGFloat(style.size*fontSizeMultiplier))
}
func addIndicatorIcon(iconName:String, _ padding:CGFloat = 5.0, trailing: Bool = true) {
let imageAttachment = NSTextAttachment()
imageAttachment.image = UIImage(named:iconName)
imageAttachment.bounds = CGRect(x: 5.0, y: 4.0, width: font.lineHeight - 2*padding, height: font.lineHeight - 2*padding)
let iconString = NSMutableAttributedString(attachment: imageAttachment)
let textXtring = NSMutableAttributedString(string: text != nil ? text! : "")
if (trailing) {
textXtring.append(iconString)
self.text = nil
self.attributedText = textXtring
} else {
iconString.append(textXtring)
self.text = nil
self.attributedText = iconString
}
}
}
extension UIButton {
@ -59,3 +76,18 @@ extension UIButton {
contentHorizontalAlignment = style.align == .left ? .left : style.align == .center ? .center : style.align == .right ? .right : .left
}
}
extension UITextView {
func applyStyle(_ style:TextStyle) {
textColor = style.fgColor.get()
backgroundColor = style.bgColor.get()
if (style.allCaps) {
text = self.text?.uppercased()
tag = 1
}
textAlignment = style.align
let fontSizeMultiplier: Float = (UIDevice.ipad() ? 1.25 : UIDevice.is5SorSEGen1() ? 0.9 : 1.0)
font = UIFont.init(name: style.font, size: CGFloat(style.size*fontSizeMultiplier))
}
}

View file

@ -20,7 +20,7 @@
import Foundation
import UIKit
class VoipTexts { // From android key names. Added intentionnally with NSLocalizedString calls for each key, so it can be picked up by translation system (Weblate or Xcode).
@objc class VoipTexts : NSObject { // From android key names. Added intentionnally with NSLocalizedString calls for each key, so it can be picked up by translation system (Weblate or Xcode).
static let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
@ -40,7 +40,7 @@ class VoipTexts { // From android key names. Added intentionnally with NSLocaliz
static let call_error_temporarily_unavailable = NSLocalizedString("Temporarily unavailable",comment:"")
static let call_error_generic = NSLocalizedString("Error: %s",comment:"")
static let call_video_update_requested_dialog = NSLocalizedString("Correspondent would like to turn the video on",comment:"")
static let call_action_participants_list = NSLocalizedString("Participants list",comment:"")
@objc static let call_action_participants_list = NSLocalizedString("Participants list",comment:"")
static let call_action_chat = NSLocalizedString("Chat",comment:"")
static let call_action_calls_list = NSLocalizedString("Calls list",comment:"")
static let call_action_numpad = NSLocalizedString("Numpad",comment:"")

View file

@ -114,6 +114,10 @@ class VoipTheme { // Names & values replicated from Android
static let conference_mode_title = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
static let conference_mode_title_selected = conference_mode_title.boldEd()
static let conference_scheduling_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
// Buttons Background (State colors)
@ -337,6 +341,18 @@ class VoipTheme { // Names & values replicated from Android
tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_merge_calls",tintColor: LightDarkColor(.white,.white))],
backgroundStateColors: button_round_background)
// Navigation
static func nav_button(_ iconName:String) -> ButtonTheme {
return ButtonTheme(
tintableStateIcons:[
UIButton.State.normal.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(.darkGray,.white)),
UIButton.State.highlighted.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(primary_color,primary_color)),
UIButton.State.disabled.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(light_grey_color,light_grey_color)),
],
backgroundStateColors: [:])
}
}

View file

@ -39,6 +39,7 @@ class AudioRoutesView: UIStackView {
// bluetooth
let blueTooth = CallControlButton(buttonTheme: VoipTheme.route_bluetooth, onClickAction: {
ControlsViewModel.shared.forceBluetoothAudioRoute()
ControlsViewModel.shared.audioRoutesSelected.value = false
})
addArrangedSubview(blueTooth)
@ -49,6 +50,7 @@ class AudioRoutesView: UIStackView {
// Earpiece
let earpiece = CallControlButton(buttonTheme: VoipTheme.route_earpiece, onClickAction: {
ControlsViewModel.shared.forceEarpieceAudioRoute()
ControlsViewModel.shared.audioRoutesSelected.value = false
})
addArrangedSubview(earpiece)
ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (isSpeakerSelected) in
@ -61,6 +63,7 @@ class AudioRoutesView: UIStackView {
// Speaker
let speaker = CallControlButton(buttonTheme: VoipTheme.route_speaker, onClickAction: {
ControlsViewModel.shared.forceSpeakerAudioRoute()
ControlsViewModel.shared.audioRoutesSelected.value = false
})
addArrangedSubview(speaker)
ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (selected) in

View file

@ -30,12 +30,15 @@ class VoipParticipantCell: UITableViewCell {
let dismiss_icon_inset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let dismiss_right_margin = 10
let check_box_size = 20.0
let cell_height = 80.0
static let cell_height = 80.0
let avatar_left_margin = 15.0
let texts_left_margin = 20.0
let lime_badge_width = 18.0
let lime_badge_offset = -10.0
let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:VoipTheme.primaryTextColor, textStyle: VoipTheme.call_generated_avatar_small)
let limeBadge = UIImageView(image: UIImage(named: "security_toggle_icon_green"))
let displayName = StyledLabel(VoipTheme.conference_participant_name_font)
let sipAddress = StyledLabel(VoipTheme.conference_participant_sip_uri_font)
let isAdminView = UIView()
@ -47,6 +50,7 @@ class VoipParticipantCell: UITableViewCell {
var participantData: ConferenceParticipantData? = nil {
didSet {
if let data = participantData {
limeBadge.isHidden = true
avatar.fillFromAddress(address: data.participant.address!)
displayName.text = data.participant.address?.addressBookEnhancedDisplayName()
sipAddress.text = data.participant.address?.asStringUriOnly()
@ -70,14 +74,29 @@ class VoipParticipantCell: UITableViewCell {
}
}
var scheduleConfParticipantAddress: Address? = nil {
didSet {
if let address = scheduleConfParticipantAddress {
avatar.fillFromAddress(address: address)
displayName.text = address.addressBookEnhancedDisplayName()
sipAddress.text = address.asStringUriOnly()
self.isAdminView.isHidden = true
self.removePart?.isHidden = true
}
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.height(cell_height).matchParentSideBorders().done()
contentView.height(VoipParticipantCell.cell_height).matchParentSideBorders().done()
contentView.addSubview(avatar)
avatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done()
limeBadge.contentMode = .scaleAspectFit
contentView.addSubview(limeBadge)
limeBadge.toRightOf(avatar,withLeftMargin: lime_badge_offset).width(lime_badge_width).done()
let nameAddress = UIView()
nameAddress.addSubview(displayName)
@ -105,6 +124,7 @@ class VoipParticipantCell: UITableViewCell {
isAdminView.height(check_box_size).toLeftOf(removePart!).centerY().done()
}
required init?(coder: NSCoder) {

View file

@ -42,7 +42,6 @@ class Avatar : UIImageView {
}
func fillFromAddress(address:Address) {
if let image = address.contact()?.avatar() {
self.image = image

View file

@ -28,11 +28,12 @@ class ButtonWithStateBackgrounds : UIButton {
super.init(coder: coder)
}
init (backgroundStateColors: [UInt: LightDarkColor]) {
init (backgroundStateColors: [UInt: LightDarkColor], iconName:String? = nil) {
super.init(frame: .zero)
backgroundStateColors.keys.forEach { (stateRawValue) in
setBackgroundColor(color: backgroundStateColors[stateRawValue]!.get(), forState: UIButton.State(rawValue: stateRawValue))
}
iconName.map { setImage(UIImage(named: $0), for: .normal) }
}
func setBackgroundColor(color: UIColor, forState: UIControl.State) {

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
import UIKit
import SwiftUI
class FormButton : ButtonWithStateBackgrounds {
let button_radius = 3.0
let button_height = 40.0
required init?(coder: NSCoder) {
super.init(coder: coder)
}
var title: String? {
didSet {
setTitle(title, for: .normal)
addSidePadding()
}
}
init () {
super.init(backgroundStateColors: VoipTheme.primary_colors_background)
layer.cornerRadius = button_radius
clipsToBounds = true
applyTitleStyle(VoipTheme.big_button)
height(button_height).done()
addSidePadding()
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
import DropDown
class StyledCheckBox: UIButton {
// layout constants
let button_size = 25.0
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init (liveValue:MutableLiveData<Bool>) {
super.init(frame: .zero)
setBackgroundImage(UIImage(named:"voip_checkbox_unchecked")/*?.tinted(with: VoipTheme.light_grey_color)*/,for: .normal) // tinting not working with those icons
setBackgroundImage(UIImage(named:"voip_checkbox_checked")/*?.tinted(with: VoipTheme.primary_color)*/,for: .selected)
onClick {
liveValue.value = !liveValue.value!
self.isSelected = liveValue.value!
}
size(w: button_size,h: button_size).done()
liveValue.readCurrentAndObserve { (value) in
self.isSelected = value!
}
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class StyledDatePicker: UIView {
// layout constants
let chevron_margin = 10
let form_input_height = 38.0
var liveValue:MutableLiveData<Date>?
let formattedLabel = StyledLabel(VoipTheme.conference_scheduling_font)
var pickerMode:UIDatePicker.Mode = .date
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init (liveValue:MutableLiveData<Date>, pickerMode:UIDatePicker.Mode, readOnly:Bool = false) {
super.init(frame: .zero)
self.liveValue = liveValue
self.pickerMode = pickerMode
let datePicker = UIDatePicker()
addSubview(datePicker)
datePicker.datePickerMode = pickerMode
datePicker.addTarget(self, action: #selector(valueChanged), for: .valueChanged)
datePicker.matchParentDimmensions().done()
formattedLabel.isUserInteractionEnabled = false
formattedLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
addSubview(formattedLabel)
formattedLabel.matchParentDimmensions().done()
let chevron = UIImageView(image: UIImage(named: "chevron_list_close"))
addSubview(chevron)
chevron.alignParentRight(withMargin: chevron_margin).centerY().done()
chevron.isHidden = readOnly
setFormInputBackground(readOnly:readOnly)
height(form_input_height).done()
datePicker.date = liveValue.value!
self.valueChanged(datePicker: datePicker)
if (readOnly) {
formattedLabel.textColor = formattedLabel.textColor.withAlphaComponent(0.5)
}
isUserInteractionEnabled = !readOnly
}
@objc func valueChanged(datePicker: UIDatePicker) {
liveValue!.value = datePicker.date
formattedLabel.text = " "+(pickerMode == .date ? TimestampUtils.dateToString(date: datePicker.date) : TimestampUtils.timeToString(date: datePicker.date))
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
class StyledSwitch: UISwitch {
var liveValue:MutableLiveData<Bool>?
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init (liveValue:MutableLiveData<Bool>) {
super.init(frame: .zero)
self.liveValue = liveValue
tintColor = VoipTheme.light_grey_color
onTintColor = VoipTheme.green_color
addTarget(self, action: #selector(valueChanged), for: .valueChanged)
liveValue.readCurrentAndObserve { (value) in
self.isOn = value == true
}
transform = CGAffineTransform(scaleX: 0.75, y: 0.75)
}
@objc func valueChanged(mySwitch: UISwitch) {
liveValue!.value = mySwitch.isOn
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
class StyledTextView: UITextView, UITextViewDelegate {
var placeholder:String?
var style:TextStyle?
var liveValue: MutableLiveData<String>? = nil
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init (_ style:TextStyle, placeHolder:String? = nil, liveValue: MutableLiveData<String>, readOnly:Bool = false) {
self.style = style
self.liveValue = liveValue
super.init(frame:.zero, textContainer: nil)
applyStyle(style)
setFormInputBackground(readOnly:readOnly)
placeHolder.map {
self.placeholder = $0
}
delegate = self
liveValue.readCurrentAndObserve { (value) in
self.text = value
if (value == nil || value?.count == 0) {
self.showPlaceHolder()
}
}
if (readOnly) {
textColor = textColor?.withAlphaComponent(0.5)
}
isUserInteractionEnabled = !readOnly
}
func textViewDidBeginEditing(_ textView: UITextView) {
if text == placeholder {
placeholder = textView.text
text = ""
textColor = style?.fgColor.get().withAlphaComponent(1.0)
}
}
func textViewDidEndEditing(_ textView: UITextView) {
if text == "" {
showPlaceHolder()
}
}
private func showPlaceHolder() {
text = placeholder
textColor = style?.fgColor.get().withAlphaComponent(0.5)
}
func textViewDidChange(_ textView: UITextView) {
liveValue?.value = textView.text
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Foundation
import UIKit
import DropDown
class StyledValuePicker: UIView {
// layout constants
let chevron_margin = 10.0
let form_input_height = 38.0
let formattedLabel = StyledLabel(VoipTheme.conference_scheduling_font)
var pickerMode:UIDatePicker.Mode = .date
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init (liveIndex:MutableLiveData<Int>, options:[String], readOnly:Bool = false) {
super.init(frame: .zero)
formattedLabel.isUserInteractionEnabled = false
formattedLabel.backgroundColor = VoipTheme.voipFormBackgroundColor.get()
formattedLabel.text = " "+options[liveIndex.value!]
if (readOnly) {
formattedLabel.textColor = formattedLabel.textColor.withAlphaComponent(0.5)
}
addSubview(formattedLabel)
formattedLabel.alignParentLeft().alignParentRight(withMargin: (readOnly ? chevron_margin : form_input_height)).matchParentHeight().done()
let chevron = UIImageView(image: UIImage(named: "chevron_list_close"))
addSubview(chevron)
chevron.alignParentRight(withMargin: chevron_margin).centerY().done()
chevron.isHidden = readOnly
setFormInputBackground(readOnly:readOnly)
DropDown.appearance().textColor = VoipTheme.conference_scheduling_font.fgColor.get()
DropDown.appearance().selectedTextColor = VoipTheme.conference_scheduling_font.fgColor.get()
DropDown.appearance().textFont = formattedLabel.font
DropDown.appearance().backgroundColor = .white
DropDown.appearance().selectionBackgroundColor = VoipTheme.light_grey_color
DropDown.appearance().cellHeight = form_input_height
let dropDown = DropDown()
dropDown.anchorView = self
dropDown.bottomOffset = CGPoint(x: 0, y:(dropDown.anchorView?.plainView.bounds.height)!)
dropDown.dataSource = options
dropDown.backgroundColor = .white
dropDown.selectionAction = { [unowned self] (index: Int, item: String) in
liveIndex.value = index
dropDown.selectRow(at: index)
//dropDown.tableView.scrollToRow(at: IndexPath(row: index, section: 0), at: .middle, animated: true)
formattedLabel.text = " "+options[liveIndex.value!]
dropDown.hide()
}
onClick {
dropDown.show()
}
height(form_input_height).done()
liveIndex.readCurrentAndObserve { (value) in
dropDown.selectRow(value!)
}
isUserInteractionEnabled = !readOnly
}
}

View file

@ -27,6 +27,8 @@ target 'linphone' do
# Pods for linphone
pod 'SVProgressHUD'
pod 'SnapKit'
pod 'DropDown'
pod 'IQKeyboardManager'
all_pods
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

View file

@ -662,6 +662,9 @@
C61B1BF22667D075001A4E4A /* menu_security_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF12667D075001A4E4A /* menu_security_default.png */; };
C61B1BF42667D202001A4E4A /* more_menu_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF32667D202001A4E4A /* more_menu_default.png */; };
C61B1BF72667EC6B001A4E4A /* ephemeral_messages_color_A.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */; };
C61E409B275A20A300CCE602 /* ConferenceSchedulingSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61E409A275A20A300CCE602 /* ConferenceSchedulingSummaryView.swift */; };
C61E409D275A94E300CCE602 /* UILabelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61E409C275A94E300CCE602 /* UILabelExtensions.swift */; };
C61E409F275A962100CCE602 /* FormButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C61E409E275A962100CCE602 /* FormButton.swift */; };
C622E3EF26A81290004F5434 /* vr_stop.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3E926A8128F004F5434 /* vr_stop.png */; };
C622E3F026A81290004F5434 /* vr_wave.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EA26A8128F004F5434 /* vr_wave.png */; };
C622E3F126A81290004F5434 /* vr_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EB26A8128F004F5434 /* vr_on.png */; };
@ -767,6 +770,8 @@
C6824FBA27219D890043D4FC /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6824FB927219D890043D4FC /* IncomingCallView.swift */; };
C683B20E2722702300D4E15C /* VoipTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C683B20D2722702300D4E15C /* VoipTheme.swift */; };
C683B213272276CF00D4E15C /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C683B212272276CF00D4E15C /* UIColorExtensions.swift */; };
C690CCB1275764CD00609077 /* ConferenceSchedulingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C690CCB0275764CD00609077 /* ConferenceSchedulingView.swift */; };
C690CCB42757683800609077 /* NavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C690CCB32757683800609077 /* NavigationView.swift */; };
C6A1BB3526E8815400540D50 /* menu_info.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3126E8815300540D50 /* menu_info.png */; };
C6A1BB3626E8815400540D50 /* menu_forward_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3226E8815400540D50 /* menu_forward_default.png */; };
C6A1BB3726E8815400540D50 /* menu_copy_text_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */; };
@ -802,10 +807,26 @@
C6D09F4127428626003C2173 /* IceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F4027428626003C2173 /* IceState.swift */; };
C6D09F43274288D4003C2173 /* PayloadType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F42274288D4003C2173 /* PayloadType.swift */; };
C6D09F4B27438707003C2173 /* CallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F4A27438706003C2173 /* CallsListView.swift */; };
C6D1E41B275937A0008EB388 /* UIButtonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D1E41A275937A0008EB388 /* UIButtonExtensions.swift */; };
C6D1E41E2759396B008EB388 /* voip_checkbox_unchecked.png in Resources */ = {isa = PBXBuildFile; fileRef = C6D1E41C2759396B008EB388 /* voip_checkbox_unchecked.png */; };
C6D1E41F2759396B008EB388 /* voip_checkbox_checked.png in Resources */ = {isa = PBXBuildFile; fileRef = C6D1E41D2759396B008EB388 /* voip_checkbox_checked.png */; };
C6D1E42127593A30008EB388 /* StyledCheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D1E42027593A30008EB388 /* StyledCheckBox.swift */; };
C6D1E42427595988008EB388 /* security_toggle_icon_grey.png in Resources */ = {isa = PBXBuildFile; fileRef = C6D1E42227595987008EB388 /* security_toggle_icon_grey.png */; };
C6D1E42527595988008EB388 /* security_toggle_icon_green.png in Resources */ = {isa = PBXBuildFile; fileRef = C6D1E42327595988008EB388 /* security_toggle_icon_green.png */; };
C6D1EC4A274D212B0091881C /* UICamSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D1EC49274D212B0091881C /* UICamSwitch.m */; };
C6D52B45274648E500904660 /* VoipGridParticipantCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D52B44274648E500904660 /* VoipGridParticipantCell.swift */; };
C6DA657C261C950C0020CB43 /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; };
C6DB1DE22757E35F00A22704 /* StyledTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DB1DE12757E35F00A22704 /* StyledTextView.swift */; };
C6DB1DE42758E49E00A22704 /* StyledSwitch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DB1DE32758E49E00A22704 /* StyledSwitch.swift */; };
C6DB1DE6275903C500A22704 /* StyledDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DB1DE5275903C500A22704 /* StyledDatePicker.swift */; };
C6DB1DE82759136100A22704 /* StyledValuePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DB1DE72759136000A22704 /* StyledValuePicker.swift */; };
C6EA2F4827514D09008E60F8 /* voip_call_forward.png in Resources */ = {isa = PBXBuildFile; fileRef = C6EA2F4727514D08008E60F8 /* voip_call_forward.png */; };
C6EA2F592754C91C008E60F8 /* ScheduledConferenceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EA2F582754C91C008E60F8 /* ScheduledConferenceData.swift */; };
C6EA2F5F2754CFB0008E60F8 /* TimestampUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EA2F5E2754CFB0008E60F8 /* TimestampUtils.swift */; };
C6EA2F632754DBBE008E60F8 /* TimeZoneData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EA2F622754DBBE008E60F8 /* TimeZoneData.swift */; };
C6EA2F672754DC45008E60F8 /* ConferenceSchedulingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EA2F662754DC45008E60F8 /* ConferenceSchedulingViewModel.swift */; };
C6EA2F6B2754DDF9008E60F8 /* Duration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EA2F6A2754DDF9008E60F8 /* Duration.swift */; };
C6EA2F6D2754E3DC008E60F8 /* MediatorLiveData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6EA2F6C2754E3DC008E60F8 /* MediatorLiveData.swift */; };
C6F2D4EF27392D970071BA52 /* CallsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4EE27392D960071BA52 /* CallsViewModel.swift */; };
C6F2D4F1273935860071BA52 /* CallExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4F0273935860071BA52 /* CallExtensions.swift */; };
C6F2D4F32739475C0071BA52 /* ActiveCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4F22739475C0071BA52 /* ActiveCallView.swift */; };
@ -1800,6 +1821,9 @@
C61B1BF12667D075001A4E4A /* menu_security_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_security_default.png; sourceTree = "<group>"; };
C61B1BF32667D202001A4E4A /* more_menu_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = more_menu_default.png; sourceTree = "<group>"; };
C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ephemeral_messages_color_A.png; sourceTree = "<group>"; };
C61E409A275A20A300CCE602 /* ConferenceSchedulingSummaryView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceSchedulingSummaryView.swift; sourceTree = "<group>"; };
C61E409C275A94E300CCE602 /* UILabelExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabelExtensions.swift; sourceTree = "<group>"; };
C61E409E275A962100CCE602 /* FormButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormButton.swift; sourceTree = "<group>"; };
C622E3E926A8128F004F5434 /* vr_stop.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_stop.png; sourceTree = "<group>"; };
C622E3EA26A8128F004F5434 /* vr_wave.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_wave.png; sourceTree = "<group>"; };
C622E3EB26A8128F004F5434 /* vr_on.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = vr_on.png; sourceTree = "<group>"; };
@ -1906,6 +1930,8 @@
C6824FB927219D890043D4FC /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
C683B20D2722702300D4E15C /* VoipTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoipTheme.swift; sourceTree = "<group>"; };
C683B212272276CF00D4E15C /* UIColorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = "<group>"; };
C690CCB0275764CD00609077 /* ConferenceSchedulingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceSchedulingView.swift; sourceTree = "<group>"; };
C690CCB32757683800609077 /* NavigationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationView.swift; sourceTree = "<group>"; };
C6A1BB3126E8815300540D50 /* menu_info.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_info.png; sourceTree = "<group>"; };
C6A1BB3226E8815400540D50 /* menu_forward_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_forward_default.png; sourceTree = "<group>"; };
C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_copy_text_default.png; sourceTree = "<group>"; };
@ -1942,11 +1968,27 @@
C6D09F4027428626003C2173 /* IceState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IceState.swift; sourceTree = "<group>"; };
C6D09F42274288D4003C2173 /* PayloadType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PayloadType.swift; sourceTree = "<group>"; };
C6D09F4A27438706003C2173 /* CallsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallsListView.swift; sourceTree = "<group>"; };
C6D1E41A275937A0008EB388 /* UIButtonExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIButtonExtensions.swift; sourceTree = "<group>"; };
C6D1E41C2759396B008EB388 /* voip_checkbox_unchecked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = voip_checkbox_unchecked.png; sourceTree = "<group>"; };
C6D1E41D2759396B008EB388 /* voip_checkbox_checked.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = voip_checkbox_checked.png; sourceTree = "<group>"; };
C6D1E42027593A30008EB388 /* StyledCheckBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledCheckBox.swift; sourceTree = "<group>"; };
C6D1E42227595987008EB388 /* security_toggle_icon_grey.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = security_toggle_icon_grey.png; sourceTree = "<group>"; };
C6D1E42327595988008EB388 /* security_toggle_icon_green.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = security_toggle_icon_green.png; sourceTree = "<group>"; };
C6D1EC48274D212A0091881C /* UICamSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICamSwitch.h; sourceTree = "<group>"; };
C6D1EC49274D212B0091881C /* UICamSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICamSwitch.m; sourceTree = "<group>"; };
C6D52B44274648E500904660 /* VoipGridParticipantCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoipGridParticipantCell.swift; sourceTree = "<group>"; };
C6DA657B261C950C0020CB43 /* VFSUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VFSUtil.swift; sourceTree = "<group>"; };
C6DB1DE12757E35F00A22704 /* StyledTextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledTextView.swift; sourceTree = "<group>"; };
C6DB1DE32758E49E00A22704 /* StyledSwitch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledSwitch.swift; sourceTree = "<group>"; };
C6DB1DE5275903C500A22704 /* StyledDatePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledDatePicker.swift; sourceTree = "<group>"; };
C6DB1DE72759136000A22704 /* StyledValuePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StyledValuePicker.swift; sourceTree = "<group>"; };
C6EA2F4727514D08008E60F8 /* voip_call_forward.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = voip_call_forward.png; sourceTree = "<group>"; };
C6EA2F582754C91C008E60F8 /* ScheduledConferenceData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScheduledConferenceData.swift; sourceTree = "<group>"; };
C6EA2F5E2754CFB0008E60F8 /* TimestampUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimestampUtils.swift; sourceTree = "<group>"; };
C6EA2F622754DBBE008E60F8 /* TimeZoneData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZoneData.swift; sourceTree = "<group>"; };
C6EA2F662754DC45008E60F8 /* ConferenceSchedulingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConferenceSchedulingViewModel.swift; sourceTree = "<group>"; };
C6EA2F6A2754DDF9008E60F8 /* Duration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Duration.swift; sourceTree = "<group>"; };
C6EA2F6C2754E3DC008E60F8 /* MediatorLiveData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediatorLiveData.swift; sourceTree = "<group>"; };
C6F2D4EE27392D960071BA52 /* CallsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallsViewModel.swift; sourceTree = "<group>"; };
C6F2D4F0273935860071BA52 /* CallExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallExtensions.swift; sourceTree = "<group>"; };
C6F2D4F22739475C0071BA52 /* ActiveCallView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveCallView.swift; sourceTree = "<group>"; };
@ -2220,6 +2262,7 @@
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
C6EA2F492752237C008E60F8 /* Conference */,
C65A5D41272184CE005BA038 /* SwiftUtil */,
C65A5D2D2721683B005BA038 /* Voip */,
22E0A81D111C44E100B04932 /* AboutView.h */,
@ -2611,6 +2654,10 @@
633FEBE11D3CD5570014B822 /* images */ = {
isa = PBXGroup;
children = (
C6D1E42327595988008EB388 /* security_toggle_icon_green.png */,
C6D1E42227595987008EB388 /* security_toggle_icon_grey.png */,
C6D1E41D2759396B008EB388 /* voip_checkbox_checked.png */,
C6D1E41C2759396B008EB388 /* voip_checkbox_unchecked.png */,
C6EA2F4727514D08008E60F8 /* voip_call_forward.png */,
C6277DA7274BF1CD00406FB9 /* voip_radio_off.png */,
C6277DA6274BF1CD00406FB9 /* voip_radio_on.png */,
@ -3313,6 +3360,7 @@
isa = PBXGroup;
children = (
C65A5D3727216CC0005BA038 /* MutableLiveData.swift */,
C6EA2F6C2754E3DC008E60F8 /* MediatorLiveData.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -3320,6 +3368,8 @@
C65A5D41272184CE005BA038 /* SwiftUtil */ = {
isa = PBXGroup;
children = (
C690CCB22757674700609077 /* GenericViews */,
C6EA2F5E2754CFB0008E60F8 /* TimestampUtils.swift */,
C683B211272276C100D4E15C /* Extensions */,
C65A5D3427216CAB005BA038 /* ViewModel */,
);
@ -3330,10 +3380,16 @@
isa = PBXGroup;
children = (
C6710F582722A9B800ED888F /* StyledLabel.swift */,
C6DB1DE32758E49E00A22704 /* StyledSwitch.swift */,
C6DB1DE5275903C500A22704 /* StyledDatePicker.swift */,
C6DB1DE72759136000A22704 /* StyledValuePicker.swift */,
C6D1E42027593A30008EB388 /* StyledCheckBox.swift */,
C6DB1DE12757E35F00A22704 /* StyledTextView.swift */,
C6710FD82722BD0100ED888F /* UICallTimer.swift */,
C6710F4E2722903200ED888F /* RotatingSpinner.swift */,
C6710FE02722F0E400ED888F /* Avatar.swift */,
C6710FE82723DD7D00ED888F /* CallControlButton.swift */,
C61E409E275A962100CCE602 /* FormButton.swift */,
C658614B273E5B5E00A0DBFC /* VoipExtraButton.swift */,
C6D09F3C273EE467003C2173 /* BouncingCounter.swift */,
C6F2D502273BAC030071BA52 /* ButtonWithStateBackgrounds.swift */,
@ -3395,6 +3451,8 @@
C60D265B272AA0BD006238BB /* UIImageExtensions.swift */,
C6710FDB2722C3BB00ED888F /* UIVIewExtensions.swift */,
C6C65E88272723DC00E48FC6 /* UIVIewControllerExtensions.swift */,
C61E409C275A94E300CCE602 /* UILabelExtensions.swift */,
C6D1E41A275937A0008EB388 /* UIButtonExtensions.swift */,
);
path = IOS;
sourceTree = "<group>";
@ -3449,6 +3507,14 @@
path = Extensions;
sourceTree = "<group>";
};
C690CCB22757674700609077 /* GenericViews */ = {
isa = PBXGroup;
children = (
C690CCB32757683800609077 /* NavigationView.swift */,
);
path = GenericViews;
sourceTree = "<group>";
};
C6D52B4727481D3F00904660 /* Conference */ = {
isa = PBXGroup;
children = (
@ -3461,6 +3527,43 @@
path = Conference;
sourceTree = "<group>";
};
C6EA2F492752237C008E60F8 /* Conference */ = {
isa = PBXGroup;
children = (
C6EA2F4C2754C691008E60F8 /* data */,
C6EA2F4B2754C683008E60F8 /* views */,
C6EA2F4D2754C69F008E60F8 /* viewmodels */,
);
path = Conference;
sourceTree = "<group>";
};
C6EA2F4B2754C683008E60F8 /* views */ = {
isa = PBXGroup;
children = (
C690CCB0275764CD00609077 /* ConferenceSchedulingView.swift */,
C61E409A275A20A300CCE602 /* ConferenceSchedulingSummaryView.swift */,
);
path = views;
sourceTree = "<group>";
};
C6EA2F4C2754C691008E60F8 /* data */ = {
isa = PBXGroup;
children = (
C6EA2F6A2754DDF9008E60F8 /* Duration.swift */,
C6EA2F622754DBBE008E60F8 /* TimeZoneData.swift */,
C6EA2F582754C91C008E60F8 /* ScheduledConferenceData.swift */,
);
path = data;
sourceTree = "<group>";
};
C6EA2F4D2754C69F008E60F8 /* viewmodels */ = {
isa = PBXGroup;
children = (
C6EA2F662754DC45008E60F8 /* ConferenceSchedulingViewModel.swift */,
);
path = viewmodels;
sourceTree = "<group>";
};
D326483415887D4400930C67 /* Utils */ = {
isa = PBXGroup;
children = (
@ -4079,6 +4182,7 @@
633FEDFD1D3CD5590014B822 /* camera_switch_default@2x.png in Resources */,
633FEEC51D3CD55A0014B822 /* numpad_5_over.png in Resources */,
633FEE721D3CD5590014B822 /* history_all_default.png in Resources */,
C6D1E42527595988008EB388 /* security_toggle_icon_green.png in Resources */,
C6B4444326AAD0980076C517 /* file_default.png in Resources */,
615A283C2180789C0060F920 /* security_toogle_button@2x.png in Resources */,
633FEF0A1D3CD55A0014B822 /* options_transfer_call_default.png in Resources */,
@ -4241,6 +4345,7 @@
633FEDF01D3CD5590014B822 /* call_transfer_disabled.png in Resources */,
633FEE351D3CD5590014B822 /* conference_exit_default@2x.png in Resources */,
C6710FCB2722B20000ED888F /* voip_remote_recording.png in Resources */,
C6D1E42427595988008EB388 /* security_toggle_icon_grey.png in Resources */,
C6710FAA2722B20000ED888F /* voip_conference_active_speaker.png in Resources */,
633FEECF1D3CD55A0014B822 /* numpad_6_over~ipad.png in Resources */,
61586B87217A17160038AC45 /* menu_assistant@2x.png in Resources */,
@ -4253,6 +4358,7 @@
C6710FD62722B20000ED888F /* voip_conference_play_big.png in Resources */,
633FEDCA1D3CD5590014B822 /* call_outgoing.png in Resources */,
633FEDF81D3CD5590014B822 /* camera_disabled.png in Resources */,
C6D1E41F2759396B008EB388 /* voip_checkbox_checked.png in Resources */,
C6710FBD2722B20000ED888F /* voip_call_header_paused.png in Resources */,
8CB2B8FB1F86229E0015CEE2 /* next_disabled@2x.png in Resources */,
633FEEE91D3CD55A0014B822 /* numpad_9~ipad.png in Resources */,
@ -4279,6 +4385,7 @@
633FEDE41D3CD5590014B822 /* call_status_incoming~ipad.png in Resources */,
633FEE4C1D3CD5590014B822 /* delete_field_default.png in Resources */,
633FEE391D3CD5590014B822 /* contact_add_default@2x.png in Resources */,
C6D1E41E2759396B008EB388 /* voip_checkbox_unchecked.png in Resources */,
633FEE741D3CD5590014B822 /* history_all_disabled.png in Resources */,
633FEE081D3CD5590014B822 /* chat_add_disabled.png in Resources */,
615A28422180C0870060F920 /* recording.png in Resources */,
@ -4661,6 +4768,8 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-linphone/Pods-linphone-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/DropDown/DropDown.framework",
"${BUILT_PRODUCTS_DIR}/IQKeyboardManager/IQKeyboardManager.framework",
"${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework",
"${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework",
"${PODS_ROOT}/../../../../../../../Volumes/dada/bc/linphone-sdk/ios/linphone-sdk/apple-darwin/Frameworks/bctoolbox-ios.framework",
@ -4683,6 +4792,8 @@
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DropDown.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManager.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/bctoolbox-ios.framework",
@ -4733,12 +4844,14 @@
C6710F5527229D5900ED888F /* LightDarkColor.swift in Sources */,
C6F2D4F9273A4CD70071BA52 /* SharedLayoutConstants.swift in Sources */,
C6D09F3F274273FB003C2173 /* CallStatsView.swift in Sources */,
C6EA2F632754DBBE008E60F8 /* TimeZoneData.swift in Sources */,
22F2508E107141E100AC9B3F /* DialerView.m in Sources */,
633756451B67D2B200E21BAD /* SideMenuView.m in Sources */,
C6F2D4F1273935860071BA52 /* CallExtensions.swift in Sources */,
C6710F5C2722AAED00ED888F /* UIDeviceExtensions.swift in Sources */,
C65A5D3027216B86005BA038 /* ActiveCallOrConferenceView.swift in Sources */,
8CD99A422090CE6F008A7CDA /* UIChatConversationImdnTableViewCell.m in Sources */,
C6D1E42127593A30008EB388 /* StyledCheckBox.swift in Sources */,
22E0A822111C44E100B04932 /* AboutView.m in Sources */,
633671611BCBAAD200BFCBDE /* ChatConversationCreateView.m in Sources */,
634610061B61330300548952 /* UILabel+Boldify.m in Sources */,
@ -4748,6 +4861,8 @@
C6B04D63274B95D500F70559 /* VoipParticipantCell.swift in Sources */,
2214EB7A12F846B1002A5394 /* UICallButton.m in Sources */,
C683B213272276CF00D4E15C /* UIColorExtensions.swift in Sources */,
C61E409F275A962100CCE602 /* FormButton.swift in Sources */,
C690CCB1275764CD00609077 /* ConferenceSchedulingView.swift in Sources */,
C6710F512722932600ED888F /* UIImageViewExtensions.swift in Sources */,
630CF5571AF7CE1500539F7A /* UITextField+DoneButton.m in Sources */,
2214EBF312F86360002A5394 /* UIMutedMicroButton.m in Sources */,
@ -4769,6 +4884,8 @@
6377AC801BDE4069007F7625 /* UIBackToCallButton.m in Sources */,
C6D09F43274288D4003C2173 /* PayloadType.swift in Sources */,
6308F9C51BF0DD6600D1234B /* XMLRPCHelper.m in Sources */,
C6EA2F5F2754CFB0008E60F8 /* TimestampUtils.swift in Sources */,
C6DB1DE82759136100A22704 /* StyledValuePicker.swift in Sources */,
C6F2D4F72739861F0071BA52 /* RemotelyRecording.swift in Sources */,
D3ED3E871586291E006C0DE4 /* TabBarView.m in Sources */,
617C242A263022690042FB4A /* UIChatContentView.m in Sources */,
@ -4784,6 +4901,7 @@
D35498211587716B000081D8 /* StatusBarView.m in Sources */,
D3A55FBC15877E5E003FD403 /* UIContactCell.m in Sources */,
6341807C1BBC103100F71761 /* ChatConversationCreateTableView.m in Sources */,
C6DB1DE42758E49E00A22704 /* StyledSwitch.swift in Sources */,
63BE7A781D75BDF6000990EF /* ShopTableView.m in Sources */,
D326483815887D5200930C67 /* OrderedDictionary.m in Sources */,
C6710F4F2722903200ED888F /* RotatingSpinner.swift in Sources */,
@ -4794,6 +4912,7 @@
C6C98CD527453ED900059B55 /* ConferenceViewModel.swift in Sources */,
D31C9C98158A1CDF00756B45 /* UIHistoryCell.m in Sources */,
D35E7597159460580066B1C1 /* ChatsListView.m in Sources */,
C6EA2F6D2754E3DC008E60F8 /* MediatorLiveData.swift in Sources */,
D35E759F159460B70066B1C1 /* SettingsView.m in Sources */,
63B81A101B57DA33009604A6 /* UIScrollView+TPKeyboardAvoidingAdditions.m in Sources */,
F03CA84318C72F1A0008889D /* UITextViewNoDefine.m in Sources */,
@ -4817,6 +4936,7 @@
8C9C5E0D1F83B2EF006987FA /* ChatConversationCreateCollectionViewController.m in Sources */,
631098491D4660580041F2B3 /* CountryListView.m in Sources */,
D32B9DFC15A2F131000B6DEC /* FastAddressBook.m in Sources */,
C6DB1DE22757E35F00A22704 /* StyledTextView.swift in Sources */,
D350F20E15A43BB100149E54 /* AssistantView.m in Sources */,
D3F795D615A582810077328B /* ChatConversationView.m in Sources */,
C60D265627299C94006238BB /* ControlsViewModel.swift in Sources */,
@ -4831,8 +4951,10 @@
C6B04D67274BD61300F70559 /* VoipConferenceActiveSpeakerView.swift in Sources */,
D37C639B15AADEF6009D0BAC /* ContactDetailsTableView.m in Sources */,
63E59A3F1ADE70D900646FB3 /* InAppProductsManager.m in Sources */,
C6DB1DE6275903C500A22704 /* StyledDatePicker.swift in Sources */,
C67C97B8274FD76B0074A0D8 /* AudioRouteUtils.swift in Sources */,
C6C98CE1274568F800059B55 /* VoipConferenceGridView.swift in Sources */,
C61E409B275A20A300CCE602 /* ConferenceSchedulingSummaryView.swift in Sources */,
D3C6526715AC1A8F0092A874 /* UIContactDetailsCell.m in Sources */,
631348301B6F7B6600C6BDCB /* UIRoundBorderedButton.m in Sources */,
C90FAA7915AF54E6002091CB /* HistoryDetailsView.m in Sources */,
@ -4845,6 +4967,7 @@
D35860D615B549B500513429 /* Utils.m in Sources */,
D3F7998115BD32370018C273 /* TPMultiLayoutViewController.m in Sources */,
D3807FBF15C28940005BE9BC /* DCRoundSwitch.m in Sources */,
C61E409D275A94E300CCE602 /* UILabelExtensions.swift in Sources */,
D3807FC115C28940005BE9BC /* DCRoundSwitchKnobLayer.m in Sources */,
C6B04D61274B954500F70559 /* ParticipantsListView.swift in Sources */,
61CCC3DF21933B580060EDEA /* UIDeviceCell.m in Sources */,
@ -4858,6 +4981,7 @@
D3807FE815C2894A005BE9BC /* IASKAppSettingsViewController.m in Sources */,
D3807FEC15C2894A005BE9BC /* IASKSpecifierValuesViewController.m in Sources */,
8CA70AE41F9E39E400A3D2EB /* UIChatConversationInfoTableViewCell.m in Sources */,
C690CCB42757683800609077 /* NavigationView.swift in Sources */,
D3807FEE15C2894A005BE9BC /* IASKSettingsReader.m in Sources */,
C6C65E8B2727274A00E48FC6 /* NumpadView.swift in Sources */,
C6F2D501273B0EFC0071BA52 /* VoipDialog.swift in Sources */,
@ -4875,8 +4999,11 @@
D3807FF615C2894A005BE9BC /* IASKSpecifier.m in Sources */,
C6710FE92723DD7D00ED888F /* CallControlButton.swift in Sources */,
D3807FF815C2894A005BE9BC /* IASKPSSliderSpecifierViewCell.m in Sources */,
C6EA2F592754C91C008E60F8 /* ScheduledConferenceData.swift in Sources */,
C6EA2F672754DC45008E60F8 /* ConferenceSchedulingViewModel.swift in Sources */,
D3807FFA15C2894A005BE9BC /* IASKPSTextFieldSpecifierViewCell.m in Sources */,
D3807FFC15C2894A005BE9BC /* IASKPSTitleValueSpecifierViewCell.m in Sources */,
C6D1E41B275937A0008EB388 /* UIButtonExtensions.swift in Sources */,
C6F2D4EF27392D970071BA52 /* CallsViewModel.swift in Sources */,
C6C65E89272723DC00E48FC6 /* UIVIewControllerExtensions.swift in Sources */,
D3807FFE15C2894A005BE9BC /* IASKSlider.m in Sources */,
@ -4889,6 +5016,7 @@
C6C98CDB274541E400059B55 /* ConferenceParticipantDeviceData.swift in Sources */,
637157A11B283FE200C91677 /* FileTransferDelegate.m in Sources */,
C6710FDE2722D44B00ED888F /* IncomingOuntgoingCommonView.swift in Sources */,
C6EA2F6B2754DDF9008E60F8 /* Duration.swift in Sources */,
D378AB2A15DCDB4A0098505D /* ImagePickerView.m in Sources */,
C6710F592722A9B800ED888F /* StyledLabel.swift in Sources */,
22405F001601C19200B92522 /* ImageView.m in Sources */,
@ -5476,7 +5604,7 @@
"-DENABLE_QRCODE=TRUE",
"-DENABLE_SMS_INVITE=TRUE",
"$(inherited)",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.20+3cffbc5\\\"",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"",
);
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
@ -5603,7 +5731,7 @@
"-DENABLE_QRCODE=TRUE",
"-DENABLE_SMS_INVITE=TRUE",
"$(inherited)",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.20+3cffbc5\\\"",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"",
);
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
@ -5729,7 +5857,7 @@
"-DENABLE_QRCODE=TRUE",
"-DENABLE_SMS_INVITE=TRUE",
"$(inherited)",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.20+3cffbc5\\\"",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"",
);
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
@ -5854,7 +5982,7 @@
"-DENABLE_QRCODE=TRUE",
"-DENABLE_SMS_INVITE=TRUE",
"$(inherited)",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.20+3cffbc5\\\"",
"-DLINPHONE_SDK_VERSION=\\\"5.2.0-alpha.36+bd3b432\\\"",
);
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;