Merge release/5.1 into master.

This includes the rework of the chat conversation in Swift, and every fixes since the 5.0 release.
This commit is contained in:
QuentinArguillere 2023-07-07 15:07:44 +02:00
parent 329193d2fa
commit 0d602260be
225 changed files with 12199 additions and 2946 deletions

View file

@ -1,55 +1,98 @@
variables: # COMMENTED FOR NOW - TODO : ENABLE CALLUI TESTS IN THE CI
workspace: linphone.xcworkspace # USE ONLY THE release/5.1 VERSION OF THIS FILE FOR NOW
scheme: linphone #
destination: name=iPhone 13 Pro #
testResult_path: derivedData/Logs/Test #
#variables:
# workspace: linphone.xcworkspace
# scheme: linphone
# destination: name=iPhone 13 Pro
# testResult_path: derivedData/Logs/Test
stages: #stages:
- Build # - Build
- UITests # - UITests
before_script: #before_script:
- pod install # - pod install
- pwd # - pwd
- sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift # - sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift
Compile & Build: #Compile & Build:
stage: Build # stage: Build
tags: ["macmini-m1-xcode13"] # tags: ["macmini-m1-xcode13"]
before_script: # before_script:
# - pod install --repo-update
# - pwd
# - sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift
# - xcrun simctl shutdown "$destination" && xcrun simctl erase "$destination"
# script:
# - xcodebuild -workspace $workspace -scheme $scheme -UseModernBuildSystem=YES -destination "$destination" -derivedDataPath derivedData
# after_script: []
#
# stage: build
# tags: [ "macos-xcode13" ]
# script:
# - pod install --repo-update
# - pwd
# - sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift
# - xcodebuild archive -scheme $archive_scheme -archivePath ./$archive_path -configuration Release -workspace ./linphone.xcworkspace -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
# - xcodebuild -exportArchive -archivePath ./$archive_path -exportPath ./$export_path -exportOptionsPlist ./$export_options_plist -allowProvisioningUpdates -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
# artifacts:
# paths:
# - derivedData/Build
# when: always
# expire_in: 2 hour
#after_script:
# - ${TRAINER_EXE} -p $testResult_path/*.xcresult -o $testResult_path/
# - ${XCPARSE_EXE} attachments $testResult_path/*.xcresult results --uti public.image
# - mv $testResult_path/*.xcresult results && mv derivedData/logs.txt results
#Call Views:
# stage: UITests
# tags: ["macmini-m1-xcode13"]
# dependencies: ["Compile & Build"]
# script:
# - xcodebuild test -workspace $workspace -scheme $scheme -sdk iphonesimulator -destination "$destination" -UseModernBuildSystem=YES -testPlan Default -derivedDataPath derivedData | tee derivedData/logs.txt
# artifacts:
# paths:
# - results/*
# when: always
# reports:
# junit:
# - $testResult_path/*.xml
# expire_in: 4 week
variables:
archive_scheme: linphone
archive_path: linphone.xcarchive
export_path: linphone-adhoc-ipa
export_options_plist: linphone-adhoc.plist
job-ios:
stage: build
tags: [ "macos-xcode13" ]
script:
- pod install --repo-update - pod install --repo-update
- pwd - pwd
- sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift - sed 's/fileprivate let tableView =/public let tableView =/g' ./Pods/DropDown/DropDown/src/DropDown.swift > tmp.swift && mv -f tmp.swift ./Pods/DropDown/DropDown/src/DropDown.swift
- xcrun simctl shutdown "$destination" && xcrun simctl erase "$destination" - xcodebuild archive -scheme $archive_scheme -archivePath ./$archive_path -configuration Release -workspace ./linphone.xcworkspace -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
script: - xcodebuild -exportArchive -archivePath ./$archive_path -exportPath ./$export_path -exportOptionsPlist ./$export_options_plist -allowProvisioningUpdates -UseModernBuildSystem=YES -destination 'generic/platform=iOS'
- xcodebuild -workspace $workspace -scheme $scheme -UseModernBuildSystem=YES -destination "$destination" -derivedDataPath derivedData
after_script: []
artifacts: artifacts:
paths: paths:
- derivedData/Build - $archive_path
- $export_path
when: always when: always
expire_in: 2 hour expire_in: 1 week
after_script:
- ${TRAINER_EXE} -p $testResult_path/*.xcresult -o $testResult_path/
- ${XCPARSE_EXE} attachments $testResult_path/*.xcresult results --uti public.image
- mv $testResult_path/*.xcresult results && mv derivedData/logs.txt results
Call Views:
stage: UITests
tags: ["macmini-m1-xcode13"]
dependencies: ["Compile & Build"]
script:
- xcodebuild test -workspace $workspace -scheme $scheme -sdk iphonesimulator -destination "$destination" -UseModernBuildSystem=YES -testPlan Default -derivedDataPath derivedData | tee derivedData/logs.txt
artifacts:
paths:
- results/*
when: always
reports:
junit:
- $testResult_path/*.xml
expire_in: 4 week

View file

@ -10,15 +10,61 @@ Group changes to describe their impact on the project, as follows:
Fixed for any bug fixes. Fixed for any bug fixes.
Security to invite users to upgrade in case of vulnerabilities. Security to invite users to upgrade in case of vulnerabilities.
## [5.0.2] - 2023-16-03
### Changed
- Update linphone SDK to 5.2.32
### Fixed
- Performance issue causing a global slowing of the app, especially at launch
- Fix several memory leaks and crashes
## [5.0.1] - 2023-10-01
### Changed
- Update linphone SDK to 5.2.11
### Fixed
- Makes sure sip.linphone.org accounts have a LIME X3DH server URL for E2E chat messages encryption
- Fix potential crash when displaying images received in a chatroom
- Fix bug that would cause the previous call to be terminated when resuming another call that was paused
- Fix participant video display in conferences when a second participant joined with video enabled
## [5.0.0] - 2022-12-06
### Added
- Post Quantum encryption when using ZRTP
- Conference creation with scheduling, video, different layouts, showing who is speaking and who is muted, etc...
- Group calls directly from group chat rooms
- Chat rooms can be individually muted (no notification when receiving a chat message)
- Outgoing call video in early-media if requested by callee
- Call recordings can be exported
- Setting to prevent international prefix from account to be applied to call & chat
- Add a "Never ask again" option to the "Link my account" pop-up when starting the app
### Changed
- In-call views have been re-designed
- Improved how contact avatars are generated
- 3-dots menu even for basic chat rooms with more options
- Update linphone SDK to 5.2.0
### Fixed
- Chatroom appearing as empty when being logged on multiple accounts
- Chatroom appearing as empty after playing a video file inside it
- Fix potential crash when entering a chatroom
- Fix potential crash when accessing to the delivery infos of a message in a group chat.
- IMDN logo not properly displayed when transfering or replying to a message with media (voice message, photo...)
- Clarified view when sending an image from the galery
- Various audio route fixes for CallKit and IOS 16
## [4.6.4] - 2021-08-06 ## [4.6.4] - 2022-08-06
### Changed ### Changed
- Update linphone SDK to 5.1.42 - Update linphone SDK to 5.1.42
### Fixed ### Fixed
- Prevent possible application freeze and crash when creating a new chatroom, depending on the phone's contacts. - Prevent possible application freeze and crash when creating a new chatroom, depending on the phone's contacts.
## [4.6.3] - 2021-02-06 ## [4.6.3] - 2022-02-06
### Added ### Added
- New "Contacts" menu in the settings, which allows the use of LDAP configurations - New "Contacts" menu in the settings, which allows the use of LDAP configurations
@ -33,7 +79,7 @@ Group changes to describe their impact on the project, as follows:
- Display bug when changing audio device - Display bug when changing audio device
## [4.6.2] - 2021-07-03 ## [4.6.2] - 2022-07-03
### Fixed ### Fixed
- Bug preventing the activation of the phone speaker during calls - Bug preventing the activation of the phone speaker during calls
@ -41,7 +87,7 @@ Group changes to describe their impact on the project, as follows:
- Bug causing IMDNs to be missing in some chatrooms - Bug causing IMDNs to be missing in some chatrooms
- Update linphone SDK to 5.1.7 - Update linphone SDK to 5.1.7
## [4.6.1] - 2021-04-03 ## [4.6.1] - 2022-04-03
### Fixed ### Fixed
- Crash in chatroom info view after entering background and re-entering foreground - Crash in chatroom info view after entering background and re-entering foreground
@ -49,7 +95,7 @@ Group changes to describe their impact on the project, as follows:
- Hard to see text (written in black) on dark mode - Hard to see text (written in black) on dark mode
- Removed duplicate push authorization request pop up on install - Removed duplicate push authorization request pop up on install
## [4.6.0] - 2021-31-02 ## [4.6.0] - 2022-31-02
### Added ### Added
- Reply to chat message feature (with original message preview) - Reply to chat message feature (with original message preview)

View file

@ -127,7 +127,9 @@ static UICompositeViewDescription *compositeDescription = nil;
if (!mustRestoreView) { if (!mustRestoreView) {
new_account = NULL; new_account = NULL;
number_of_accounts_before = bctbx_list_size(linphone_core_get_account_list(LC)); MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
number_of_accounts_before = bctbx_list_size(accounts);
bctbx_free(accounts);
[self resetTextFields]; [self resetTextFields];
[self changeView:_welcomeView back:FALSE animation:FALSE]; [self changeView:_welcomeView back:FALSE animation:FALSE];
} }
@ -519,6 +521,10 @@ static UICompositeViewDescription *compositeDescription = nil;
#endif #endif
linphone_push_notification_config_set_provider(pushConfig, PROVIDER_NAME); linphone_push_notification_config_set_provider(pushConfig, PROVIDER_NAME);
if (strcmp(creatorDomain, "sip.linphone.org")==0) {
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP);
}
new_account = linphone_core_create_account(LC, accountParams); new_account = linphone_core_create_account(LC, accountParams);
linphone_account_params_unref(accountParams); linphone_account_params_unref(accountParams);
@ -705,7 +711,7 @@ static UICompositeViewDescription *compositeDescription = nil;
LinphoneAccount *default_account = linphone_core_create_account(LC, default_account_params); LinphoneAccount *default_account = linphone_core_create_account(LC, default_account_params);
const char *identity = linphone_account_params_get_identity(linphone_account_get_params(default_account)); const char *identity = linphone_account_params_get_identity(linphone_account_get_params(default_account));
if (identity) { if (identity) {
LinphoneAddress *default_addr = linphone_core_interpret_url(LC, identity); LinphoneAddress *default_addr = linphone_core_interpret_url_2(LC, identity, false);
if (default_addr) { if (default_addr) {
const char *domain = linphone_address_get_domain(default_addr); const char *domain = linphone_address_get_domain(default_addr);
const char *username = linphone_address_get_username(default_addr); const char *username = linphone_address_get_username(default_addr);
@ -1011,10 +1017,13 @@ static UICompositeViewDescription *compositeDescription = nil;
[LinphoneManager.instance lpConfigSetInt:[NSDate new].timeIntervalSince1970 [LinphoneManager.instance lpConfigSetInt:[NSDate new].timeIntervalSince1970
forKey:@"must_link_account_time"]; forKey:@"must_link_account_time"];
[LinphoneManager.instance configurePushProviderForAccounts]; [LinphoneManager.instance configurePushProviderForAccounts];
if (number_of_accounts_before < bctbx_list_size(linphone_core_get_account_list(LC))) {
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
if (number_of_accounts_before < bctbx_list_size(accounts)) {
LOGI(@"A proxy config was set up with the remote provisioning, skip assistant"); LOGI(@"A proxy config was set up with the remote provisioning, skip assistant");
[self onDialerClick:nil]; [self onDialerClick:nil];
} }
bctbx_free(accounts);
_waitView.hidden = true; _waitView.hidden = true;
if (nextView == nil) { if (nextView == nil) {
@ -1149,7 +1158,11 @@ static UICompositeViewDescription *compositeDescription = nil;
_outgoingView = DialerView.compositeViewDescription; _outgoingView = DialerView.compositeViewDescription;
[self configureAccount]; [self configureAccount];
} else if (status == LinphoneAccountCreatorStatusAccountExist) { } else if (status == LinphoneAccountCreatorStatusAccountExist) {
_outgoingView = AssistantLinkView.compositeViewDescription; if([LinphoneManager.instance lpConfigIntForKey:@"hide_link_phone_number"]){
_outgoingView = DialerView.compositeViewDescription;
}else{
_outgoingView = AssistantLinkView.compositeViewDescription;
}
[self configureAccount]; [self configureAccount];
} else { } else {
if (resp) { if (resp) {
@ -1568,7 +1581,6 @@ UIColor *previousColor = (UIColor*)[sender backgroundColor]; \
- (IBAction)onRemoteProvisioningLoginClick:(id)sender { - (IBAction)onRemoteProvisioningLoginClick:(id)sender {
ONCLICKBUTTON(sender, 100, { ONCLICKBUTTON(sender, 100, {
_waitView.hidden = NO; _waitView.hidden = NO;
[LinphoneManager.instance lpConfigSetInt:1 forKey:@"transient_provisioning" inSection:@"misc"];
[self configureAccount]; [self configureAccount];
}); });
} }
@ -1584,10 +1596,11 @@ UIColor *previousColor = (UIColor*)[sender backgroundColor]; \
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}]; handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction]; [errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil]; [self presentViewController:errView animated:YES completion:nil];
_waitView.hidden = TRUE;
} else { } else {
linphone_core_set_provisioning_uri(LC, [self addSchemeToProvisiionninUriIMissing:[self findTextField:ViewElement_URL].text].UTF8String); linphone_core_set_provisioning_uri(LC, [self addSchemeToProvisiionninUriIMissing:[self findTextField:ViewElement_URL].text].UTF8String);
[self resetLiblinphone:TRUE]; [self resetLiblinphone:TRUE];

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -175,13 +175,13 @@
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KRQ-Fm-3cQ" userLabel="addedContacts" customClass="UICollectionView"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KRQ-Fm-3cQ" userLabel="addedContacts" customClass="UICollectionView">
<rect key="frame" x="8" y="110" width="398" height="70"/> <rect key="frame" x="8" y="110" width="398" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<accessibility key="accessibilityConfiguration" label="addedContacts"/> <accessibility key="accessibilityConfiguration" label="addedContacts"/>
</view> </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"> <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="403" height="610"/> <rect key="frame" x="5" y="158" width="403" height="610"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="separatorColor" red="0.67030966281890869" green="0.71867996454238892" blue="0.75078284740447998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="separatorColor" red="0.67030966281890869" green="0.71867996454238892" blue="0.75078284740447998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -97,7 +97,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XSI-9T-NtW" userLabel="addButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XSI-9T-NtW" userLabel="addButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="382" y="4" width="24" height="21"/> <rect key="frame" x="382" y="2" width="24" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/> <accessibility key="accessibilityConfiguration" label="Back"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>

View file

@ -1,14 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ChatConversationView"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ChatConversationViewSwift">
<connections> <connections>
<outlet property="addressLabel" destination="40" id="43"/> <outlet property="addressLabel" destination="40" id="43"/>
<outlet property="backButton" destination="9" id="Jcb-ET-bKd"/> <outlet property="backButton" destination="9" id="Jcb-ET-bKd"/>
@ -50,19 +49,19 @@
</placeholder> </placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="v2I-ka-LYa" userLabel="iphone6MetricsView"> <view contentMode="scaleToFill" id="v2I-ka-LYa" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6"> <view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6">
<rect key="frame" x="0.0" y="42" width="414" height="788"/> <rect key="frame" x="-1" y="41" width="393" height="744"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view alpha="0.90000000000000002" tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7" userLabel="topBar"> <view alpha="0.90000000000000002" tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="414" height="66"/> <rect key="frame" x="0.0" y="0.0" width="393" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" userLabel="backButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" userLabel="backButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="0.0" y="0.0" width="82" height="66"/> <rect key="frame" x="0.0" y="0.0" width="77" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/> <accessibility key="accessibilityConfiguration" label="Back"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -77,7 +76,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bci-3K-AcG" userLabel="cancelButton" customClass="UIInterfaceStyleButton"> <button hidden="YES" opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bci-3K-AcG" userLabel="cancelButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="0.0" y="0.0" width="82" height="66"/> <rect key="frame" x="0.0" y="0.0" width="77" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Cancel"/> <accessibility key="accessibilityConfiguration" label="Cancel"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -92,7 +91,7 @@
</connections> </connections>
</button> </button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="Contact1" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="40" userLabel="addressLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="Contact1" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="40" userLabel="addressLabel">
<rect key="frame" x="75" y="0.0" width="160" height="44"/> <rect key="frame" x="75" y="0.0" width="149" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"> <accessibility key="accessibilityConfiguration" label="Contact name">
<accessibilityTraits key="traits" none="YES"/> <accessibilityTraits key="traits" none="YES"/>
@ -102,7 +101,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Wzg-i0-spp" userLabel="callButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Wzg-i0-spp" userLabel="callButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="248" y="0.0" width="83" height="66"/> <rect key="frame" x="234" y="0.0" width="79" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<state key="normal" image="call_alt_start_default.png"> <state key="normal" image="call_alt_start_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -114,7 +113,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Hc0-GX-fC5" userLabel="backToCallButton" customClass="UIBackToCallButton"> <button hidden="YES" opaque="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Hc0-GX-fC5" userLabel="backToCallButton" customClass="UIBackToCallButton">
<rect key="frame" x="248" y="0.0" width="83" height="66"/> <rect key="frame" x="234" y="0.0" width="79" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<state key="normal" image="call_back_default.png"> <state key="normal" image="call_back_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -127,7 +126,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="8" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Wag-Nx-kd6" userLabel="deleteButton" customClass="UIInterfaceStyleButton"> <button hidden="YES" opaque="NO" tag="8" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Wag-Nx-kd6" userLabel="deleteButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="331" y="0.0" width="83" height="66"/> <rect key="frame" x="313" y="0.0" width="80" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Delete all"/> <accessibility key="accessibilityConfiguration" label="Delete all"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -141,7 +140,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FqM-Ud-i58" userLabel="editButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FqM-Ud-i58" userLabel="editButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="331" y="0.0" width="83" height="66"/> <rect key="frame" x="313" y="0.0" width="80" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Edit"/> <accessibility key="accessibilityConfiguration" label="Edit"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -156,7 +155,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="4697" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CPn-Oc-9PX" userLabel="toggleMenuButton" customClass="UIInterfaceStyleButton"> <button hidden="YES" opaque="NO" tag="4697" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="CPn-Oc-9PX" userLabel="toggleMenuButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="331" y="0.0" width="83" height="66"/> <rect key="frame" x="313" y="0.0" width="80" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Edit"/> <accessibility key="accessibilityConfiguration" label="Edit"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -169,7 +168,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c9z-aq-2UP" userLabel="toggleSelectionButton" customClass="UIInterfaceStyleButton"> <button hidden="YES" opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="c9z-aq-2UP" userLabel="toggleSelectionButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="248" y="0.0" width="83" height="66"/> <rect key="frame" x="234" y="0.0" width="79" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Select all"/> <accessibility key="accessibilityConfiguration" label="Select all"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -185,7 +184,7 @@
</connections> </connections>
</button> </button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="addresses" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ncq-Zc-X6j" userLabel="participantsLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="addresses" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ncq-Zc-X6j" userLabel="participantsLabel">
<rect key="frame" x="75" y="36" width="160" height="25"/> <rect key="frame" x="75" y="35" width="149" height="25"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"> <accessibility key="accessibilityConfiguration" label="Contact name">
<accessibilityTraits key="traits" none="YES"/> <accessibilityTraits key="traits" none="YES"/>
@ -198,11 +197,11 @@
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<view tag="12" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" userLabel="contentView"> <view tag="12" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="49" userLabel="contentView">
<rect key="frame" x="0.0" y="66" width="414" height="722"/> <rect key="frame" x="0.0" y="65" width="393" height="679"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<tableView clipsSubviews="YES" tag="13" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="60" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="messagesTableView"> <tableView clipsSubviews="YES" tag="13" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="60" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="messagesTableView">
<rect key="frame" x="0.0" y="0.0" width="414" height="574"/> <rect key="frame" x="0.0" y="0.0" width="393" height="529"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<gestureRecognizers/> <gestureRecognizers/>
@ -214,11 +213,11 @@
</connections> </connections>
</tableView> </tableView>
<view hidden="YES" tag="14" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fx4-ao-53M" userLabel="composeIndicatorView"> <view hidden="YES" tag="14" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fx4-ao-53M" userLabel="composeIndicatorView">
<rect key="frame" x="0.0" y="574" width="414" height="22"/> <rect key="frame" x="0.0" y="529" width="393" height="23"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="15" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="%@ is composing..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="fpY-Fv-ht2" userLabel="composeLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="15" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="%@ is composing..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="fpY-Fv-ht2" userLabel="composeLabel">
<rect key="frame" x="0.0" y="1" width="414" height="22"/> <rect key="frame" x="10" y="-5" width="385" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label=""/> <accessibility key="accessibilityConfiguration" label=""/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -229,7 +228,7 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="16" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No conversation." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p7C-WH-uR1" userLabel="emptyTableLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="16" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No conversation." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p7C-WH-uR1" userLabel="emptyTableLabel">
<rect key="frame" x="0.0" y="0.0" width="414" height="574"/> <rect key="frame" x="0.0" y="0.0" width="393" height="539"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -237,11 +236,11 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view alpha="0.90000000000000002" tag="17" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="14" userLabel="messageView"> <view alpha="0.90000000000000002" tag="17" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="14" userLabel="messageView">
<rect key="frame" x="0.0" y="656" width="414" height="66"/> <rect key="frame" x="0.0" y="612" width="393" height="67"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<button opaque="NO" tag="19" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="73" userLabel="pictureButton"> <button opaque="NO" tag="19" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="73" userLabel="pictureButton">
<rect key="frame" x="0.0" y="0.0" width="66" height="66"/> <rect key="frame" x="0.0" y="0.0" width="66" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Send picture"/> <accessibility key="accessibilityConfiguration" label="Send picture"/>
<state key="normal" image="chat_attachment_default.png"> <state key="normal" image="chat_attachment_default.png">
@ -254,7 +253,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="9019" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aTi-pm-fAG" userLabel="audioRecordingButton"> <button opaque="NO" tag="9019" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aTi-pm-fAG" userLabel="audioRecordingButton">
<rect key="frame" x="66" y="0.0" width="56" height="66"/> <rect key="frame" x="66" y="0.0" width="56" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Send picture"/> <accessibility key="accessibilityConfiguration" label="Send picture"/>
<inset key="imageEdgeInsets" minX="15" minY="20" maxX="15" maxY="20"/> <inset key="imageEdgeInsets" minX="15" minY="20" maxX="15" maxY="20"/>
@ -267,7 +266,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="21" contentMode="scaleToFill" fixedFrame="YES" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="15" userLabel="sendButton"> <button opaque="NO" tag="21" contentMode="scaleToFill" fixedFrame="YES" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="15" userLabel="sendButton">
<rect key="frame" x="349" y="0.0" width="66" height="66"/> <rect key="frame" x="328" y="0.0" width="65" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Send"/> <accessibility key="accessibilityConfiguration" label="Send"/>
<inset key="titleEdgeInsets" minX="0.0" minY="30" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="30" maxX="0.0" maxY="0.0"/>
@ -281,24 +280,24 @@
</connections> </connections>
</button> </button>
<view tag="20" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pqa-tg-5ml" userLabel="messageField" customClass="HPGrowingTextView"> <view tag="20" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pqa-tg-5ml" userLabel="messageField" customClass="HPGrowingTextView">
<rect key="frame" x="130" y="13" width="208" height="40"/> <rect key="frame" x="123" y="11" width="197" height="43"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<accessibility key="accessibilityConfiguration" label="Message field"/> <accessibility key="accessibilityConfiguration" label="Message field"/>
</view> </view>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" tag="44536" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="LN7-ci-kNn" userLabel="ephemeralIndicator"> <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" tag="44536" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="LN7-ci-kNn" userLabel="ephemeralIndicator">
<rect key="frame" x="393" y="44" width="15" height="15"/> <rect key="frame" x="372" y="44" width="14" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView> </imageView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<view hidden="YES" tag="28021" contentMode="scaleToFill" id="Tru-Zm-4EZ" userLabel="VoiceRecording"> <view hidden="YES" tag="28021" contentMode="scaleToFill" id="Tru-Zm-4EZ" userLabel="VoiceRecording">
<rect key="frame" x="0.0" y="596" width="414" height="60"/> <rect key="frame" x="0.0" y="596" width="393" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<subviews> <subviews>
<button opaque="NO" tag="28022" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wi9-en-JCZ"> <button opaque="NO" tag="28022" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wi9-en-JCZ">
<rect key="frame" x="12" y="13" width="24" height="34"/> <rect key="frame" x="12" y="12" width="24" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="delete_default.png"/> <state key="normal" image="delete_default.png"/>
<connections> <connections>
@ -306,26 +305,26 @@
</connections> </connections>
</button> </button>
<view tag="28023" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eXD-Gd-FXA"> <view tag="28023" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="eXD-Gd-FXA">
<rect key="frame" x="50" y="8" width="302" height="44"/> <rect key="frame" x="50" y="7" width="280" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view tag="28024" contentMode="scaleToFill" id="OTf-Od-TDn" userLabel="vr_wave_mask_playback"> <view tag="28024" contentMode="scaleToFill" id="OTf-Od-TDn" userLabel="vr_wave_mask_playback">
<rect key="frame" x="0.0" y="0.0" width="240" height="44"/> <rect key="frame" x="0.0" y="0.0" width="217" height="43"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="tintColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view> </view>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="28025" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="vr_wave.png" translatesAutoresizingMaskIntoConstraints="NO" id="m9m-2e-T7E"> <imageView clipsSubviews="YES" userInteractionEnabled="NO" tag="28025" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="vr_wave.png" translatesAutoresizingMaskIntoConstraints="NO" id="m9m-2e-T7E">
<rect key="frame" x="8" y="8" width="232" height="28"/> <rect key="frame" x="7" y="8" width="210" height="27"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<view tag="28026" contentMode="scaleToFill" id="TzM-ND-yp4" userLabel="vr_wave_mask_record"> <view tag="28026" contentMode="scaleToFill" id="TzM-ND-yp4" userLabel="vr_wave_mask_record">
<rect key="frame" x="0.0" y="0.0" width="240" height="44"/> <rect key="frame" x="0.0" y="0.0" width="217" height="43"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view> </view>
<label opaque="NO" userInteractionEnabled="NO" tag="28027" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dMW-Ix-4k0"> <label opaque="NO" userInteractionEnabled="NO" tag="28027" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="dMW-Ix-4k0">
<rect key="frame" x="245" y="12" width="49" height="21"/> <rect key="frame" x="222" y="12" width="48" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/> <fontDescription key="fontDescription" type="system" pointSize="16"/>
<nil key="textColor"/> <nil key="textColor"/>
@ -335,7 +334,7 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
<button opaque="NO" tag="28028" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FNM-bb-AlC"> <button opaque="NO" tag="28028" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FNM-bb-AlC">
<rect key="frame" x="366" y="13" width="35" height="35"/> <rect key="frame" x="343" y="12" width="37" height="35"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="vr_play.png"/> <state key="normal" image="vr_play.png"/>
<connections> <connections>
@ -346,11 +345,11 @@
<color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="calibratedRGB"/> <color key="backgroundColor" red="0.93333333330000001" green="0.93333333330000001" blue="0.93333333330000001" alpha="1" colorSpace="calibratedRGB"/>
</view> </view>
<view clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3qd-ys-t2L" userLabel="imagesView"> <view clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3qd-ys-t2L" userLabel="imagesView">
<rect key="frame" x="0.0" y="625" width="414" height="0.0"/> <rect key="frame" x="0.0" y="587" width="393" height="0.0"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" showsVerticalScrollIndicator="NO" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="JGQ-p2-HCX" userLabel="imagesCollectionView"> <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" showsVerticalScrollIndicator="NO" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="JGQ-p2-HCX" userLabel="imagesCollectionView">
<rect key="frame" x="2" y="0.0" width="413" height="0.0"/> <rect key="frame" x="2" y="0.0" width="391" height="0.0"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.98780487804878048" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.98780487804878048" green="1" blue="1" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="c7z-F2-r1y"> <collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="c7z-F2-r1y">
@ -364,7 +363,7 @@
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tjL-Vc-5gN" userLabel="encryptedButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tjL-Vc-5gN" userLabel="encryptedButton">
<rect key="frame" x="359" y="10" width="34" height="40"/> <rect key="frame" x="372" y="8" width="34" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<state key="normal" image="security_1_indicator.png"/> <state key="normal" image="security_1_indicator.png"/>
<connections> <connections>
@ -372,7 +371,7 @@
</connections> </connections>
</button> </button>
<view hidden="YES" tag="290392" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wve-MK-7ME" userLabel="ReplyView"> <view hidden="YES" tag="290392" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wve-MK-7ME" userLabel="ReplyView">
<rect key="frame" x="0.0" y="536" width="414" height="120"/> <rect key="frame" x="0.0" y="536" width="393" height="120"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</view> </view>
@ -380,7 +379,7 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
<tableView hidden="YES" clipsSubviews="YES" tag="6992" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="gdT-1Q-vU2" userLabel="popupMenu"> <tableView hidden="YES" clipsSubviews="YES" tag="6992" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="gdT-1Q-vU2" userLabel="popupMenu">
<rect key="frame" x="142" y="66" width="273" height="132"/> <rect key="frame" x="121" y="66" width="272" height="132"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</tableView> </tableView>
@ -393,23 +392,23 @@
<point key="canvasLocation" x="365.21739130434787" y="-36.830357142857139"/> <point key="canvasLocation" x="365.21739130434787" y="-36.830357142857139"/>
</view> </view>
<view contentMode="scaleToFill" id="680-UL-sil" userLabel="iphone6MetricsView"> <view contentMode="scaleToFill" id="680-UL-sil" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/> <rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VoU-7Q-fgp"> <view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VoU-7Q-fgp">
<rect key="frame" x="90" y="42" width="324" height="854"/> <rect key="frame" x="90" y="42" width="303" height="810"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Z3y-hY-5xp" userLabel="topBar"> <view tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Z3y-hY-5xp" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="324" height="66"/> <rect key="frame" x="0.0" y="0.0" width="303" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="Uvs-m3-GPj" userLabel="backgroundColor"> <imageView userInteractionEnabled="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="Uvs-m3-GPj" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="324" height="66"/> <rect key="frame" x="0.0" y="0.0" width="303" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N2g-vL-3x8" userLabel="backButton" customClass="UIIconButton"> <button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N2g-vL-3x8" userLabel="backButton" customClass="UIIconButton">
<rect key="frame" x="0.0" y="0.0" width="37" height="66"/> <rect key="frame" x="0.0" y="0.0" width="34" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/> <accessibility key="accessibilityConfiguration" label="Back"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -424,7 +423,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Bqf-Gg-2Rw" userLabel="cancelButton" customClass="UIIconButton"> <button hidden="YES" opaque="NO" tag="11" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Bqf-Gg-2Rw" userLabel="cancelButton" customClass="UIIconButton">
<rect key="frame" x="0.0" y="0.0" width="37" height="66"/> <rect key="frame" x="0.0" y="0.0" width="34" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Cancel"/> <accessibility key="accessibilityConfiguration" label="Cancel"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -449,7 +448,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button hidden="YES" opaque="NO" tag="8" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RDW-7W-25T" userLabel="deleteButton" customClass="UIIconButton"> <button hidden="YES" opaque="NO" tag="8" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="RDW-7W-25T" userLabel="deleteButton" customClass="UIIconButton">
<rect key="frame" x="285" y="0.0" width="39" height="66"/> <rect key="frame" x="264" y="0.0" width="39" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Delete all"/> <accessibility key="accessibilityConfiguration" label="Delete all"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -463,7 +462,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KeL-Ej-92j" userLabel="editButton" customClass="UIIconButton"> <button opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KeL-Ej-92j" userLabel="editButton" customClass="UIIconButton">
<rect key="frame" x="285" y="0.0" width="39" height="66"/> <rect key="frame" x="264" y="0.0" width="39" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Edit"/> <accessibility key="accessibilityConfiguration" label="Edit"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -478,7 +477,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="4697" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pQX-Ll-9wL" userLabel="toggleMenuButton" customClass="UIInterfaceStyleButton"> <button hidden="YES" opaque="NO" tag="4697" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pQX-Ll-9wL" userLabel="toggleMenuButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="285" y="0.0" width="39" height="66"/> <rect key="frame" x="264" y="0.0" width="39" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Edit"/> <accessibility key="accessibilityConfiguration" label="Edit"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -491,7 +490,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wag-QV-oUD" userLabel="callButton" customClass="UIIconButton"> <button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wag-QV-oUD" userLabel="callButton" customClass="UIIconButton">
<rect key="frame" x="248" y="0.0" width="37" height="66"/> <rect key="frame" x="230" y="0.0" width="34" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<state key="normal" image="call_alt_start_default.png"> <state key="normal" image="call_alt_start_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -503,7 +502,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="t25-en-4LP" userLabel="backToCallButton" customClass="UIBackToCallButton"> <button hidden="YES" opaque="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="t25-en-4LP" userLabel="backToCallButton" customClass="UIBackToCallButton">
<rect key="frame" x="248" y="0.0" width="37" height="66"/> <rect key="frame" x="230" y="0.0" width="34" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<state key="normal" image="call_back_default.png"> <state key="normal" image="call_back_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -515,7 +514,7 @@
</connections> </connections>
</button> </button>
<button hidden="YES" opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4RV-US-Kr1" userLabel="toggleSelectionButton" customClass="UIIconButton"> <button hidden="YES" opaque="NO" tag="10" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" reversesTitleShadowWhenHighlighted="YES" showsTouchWhenHighlighted="YES" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4RV-US-Kr1" userLabel="toggleSelectionButton" customClass="UIIconButton">
<rect key="frame" x="248" y="0.0" width="37" height="66"/> <rect key="frame" x="230" y="0.0" width="34" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Select all"/> <accessibility key="accessibilityConfiguration" label="Select all"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -531,7 +530,7 @@
</connections> </connections>
</button> </button>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="addresses" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="tyU-Wy-rLs" userLabel="participantsLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="5" contentMode="left" fixedFrame="YES" text="addresses" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="tyU-Wy-rLs" userLabel="participantsLabel">
<rect key="frame" x="67" y="37" width="366" height="25"/> <rect key="frame" x="67" y="36" width="366" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"> <accessibility key="accessibilityConfiguration" label="Contact name">
<accessibilityTraits key="traits" none="YES"/> <accessibilityTraits key="traits" none="YES"/>
@ -543,11 +542,11 @@
</subviews> </subviews>
</view> </view>
<view tag="12" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OTt-fc-941" userLabel="contentView"> <view tag="12" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OTt-fc-941" userLabel="contentView">
<rect key="frame" x="0.0" y="66" width="324" height="788"/> <rect key="frame" x="0.0" y="66" width="303" height="744"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<tableView clipsSubviews="YES" tag="13" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="60" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="CU7-Za-RwN" userLabel="messagesTableView"> <tableView clipsSubviews="YES" tag="13" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" allowsSelectionDuringEditing="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="60" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="CU7-Za-RwN" userLabel="messagesTableView">
<rect key="frame" x="0.0" y="0.0" width="324" height="700"/> <rect key="frame" x="0.0" y="0.0" width="303" height="657"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/> <gestureRecognizers/>
@ -557,7 +556,7 @@
</connections> </connections>
</tableView> </tableView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="16" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No conversation." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pzm-tk-LH0" userLabel="emptyTableLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="16" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No conversation." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pzm-tk-LH0" userLabel="emptyTableLabel">
<rect key="frame" x="0.0" y="0.0" width="324" height="617"/> <rect key="frame" x="0.0" y="0.0" width="303" height="582"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -565,11 +564,11 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view hidden="YES" tag="14" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nTf-7h-Z4z" userLabel="composeIndicatorView"> <view hidden="YES" tag="14" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nTf-7h-Z4z" userLabel="composeIndicatorView">
<rect key="frame" x="0.0" y="700" width="324" height="22"/> <rect key="frame" x="0.0" y="657" width="303" height="21"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="15" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="%@ is composing..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="I34-aL-yuS" userLabel="composeLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="15" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="%@ is composing..." lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="I34-aL-yuS" userLabel="composeLabel">
<rect key="frame" x="0.0" y="0.0" width="324" height="22"/> <rect key="frame" x="0.0" y="0.0" width="303" height="22"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label=""/> <accessibility key="accessibilityConfiguration" label=""/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -579,11 +578,11 @@
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
<view tag="17" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LA5-wD-ftj" userLabel="messageView"> <view tag="17" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="LA5-wD-ftj" userLabel="messageView">
<rect key="frame" x="0.0" y="722" width="324" height="66"/> <rect key="frame" x="0.0" y="678" width="303" height="67"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" tag="18" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="kKc-DG-gwg" userLabel="backgroundColor"> <imageView userInteractionEnabled="NO" tag="18" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_F.png" translatesAutoresizingMaskIntoConstraints="NO" id="kKc-DG-gwg" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="324" height="66"/> <rect key="frame" x="0.0" y="0.0" width="303" height="67"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView> </imageView>
<button opaque="NO" tag="19" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gSL-jE-GYO" userLabel="pictureButton"> <button opaque="NO" tag="19" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gSL-jE-GYO" userLabel="pictureButton">
@ -612,13 +611,13 @@
</connections> </connections>
</button> </button>
<view tag="20" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C02-2r-vXK" userLabel="messageField" customClass="HPGrowingTextView"> <view tag="20" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C02-2r-vXK" userLabel="messageField" customClass="HPGrowingTextView">
<rect key="frame" x="59" y="13" width="230" height="40"/> <rect key="frame" x="53" y="12" width="215" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" label="Message field"/> <accessibility key="accessibilityConfiguration" label="Message field"/>
</view> </view>
<button opaque="NO" tag="21" contentMode="scaleToFill" fixedFrame="YES" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nV9-xZ-oSM" userLabel="sendButton"> <button opaque="NO" tag="21" contentMode="scaleToFill" fixedFrame="YES" enabled="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="nV9-xZ-oSM" userLabel="sendButton">
<rect key="frame" x="258" y="0.0" width="66" height="66"/> <rect key="frame" x="236" y="0.0" width="67" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Send"/> <accessibility key="accessibilityConfiguration" label="Send"/>
<inset key="titleEdgeInsets" minX="0.0" minY="30" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="30" maxX="0.0" maxY="0.0"/>
@ -632,7 +631,7 @@
</connections> </connections>
</button> </button>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" tag="44536" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="Gsu-3J-HRp" userLabel="ephemeralIndicator"> <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" tag="44536" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="Gsu-3J-HRp" userLabel="ephemeralIndicator">
<rect key="frame" x="305" y="40" width="15" height="15"/> <rect key="frame" x="284" y="41" width="13" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView> </imageView>
</subviews> </subviews>
@ -640,7 +639,7 @@
</subviews> </subviews>
</view> </view>
<tableView hidden="YES" clipsSubviews="YES" tag="6992" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="iwm-pi-mku" userLabel="popupMenu"> <tableView hidden="YES" clipsSubviews="YES" tag="6992" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="iwm-pi-mku" userLabel="popupMenu">
<rect key="frame" x="13" y="66" width="311" height="132"/> <rect key="frame" x="-10" y="66" width="313" height="132"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</tableView> </tableView>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -212,6 +212,7 @@
<outlet property="toggleSelectionButton" destination="uqG-2T-VOa" id="ytx-bj-7Qr"/> <outlet property="toggleSelectionButton" destination="uqG-2T-VOa" id="ytx-bj-7Qr"/>
<outlet property="view" destination="6" id="13"/> <outlet property="view" destination="6" id="13"/>
</connections> </connections>
<point key="canvasLocation" x="460" y="-11"/>
</tableViewController> </tableViewController>
</objects> </objects>
<resources> <resources>

View file

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -16,6 +19,7 @@
<outlet property="emptyLabel" destination="Mdj-Pz-nu4" id="ijc-2c-waE"/> <outlet property="emptyLabel" destination="Mdj-Pz-nu4" id="ijc-2c-waE"/>
<outlet property="landscapeView" destination="lgD-Mw-h57" id="DTS-80-rMM"/> <outlet property="landscapeView" destination="lgD-Mw-h57" id="DTS-80-rMM"/>
<outlet property="nameLabel" destination="moZ-Bg-zcv" id="Lt9-h0-2o1"/> <outlet property="nameLabel" destination="moZ-Bg-zcv" id="Lt9-h0-2o1"/>
<outlet property="organizationLabel" destination="pAA-jk-E4s" id="J0M-Ms-5dp"/>
<outlet property="portraitView" destination="1" id="k69-5P-ieM"/> <outlet property="portraitView" destination="1" id="k69-5P-ieM"/>
<outlet property="tableController" destination="20" id="27"/> <outlet property="tableController" destination="20" id="27"/>
<outlet property="view" destination="1" id="3"/> <outlet property="view" destination="1" id="3"/>
@ -24,19 +28,19 @@
</placeholder> </placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="XnN-PU-Vk7" userLabel="iphone6MetricsView"> <view contentMode="scaleToFill" id="XnN-PU-Vk7" 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="393" height="852"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="42" width="375" height="559"/> <rect key="frame" x="0.0" y="42" width="393" height="744"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view alpha="0.90000000000000002" tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4" userLabel="topBar"> <view alpha="0.90000000000000002" tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4" 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="393" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button hidden="YES" opaque="NO" tag="3" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bPQ-aJ-Lk6" userLabel="cancelButton" customClass="UIInterfaceStyleButton"> <button hidden="YES" opaque="NO" tag="3" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bPQ-aJ-Lk6" userLabel="cancelButton" 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="79" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Delete all"/> <accessibility key="accessibilityConfiguration" label="Delete all"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -50,7 +54,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" userLabel="backButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" 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="79" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/> <accessibility key="accessibilityConfiguration" label="Back"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -66,7 +70,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MuB-yy-R9o" userLabel="deleteButton" customClass="UIInterfaceStyleToggleButton"> <button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="MuB-yy-R9o" userLabel="deleteButton" customClass="UIInterfaceStyleToggleButton">
<rect key="frame" x="225" y="0.0" width="75" height="66"/> <rect key="frame" x="236" y="0.0" width="78" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Delete"/> <accessibility key="accessibilityConfiguration" label="Delete"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -82,7 +86,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="editButton" customClass="UIInterfaceStyleToggleButton"> <button opaque="NO" tag="6" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8" userLabel="editButton" customClass="UIInterfaceStyleToggleButton">
<rect key="frame" x="300" y="0.0" width="75" height="66"/> <rect key="frame" x="314" y="0.0" width="79" height="66"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Edit"/> <accessibility key="accessibilityConfiguration" label="Edit"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/> <fontDescription key="fontDescription" type="system" pointSize="14"/>
@ -99,30 +103,37 @@
</connections> </connections>
</button> </button>
</subviews> </subviews>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" tag="7" contentMode="scaleToFill" fixedFrame="YES" directionalLockEnabled="YES" showsHorizontalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8D6-vy-obt" userLabel="contentView"> <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" tag="7" contentMode="scaleToFill" fixedFrame="YES" directionalLockEnabled="YES" showsHorizontalScrollIndicator="NO" translatesAutoresizingMaskIntoConstraints="NO" id="8D6-vy-obt" userLabel="contentView">
<rect key="frame" x="0.0" y="66" width="375" height="493"/> <rect key="frame" x="0.0" y="66" width="393" height="678"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView tag="8" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="B6X-C9-2vm" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView tag="8" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="B6X-C9-2vm" userLabel="avatarImage">
<rect key="frame" x="142" y="10" width="90" height="90"/> <rect key="frame" x="150" y="10" width="90" height="90"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<connections> <connections>
<outletCollection property="gestureRecognizers" destination="8bV-f4-pLL" appends="YES" id="4V5-Px-aHT"/> <outletCollection property="gestureRecognizers" destination="8bV-f4-pLL" appends="YES" id="4V5-Px-aHT"/>
</connections> </connections>
</imageView> </imageView>
<label opaque="NO" userInteractionEnabled="NO" tag="9" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="moZ-Bg-zcv" userLabel="nameLabel"> <label opaque="NO" userInteractionEnabled="NO" tag="9" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="moZ-Bg-zcv" userLabel="nameLabel" customClass="CopyableLabel" customModule="linphoneapp" customModuleProvider="target">
<rect key="frame" x="0.0" y="108" width="375" height="40"/> <rect key="frame" x="0.0" y="108" width="393" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="27"/> <fontDescription key="fontDescription" type="system" pointSize="27"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Organization" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="pAA-jk-E4s" userLabel="organizationLabel">
<rect key="frame" x="0.0" y="148" width="393" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="22"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<tableView clipsSubviews="YES" tag="10" contentMode="scaleToFill" fixedFrame="YES" directionalLockEnabled="YES" alwaysBounceVertical="YES" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" style="plain" allowsSelection="NO" allowsSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="1" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="19" userLabel="tableView"> <tableView clipsSubviews="YES" tag="10" contentMode="scaleToFill" fixedFrame="YES" directionalLockEnabled="YES" alwaysBounceVertical="YES" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" style="plain" allowsSelection="NO" allowsSelectionDuringEditing="YES" rowHeight="44" sectionHeaderHeight="1" sectionFooterHeight="1" translatesAutoresizingMaskIntoConstraints="NO" id="19" userLabel="tableView">
<rect key="frame" x="0.0" y="156" width="375" height="337"/> <rect key="frame" x="0.0" y="176" width="393" height="502"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<inset key="scrollIndicatorInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/> <inset key="scrollIndicatorInsets" minX="0.0" minY="0.0" maxX="0.0" maxY="10"/>
<connections> <connections>
<outlet property="dataSource" destination="20" id="28"/> <outlet property="dataSource" destination="20" id="28"/>
@ -130,26 +141,26 @@
</connections> </connections>
</tableView> </tableView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</scrollView> </scrollView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No contact selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Mdj-Pz-nu4" userLabel="emptyLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No contact selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Mdj-Pz-nu4" userLabel="emptyLabel">
<rect key="frame" x="0.0" y="66" width="375" height="493"/> <rect key="frame" x="0.0" y="66" width="393" height="678"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JK8-Td-I1i" userLabel="waitView"> <view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JK8-Td-I1i" 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="393" height="744"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="J67-KE-kHm" userLabel="activityIndicatorView"> <activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="J67-KE-kHm" userLabel="activityIndicatorView">
<rect key="frame" x="179" y="267" width="20" height="20"/> <rect key="frame" x="188" y="358" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView> </activityIndicatorView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<gestureRecognizers/> <gestureRecognizers/>
</view> </view>
</subviews> </subviews>
@ -244,7 +255,7 @@
<rect key="frame" x="0.0" y="66" width="667" height="267"/> <rect key="frame" x="0.0" y="66" width="667" height="267"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView tag="8" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="IJJ-eZ-rC2" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView tag="8" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="IJJ-eZ-rC2" userLabel="avatarImage">
<rect key="frame" x="46" y="8" width="62" height="62"/> <rect key="frame" x="46" y="8" width="62" height="62"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
@ -321,5 +332,11 @@
<image name="edit_default.png" width="46.400001525878906" height="46.400001525878906"/> <image name="edit_default.png" width="46.400001525878906" height="46.400001525878906"/>
<image name="edit_disabled.png" width="46.400001525878906" height="46.400001525878906"/> <image name="edit_disabled.png" width="46.400001525878906" height="46.400001525878906"/>
<image name="valid_default.png" width="44.799999237060547" height="30.399999618530273"/> <image name="valid_default.png" width="44.799999237060547" height="30.399999618530273"/>
<systemColor name="secondarySystemBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources> </resources>
</document> </document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait" appearance="light"/> <device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -12,11 +12,14 @@
<connections> <connections>
<outlet property="addButton" destination="6" id="91"/> <outlet property="addButton" destination="6" id="91"/>
<outlet property="allButton" destination="4" id="27"/> <outlet property="allButton" destination="4" id="27"/>
<outlet property="deleteButton" destination="DZc-zR-1Q7" id="M6N-vO-UIb"/>
<outlet property="ldapMoreResultsLabel" destination="cDH-mL-cHP" id="3d9-gp-Hog"/>
<outlet property="linphoneButton" destination="5" id="31"/> <outlet property="linphoneButton" destination="5" id="31"/>
<outlet property="loadingLabel" destination="qSa-Ba-dq9" id="CPa-pO-OQD"/> <outlet property="loadingLabel" destination="qSa-Ba-dq9" id="CPa-pO-OQD"/>
<outlet property="loadingView" destination="CM2-Aq-Q3g" id="uie-SJ-TKf"/> <outlet property="loadingView" destination="CM2-Aq-Q3g" id="uie-SJ-TKf"/>
<outlet property="searchBar" destination="5jE-oF-d45" id="xfS-xo-2Bm"/> <outlet property="searchBar" destination="5jE-oF-d45" id="xfS-xo-2Bm"/>
<outlet property="selectedButtonImage" destination="A9k-KU-Dlm" id="4dX-pd-Y2D"/> <outlet property="selectedButtonImage" destination="A9k-KU-Dlm" id="4dX-pd-Y2D"/>
<outlet property="switchView" destination="93" id="4iM-Fl-F9B"/>
<outlet property="tableController" destination="TJG-JZ-YRR" id="0lt-gC-EOm"/> <outlet property="tableController" destination="TJG-JZ-YRR" id="0lt-gC-EOm"/>
<outlet property="toggleSelectionButton" destination="5lZ-u7-Yex" id="ULR-WM-Yuo"/> <outlet property="toggleSelectionButton" destination="5lZ-u7-Yex" id="ULR-WM-Yuo"/>
<outlet property="topBar" destination="3" id="w1O-2o-b18"/> <outlet property="topBar" destination="3" id="w1O-2o-b18"/>
@ -179,6 +182,14 @@
<outlet property="delegate" destination="TJG-JZ-YRR" id="V1N-gI-U4J"/> <outlet property="delegate" destination="TJG-JZ-YRR" id="V1N-gI-U4J"/>
</connections> </connections>
</tableView> </tableView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="More results are available, refine search to see them" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cDH-mL-cHP" userLabel="ldapMoreResultsLabel">
<rect key="frame" x="8" y="110" width="359" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No contact found in your address book" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JR3-k7-gVP" userLabel="emptyTableLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No contact found in your address book" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="JR3-k7-gVP" userLabel="emptyTableLabel">
<rect key="frame" x="0.0" y="110" width="375" height="449"/> <rect key="frame" x="0.0" y="110" width="375" height="449"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
@ -187,7 +198,7 @@
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view hidden="YES" contentMode="scaleToFill" id="CM2-Aq-Q3g"> <view hidden="YES" alpha="0.80000000000000004" contentMode="scaleToFill" id="CM2-Aq-Q3g">
<rect key="frame" x="0.0" y="110" width="375" height="449"/> <rect key="frame" x="0.0" y="110" width="375" height="449"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
@ -209,7 +220,7 @@
</view> </view>
</subviews> </subviews>
<color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<point key="canvasLocation" x="6.5217391304347831" y="142.29910714285714"/> <point key="canvasLocation" x="5.7971014492753632" y="141.96428571428569"/>
</view> </view>
<tableViewController id="TJG-JZ-YRR" userLabel="tableController" customClass="ContactsListTableView"> <tableViewController id="TJG-JZ-YRR" userLabel="tableController" customClass="ContactsListTableView">
<connections> <connections>

View file

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_0" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -11,6 +14,7 @@
<outlet property="addressLabel" destination="EoB-ux-sD7" id="Ajw-2s-M6X"/> <outlet property="addressLabel" destination="EoB-ux-sD7" id="Ajw-2s-M6X"/>
<outlet property="avatarImage" destination="23" id="43"/> <outlet property="avatarImage" destination="23" id="43"/>
<outlet property="backButton" destination="9" id="Pqj-y9-hqc"/> <outlet property="backButton" destination="9" id="Pqj-y9-hqc"/>
<outlet property="chatButton" destination="obZ-W7-q8P" id="96n-Oe-Gm0"/>
<outlet property="contactLabel" destination="25" id="rTL-Ut-42o"/> <outlet property="contactLabel" destination="25" id="rTL-Ut-42o"/>
<outlet property="emptyLabel" destination="hvz-CS-NME" id="Qws-r1-XMh"/> <outlet property="emptyLabel" destination="hvz-CS-NME" id="Qws-r1-XMh"/>
<outlet property="encryptedChatView" destination="JU4-bf-tVI" id="j6f-qz-VKd"/> <outlet property="encryptedChatView" destination="JU4-bf-tVI" id="j6f-qz-VKd"/>
@ -26,19 +30,19 @@
</placeholder> </placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="HKr-sq-hGv" userLabel="iphone6MetricsView"> <view contentMode="scaleToFill" id="HKr-sq-hGv" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4"> <view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4">
<rect key="frame" x="0.0" y="42" width="667" height="267"/> <rect key="frame" x="-1" y="41" width="389" height="735"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view alpha="0.90000000000000002" tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6" userLabel="topBar"> <view alpha="0.90000000000000002" tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/> <rect key="frame" x="0.0" y="0.0" width="389" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" userLabel="backButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9" userLabel="backButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="0.0" y="0.0" width="128" height="66"/> <rect key="frame" x="0.0" y="0.0" width="74" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/> <accessibility key="accessibilityConfiguration" label="Back"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -50,7 +54,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="50" userLabel="addButton" customClass="UIInterfaceStyleButton"> <button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="50" userLabel="addButton" customClass="UIInterfaceStyleButton">
<rect key="frame" x="539" y="0.0" width="128" height="66"/> <rect key="frame" x="314" y="0.0" width="75" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Add to contact"/> <accessibility key="accessibilityConfiguration" label="Add to contact"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -62,22 +66,22 @@
</connections> </connections>
</button> </button>
</subviews> </subviews>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor"/>
</view> </view>
<view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" userLabel="headerView"> <view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="33" userLabel="headerView">
<rect key="frame" x="0.0" y="66" width="667" height="250"/> <rect key="frame" x="0.0" y="66" width="389" height="250"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="23" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="23" userLabel="avatarImage">
<rect key="frame" x="244" y="8" width="178" height="100"/> <rect key="frame" x="141" y="8" width="104" height="100"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar"> <accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/> <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/> <bool key="isElement" value="YES"/>
</accessibility> </accessibility>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="25" userLabel="contactLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="John Doe" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="25" userLabel="contactLabel" customClass="CopyableLabel" customModule="linphoneapp" customModuleProvider="target">
<rect key="frame" x="0.0" y="110" width="667" height="40"/> <rect key="frame" x="0.0" y="110" width="389" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/> <accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/> <fontDescription key="fontDescription" type="system" pointSize="33"/>
@ -85,15 +89,15 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="mfN-Ai-9RX" userLabel="linphoneImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="mfN-Ai-9RX" userLabel="linphoneImage" customClass="UIRoundedImageView">
<rect key="frame" x="642" y="124" width="15" height="15"/> <rect key="frame" x="363" y="124" width="16" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar"> <accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/> <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
<bool key="isElement" value="YES"/> <bool key="isElement" value="YES"/>
</accessibility> </accessibility>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="11" contentMode="left" fixedFrame="YES" text="johndoe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="EoB-ux-sD7" userLabel="addressLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="11" contentMode="left" fixedFrame="YES" text="johndoe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="EoB-ux-sD7" userLabel="addressLabel" customClass="CopyableLabel" customModule="linphoneapp" customModuleProvider="target">
<rect key="frame" x="0.0" y="158" width="667" height="23"/> <rect key="frame" x="0.0" y="158" width="389" height="23"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/> <accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/> <fontDescription key="fontDescription" type="system" pointSize="18"/>
@ -101,11 +105,11 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k0D-99-OKO" userLabel="optionsView"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="k0D-99-OKO" userLabel="optionsView">
<rect key="frame" x="0.0" y="189" width="667" height="44"/> <rect key="frame" x="0.0" y="189" width="389" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button opaque="NO" tag="13" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5eX-W0-T4B" userLabel="callButton"> <button opaque="NO" tag="13" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="5eX-W0-T4B" userLabel="callButton">
<rect key="frame" x="179" y="0.0" width="51" height="51"/> <rect key="frame" x="98" y="0.0" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="call_start_body_default.png"> <state key="normal" image="call_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -117,7 +121,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="12" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="obZ-W7-q8P" userLabel="chatButton"> <button opaque="NO" tag="12" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="obZ-W7-q8P" userLabel="chatButton">
<rect key="frame" x="317" y="0.0" width="50" height="51"/> <rect key="frame" x="174" y="0.0" width="50" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="chat_start_body_default.png"> <state key="normal" image="chat_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -129,11 +133,11 @@
</connections> </connections>
</button> </button>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JU4-bf-tVI" userLabel="encryptedChatView"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JU4-bf-tVI" userLabel="encryptedChatView">
<rect key="frame" x="456" y="-1" width="50" height="51"/> <rect key="frame" x="251" y="-2" width="49" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="E2n-mF-saI" userLabel="encryptedChatButton" customClass="UIIconButton"> <button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="E2n-mF-saI" userLabel="encryptedChatButton" customClass="UIIconButton">
<rect key="frame" x="-1" y="0.0" width="51" height="51"/> <rect key="frame" x="0.0" y="-2" width="51" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Chat"/> <accessibility key="accessibilityConfiguration" label="Chat"/>
<state key="normal" image="chat_start_body_default.png"> <state key="normal" image="chat_start_body_default.png">
@ -154,35 +158,35 @@
</subviews> </subviews>
</view> </view>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
<tableView clipsSubviews="YES" tag="6" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="30" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="k6N-Av-eOu"> <tableView clipsSubviews="YES" tag="6" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="30" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="k6N-Av-eOu">
<rect key="frame" x="0.0" y="316" width="667" height="0.0"/> <rect key="frame" x="0.0" y="316" width="389" height="419"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <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"/>
<connections> <connections>
<outlet property="dataSource" destination="baU-d4-eu3" id="p7o-Mx-Kmc"/> <outlet property="dataSource" destination="baU-d4-eu3" id="p7o-Mx-Kmc"/>
<outlet property="delegate" destination="baU-d4-eu3" id="iS5-xg-0C2"/> <outlet property="delegate" destination="baU-d4-eu3" id="iS5-xg-0C2"/>
</connections> </connections>
</tableView> </tableView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No log selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hvz-CS-NME" userLabel="emptyLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No log selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hvz-CS-NME" userLabel="emptyLabel">
<rect key="frame" x="0.0" y="66" width="667" height="201"/> <rect key="frame" x="0.0" y="65" width="389" height="670"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/> <nil key="textColor"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dEJ-xc-518" userLabel="waitView"> <view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dEJ-xc-518" userLabel="waitView">
<rect key="frame" x="0.0" y="0.0" width="667" height="267"/> <rect key="frame" x="0.0" y="0.0" width="389" height="735"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="NK3-ME-9jd" userLabel="activityIndicatorView"> <activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="NK3-ME-9jd" userLabel="activityIndicatorView">
<rect key="frame" x="326" y="122" width="20" height="20"/> <rect key="frame" x="186" y="352" width="20" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView> </activityIndicatorView>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
<gestureRecognizers/> <gestureRecognizers/>
</view> </view>
</subviews> </subviews>
@ -193,23 +197,23 @@
<point key="canvasLocation" x="-3.2000000000000002" y="22.488755622188908"/> <point key="canvasLocation" x="-3.2000000000000002" y="22.488755622188908"/>
</view> </view>
<view contentMode="scaleToFill" id="LBc-mh-ozk" userLabel="iphone6MetricsView"> <view contentMode="scaleToFill" id="LBc-mh-ozk" userLabel="iphone6MetricsView">
<rect key="frame" x="0.0" y="0.0" width="667" height="375"/> <rect key="frame" x="0.0" y="0.0" width="390" height="844"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NHC-7w-48z"> <view tag="1" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NHC-7w-48z">
<rect key="frame" x="0.0" y="42" width="667" height="333"/> <rect key="frame" x="-2" y="42" width="391" height="800"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Rtv-hu-bCz" userLabel="topBar"> <view tag="2" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Rtv-hu-bCz" userLabel="topBar">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/> <rect key="frame" x="0.0" y="0.0" width="391" height="66"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="imageView:JOe-5t-C7f:image" translatesAutoresizingMaskIntoConstraints="NO" id="JOe-5t-C7f" userLabel="backgroundColor"> <imageView userInteractionEnabled="NO" tag="3" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="imageView:JOe-5t-C7f:image" translatesAutoresizingMaskIntoConstraints="NO" id="JOe-5t-C7f" userLabel="backgroundColor">
<rect key="frame" x="0.0" y="0.0" width="667" height="66"/> <rect key="frame" x="0.0" y="0.0" width="391" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>
<button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NJl-Lb-CU6" userLabel="backButton" customClass="UIIconButton"> <button opaque="NO" tag="4" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NJl-Lb-CU6" userLabel="backButton" customClass="UIIconButton">
<rect key="frame" x="0.0" y="0.0" width="71" height="66"/> <rect key="frame" x="0.0" y="0.0" width="41" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Back"/> <accessibility key="accessibilityConfiguration" label="Back"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -221,7 +225,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="O7r-6t-b7w" userLabel="addButton" customClass="UIIconButton"> <button opaque="NO" tag="5" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" adjustsImageWhenHighlighted="NO" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="O7r-6t-b7w" userLabel="addButton" customClass="UIIconButton">
<rect key="frame" x="594" y="0.0" width="73" height="66"/> <rect key="frame" x="346" y="0.0" width="45" height="65"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<accessibility key="accessibilityConfiguration" label="Add to contact"/> <accessibility key="accessibilityConfiguration" label="Add to contact"/>
<inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/> <inset key="titleEdgeInsets" minX="0.0" minY="18" maxX="0.0" maxY="0.0"/>
@ -235,7 +239,7 @@
</subviews> </subviews>
</view> </view>
<tableView clipsSubviews="YES" tag="6" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="30" sectionHeaderHeight="44" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2jK-gw-ULv"> <tableView clipsSubviews="YES" tag="6" contentMode="scaleToFill" fixedFrame="YES" alwaysBounceVertical="YES" style="plain" separatorStyle="none" allowsSelection="NO" rowHeight="30" sectionHeaderHeight="44" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="2jK-gw-ULv">
<rect key="frame" x="0.0" y="168" width="667" height="165"/> <rect key="frame" x="0.0" y="167" width="391" height="633"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections> <connections>
@ -244,11 +248,11 @@
</connections> </connections>
</tableView> </tableView>
<view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Mwp-y3-g1b" userLabel="headerView"> <view tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Mwp-y3-g1b" userLabel="headerView">
<rect key="frame" x="0.0" y="66" width="667" height="102"/> <rect key="frame" x="0.0" y="66" width="391" height="102"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="d9m-G0-1u3" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="d9m-G0-1u3" userLabel="avatarImage">
<rect key="frame" x="28" y="8" width="88" height="86"/> <rect key="frame" x="16" y="8" width="51" height="86"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar"> <accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/> <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
@ -256,7 +260,7 @@
</accessibility> </accessibility>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Qbg-hm-bd7" userLabel="contactLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="Qbg-hm-bd7" userLabel="contactLabel">
<rect key="frame" x="160" y="8" width="356" height="50"/> <rect key="frame" x="94" y="8" width="207" height="48"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/> <accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="33"/> <fontDescription key="fontDescription" type="system" pointSize="33"/>
@ -264,7 +268,7 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="11" contentMode="left" fixedFrame="YES" text="johndoe@sip.linphone.org" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="XJa-f6-K0y" userLabel="addressLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="11" contentMode="left" fixedFrame="YES" text="johndoe@sip.linphone.org" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="XJa-f6-K0y" userLabel="addressLabel">
<rect key="frame" x="160" y="56" width="356" height="38"/> <rect key="frame" x="94" y="54" width="207" height="39"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact name"/> <accessibility key="accessibilityConfiguration" label="Contact name"/>
<fontDescription key="fontDescription" type="system" pointSize="18"/> <fontDescription key="fontDescription" type="system" pointSize="18"/>
@ -272,11 +276,11 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="teU-AB-8hO" userLabel="optionsView"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="teU-AB-8hO" userLabel="optionsView">
<rect key="frame" x="0.0" y="29" width="667" height="44"/> <rect key="frame" x="0.0" y="29" width="391" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button opaque="NO" tag="13" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pBo-Oo-bAW" userLabel="callButton"> <button opaque="NO" tag="13" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pBo-Oo-bAW" userLabel="callButton">
<rect key="frame" x="491" y="0.0" width="51" height="51"/> <rect key="frame" x="271" y="0.0" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="call_start_body_default.png"> <state key="normal" image="call_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -288,7 +292,7 @@
</connections> </connections>
</button> </button>
<button opaque="NO" tag="12" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iDG-Mn-jm2" userLabel="chatButton"> <button opaque="NO" tag="12" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iDG-Mn-jm2" userLabel="chatButton">
<rect key="frame" x="551" y="0.0" width="51" height="51"/> <rect key="frame" x="303" y="0.0" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
<state key="normal" image="chat_start_body_default.png"> <state key="normal" image="chat_start_body_default.png">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -300,11 +304,11 @@
</connections> </connections>
</button> </button>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="m90-u6-x3J" userLabel="encryptedChatView"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="m90-u6-x3J" userLabel="encryptedChatView">
<rect key="frame" x="611" y="0.0" width="51" height="51"/> <rect key="frame" x="336" y="-1" width="51" height="51"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8fY-hz-ECC" userLabel="encryptedChatButton" customClass="UIIconButton"> <button opaque="NO" contentMode="scaleAspectFit" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="8fY-hz-ECC" userLabel="encryptedChatButton" customClass="UIIconButton">
<rect key="frame" x="0.0" y="0.0" width="51" height="51"/> <rect key="frame" x="0.0" y="-2" width="51" height="53"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Chat"/> <accessibility key="accessibilityConfiguration" label="Chat"/>
<state key="normal" image="chat_start_body_default.png"> <state key="normal" image="chat_start_body_default.png">
@ -325,7 +329,7 @@
</subviews> </subviews>
</view> </view>
<imageView hidden="YES" userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="G2O-Yh-fZA" userLabel="linphoneImage"> <imageView hidden="YES" userInteractionEnabled="NO" tag="10" contentMode="scaleAspectFit" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="G2O-Yh-fZA" userLabel="linphoneImage">
<rect key="frame" x="123" y="8" width="30" height="25"/> <rect key="frame" x="71" y="8" width="19" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Contact avatar"> <accessibility key="accessibilityConfiguration" label="Contact avatar">
<accessibilityTraits key="traits" image="YES" notEnabled="YES"/> <accessibilityTraits key="traits" image="YES" notEnabled="YES"/>
@ -336,7 +340,7 @@
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No log selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IHY-Yg-pkN" userLabel="emptyLabel"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" tag="40" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="No log selected" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IHY-Yg-pkN" userLabel="emptyLabel">
<rect key="frame" x="0.0" y="66" width="667" height="267"/> <rect key="frame" x="0.0" y="65" width="391" height="735"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -344,11 +348,11 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X29-vB-VIz" userLabel="waitView"> <view hidden="YES" tag="8" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="X29-vB-VIz" userLabel="waitView">
<rect key="frame" x="0.0" y="0.0" width="667" height="333"/> <rect key="frame" x="0.0" y="0.0" width="391" height="800"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="7l5-ZU-CbW" userLabel="activityIndicatorView"> <activityIndicatorView opaque="NO" tag="9" contentMode="scaleToFill" fixedFrame="YES" animating="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="7l5-ZU-CbW" userLabel="activityIndicatorView">
<rect key="frame" x="326" y="155" width="20" height="20"/> <rect key="frame" x="185" y="385" width="21" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</activityIndicatorView> </activityIndicatorView>
</subviews> </subviews>
@ -384,106 +388,113 @@
<image name="contact_add_disabled.png" width="55.200000762939453" height="47.200000762939453"/> <image name="contact_add_disabled.png" width="55.200000762939453" height="47.200000762939453"/>
<image name="imageView:JOe-5t-C7f:image" width="2" height="2"> <image name="imageView:JOe-5t-C7f:image" width="2" height="2">
<mutableData key="keyedArchiveRepresentation"> <mutableData key="keyedArchiveRepresentation">
YnBsaXN0MDDUAQIDBAUGUlNYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK8QEQcI YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T
ERYbHCAhKCsuOEBESExPVSRudWxs1AkKCwwNDg8QViRjbGFzc1xOU0ltYWdlRmxhZ3NWTlNSZXBzV05T S2V5ZWRBcmNoaXZlctEICVRyb290gAGvEBALDBccEyEmJy4xND5GR0tOVSRudWxs1Q0ODxAREhMUFRZW
Q29sb3KAEBIAwAAAgAKACtISCRMVWk5TLm9iamVjdHOhFIADgAnSEgkXGqIYGYAEgAWACBAA0h0JHh9f JGNsYXNzXk5TUmVzaXppbmdNb2RlXE5TSW1hZ2VGbGFnc1ZOU1JlcHNXTlNDb2xvcoAPEAASAMAAAIAC
EBROU1RJRkZSZXByZXNlbnRhdGlvboAGgAdPEQI+TU0AKgAAAAzh4eHhAA8BAAADAAAAAQACAAABAQAD gArSGA0ZG1pOUy5vYmplY3RzoRqAA4AJ0hgNHSCiHh+ABIAFgAjTDSIjJCUTXxAUTlNUSUZGUmVwcmVz
AAAAAQACAAABAgADAAAAAQAIAAABAwADAAAAAQABAAABBgADAAAAAQABAAABCgADAAAAAQABAAABEQAE ZW50YXRpb25fEBlOU0ludGVybmFsTGF5b3V0RGlyZWN0aW9ugAeABk8RAj5NTQAqAAAADOHh4eEADwEA
AAAAAQAAAAgBEgADAAAAAQABAAABFQADAAAAAQABAAABFgADAAAAAQACAAABFwAEAAAAAQAAAAQBHAAD AAMAAAABAAIAAAEBAAMAAAABAAIAAAECAAMAAAABAAgAAAEDAAMAAAABAAEAAAEGAAMAAAABAAEAAAEK
AAAAAQABAAABKAADAAAAAQACAAABUwADAAAAAQABAACHcwAHAAABeAAAAMYAAAAAAAABeGFwcGwCEAAA AAMAAAABAAEAAAERAAQAAAABAAAACAESAAMAAAABAAEAAAEVAAMAAAABAAEAAAEWAAMAAAABAAIAAAEX
bW50ckdSQVlYWVogB9UABwABAAAAAAAAYWNzcEFQUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPbW AAQAAAABAAAABAEcAAMAAAABAAEAAAEoAAMAAAABAAIAAAFTAAMAAAABAAEAAIdzAAcAAAF4AAAAxgAA
AAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE AAAAAAF4YXBwbAIQAABtbnRyR1JBWVhZWiAH1QAHAAEAAAAAAABhY3NwQVBQTAAAAAAAAAAAAAAAAAAA
ZGVzYwAAALQAAAB1Y3BydAAAASwAAAAnd3RwdAAAAVQAAAAUa1RSQwAAAWgAAAAOZGVzYwAAAAAAAAAb AAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Q2FsaWJyYXRlZCBHcmF5IENvbG9yc3BhY2UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAARkZXNjAAAAtAAAAHVjcHJ0AAABLAAAACd3dHB0AAABVAAAABRrVFJDAAABaAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5 AA5kZXNjAAAAAAAAABtDYWxpYnJhdGVkIEdyYXkgQ29sb3JzcGFjZQAAAAAAAAAAAAAAAAAAAAAAAAAA
cmlnaHQgQXBwbGUgQ29tcHV0ZXIsIEluYy4AAFhZWiAAAAAAAADzUQABAAAAARbMY3VydgAAAAAAAAAB
AjMAANIiIyQlWiRjbGFzc25hbWVYJGNsYXNzZXNfEBBOU0JpdG1hcEltYWdlUmVwoyQmJ1pOU0ltYWdl
UmVwWE5TT2JqZWN00iIjKSpXTlNBcnJheaIpJ9IiIywtXk5TTXV0YWJsZUFycmF5oywpJ9UvMDEyCTM0
NTY3V05TV2hpdGVcTlNDb21wb25lbnRzXE5TQ29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZUQw
IDAAQzAgMBADgAuAD9Q5OjsJPD0+P1ROU0lEVU5TSUNDV05TTW9kZWwQCYAMEACADtJBCUJDV05TLmRh
dGFPERFoAAARaGFwcGwCAAAAbW50ckdSQVlYWVogB9wACAAXAA8ALgAPYWNzcEFQUEwAAAAAbm9uZQAA
AAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAFZGVzYwAAAMAAAAB5ZHNjbQAAATwAAAfoY3BydAAACSQAAAAjd3RwdAAA
CUgAAAAUa1RSQwAACVwAAAgMZGVzYwAAAAAAAAAfR2VuZXJpYyBHcmF5IEdhbW1hIDIuMiBQcm9maWxl
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAG1sdWMAAAAAAAAAHwAAAAxza1NLAAAALgAAAYRkYURLAAAAOAAA AAB0ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBDb21wdXRlciwgSW5jLgAAWFlaIAAAAAAAAPNRAAEAAAAB
AbJjYUVTAAAAOAAAAep2aVZOAAAAQAAAAiJwdEJSAAAASgAAAmJ1a1VBAAAALAAAAqxmckZVAAAAPgAA FsxjdXJ2AAAAAAAAAAECMwAA0igpKitaJGNsYXNzbmFtZVgkY2xhc3Nlc18QEE5TQml0bWFwSW1hZ2VS
AthodUhVAAAANAAAAxZ6aFRXAAAAHgAAA0puYk5PAAAAOgAAA2hjc0NaAAAAKAAAA6JoZUlMAAAAJAAA ZXCjKiwtWk5TSW1hZ2VSZXBYTlNPYmplY3TSKCkvMFdOU0FycmF5oi8t0igpMjNeTlNNdXRhYmxlQXJy
A8ppdElUAAAATgAAA+5yb1JPAAAAKgAABDxkZURFAAAATgAABGZrb0tSAAAAIgAABLRzdlNFAAAAOAAA YXmjMi8t1TU2NzgNOTo7PD1XTlNXaGl0ZVxOU0NvbXBvbmVudHNcTlNDb2xvclNwYWNlXxASTlNDdXN0
AbJ6aENOAAAAHgAABNZqYUpQAAAAJgAABPRlbEdSAAAAKgAABRpwdFBPAAAAUgAABURubE5MAAAAQAAA b21Db2xvclNwYWNlRDAgMABDMCAwEAOAC4AO1D9AQQ1CQ0RFVE5TSURVTlNJQ0NXTlNNb2RlbBAJgAwQ
BZZlc0VTAAAATAAABdZ0aFRIAAAAMgAABiJ0clRSAAAAJAAABlRmaUZJAAAARgAABnhockhSAAAAPgAA AIANTxERnAAAEZxhcHBsAgAAAG1udHJHUkFZWFlaIAfcAAgAFwAPAC4AD2Fjc3BBUFBMAAAAAG5vbmUA
Br5wbFBMAAAASgAABvxydVJVAAAAOgAAB0ZlblVTAAAAPAAAB4BhckVHAAAALAAAB7wAVgFhAGUAbwBi AAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AGUAYwBuAOEAIABzAGkAdgDhACAAZwBhAG0AYQAgADIALAAyAEcAZQBuAGUAcgBpAHMAawAgAGcAcgDl AAAAAAAAAAAAAAAAAAAAAAAABWRlc2MAAADAAAAAeWRzY20AAAE8AAAIGmNwcnQAAAlYAAAAI3d0cHQA
ACAAMgAsADIAIABnAGEAbQBtAGEAcAByAG8AZgBpAGwARwBhAG0AbQBhACAAZABlACAAZwByAGkAcwBv AAl8AAAAFGtUUkMAAAmQAAAIDGRlc2MAAAAAAAAAH0dlbmVyaWMgR3JheSBHYW1tYSAyLjIgUHJvZmls
AHMAIABnAGUAbgDoAHIAaQBjAGEAIAAyAC4AMgBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBt ZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
ACAAQwBoAHUAbgBnACAARwBhAG0AbQBhACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIAaQBj AAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAAC4AAAGEZGFESwAAADoA
AG8AIABkAGEAIABHAGEAbQBhACAAZABlACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsETAQ9 AAGyY2FFUwAAADgAAAHsdmlWTgAAAEAAAAIkcHRCUgAAAEoAAAJkdWtVQQAAACwAAAKuZnJGVQAAAD4A
BDAAIABHAHIAYQB5AC0EMwQwBDwEMAAgADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1 AALaaHVIVQAAADQAAAMYemhUVwAAABoAAANMa29LUgAAACIAAANmbmJOTwAAADoAAAOIY3NDWgAAACgA
AGUAIABnAHIAaQBzACAAZwBhAG0AbQBhACAAMgAsADIAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA/ABy AAPCaGVJTAAAACQAAAPqcm9STwAAACoAAAQOZGVERQAAAE4AAAQ4aXRJVAAAAE4AAASGc3ZTRQAAADgA
AGsAZQAgAGcAYQBtAG0AYQAgADIALgAykBp1KHBwlo5RSV6mACAAMgAuADIAIIJyX2ljz4/wAEcAZQBu AATUemhDTgAAABoAAAUMamFKUAAAACYAAAUmZWxHUgAAACoAAAVMcHRQTwAAAFIAAAV2bmxOTAAAAEAA
AGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0AbQBhACAAMgAsADIALQBwAHIAbwBmAGkAbABPAGIAZQBj AAXIZXNFUwAAAEwAAAYIdGhUSAAAADIAAAZUdHJUUgAAACQAAAaGZmlGSQAAAEYAAAaqaHJIUgAAAD4A
AG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAAMgAuADIF0gXQBd4F1AAgBdAF5AXVBegAIAXbBdwF3AXZ AAbwcGxQTAAAAEoAAAcuYXJFRwAAACwAAAd4cnVSVQAAADoAAAekZW5VUwAAADwAAAfeAFYBYQBlAG8A
ACAAMgAuADIAUAByAG8AZgBpAGwAbwAgAGcAcgBpAGcAaQBvACAAZwBlAG4AZQByAGkAYwBvACAAZABl YgBlAGMAbgDhACAAcwBpAHYA4QAgAGcAYQBtAGEAIAAyACwAMgBHAGUAbgBlAHIAaQBzAGsAIABnAHIA
AGwAbABhACAAZwBhAG0AbQBhACAAMgAsADIARwBhAG0AYQAgAGcAcgBpACAAZwBlAG4AZQByAGkAYwED 5QAgADIALAAyACAAZwBhAG0AbQBhAC0AcAByAG8AZgBpAGwARwBhAG0AbQBhACAAZABlACAAZwByAGkA
ACAAMgAsADIAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBmAGUAbgAtAFAAcgBv cwBvAHMAIABnAGUAbgDoAHIAaQBjAGEAIAAyAC4AMgBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA
AGYAaQBsACAARwBhAG0AbQBhACAAMgAsADLHfLwYACDWjMDJACCsELnIACAAMgAuADIAINUEuFzTDMd8 4QBtACAAQwBoAHUAbgBnACAARwBhAG0AbQBhACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIA
Zm6QGnBwXqZ8+2VwACAAMgAuADIAIGPPj/Blh072TgCCLDCwMOwwpDCsMPMw3gAgADIALgAyACAw1zDt aQBjAG8AIABkAGEAIABHAGEAbQBhACAAZABlACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsE
MNUwoTCkMOsDkwO1A70DuQO6A8wAIAOTA7oDwQO5ACADkwOsA7wDvAOxACAAMgAuADIAUABlAHIAZgBp TAQ9BDAAIABHAHIAYQB5AC0EMwQwBDwEMAAgADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkA
AGwAIABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBt cQB1AGUAIABnAHIAaQBzACAAZwBhAG0AbQBhACAAMgAsADIAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA
AG0AYQAgADIALAAyAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwAgAGcAYQBtAG0AYQAgADIALAAy /AByAGsAZQAgAGcAYQBtAG0AYQAgADIALgAykBp1KHBwlo5RSV6mADIALgAygnJfaWPPj/DHfLwYACDW
AC0AcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGcAYQBt jMDJACCsELnIACAAMgAuADIAINUEuFzTDMd8AEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0A
AG0AYQAgAGQAZQAgAGcAcgBpAHMAZQBzACAAMgAsADIOIw4xDgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4i bQBhACAAMgAsADIALQBwAHIAbwBmAGkAbABPAGIAZQBjAG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAA
DkwOFw4xDkgOJw5EDhsAIAAyAC4AMgBHAGUAbgBlAGwAIABHAHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZ MgAuADIF0gXQBd4F1AAgBdAF5AXVBegAIAXbBdwF3AXZACAAMgAuADIARwBhAG0AYQAgAGcAcgBpACAA
AGwAZQBpAG4AZQBuACAAaABhAHIAbQBhAGEAbgAgAGcAYQBtAG0AYQAgADIALAAyACAALQBwAHIAbwBm ZwBlAG4AZQByAGkAYwEDACAAMgAsADIAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQA
AGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABw dQBmAGUAbgAtAFAAcgBvAGYAaQBsACAARwBhAG0AbQBhACAAMgAsADIAUAByAG8AZgBpAGwAbwAgAGcA
AHIAbwBmAGkAbABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAHMAegBhAHIAbwFb cgBpAGcAaQBvACAAZwBlAG4AZQByAGkAYwBvACAAZABlAGwAbABhACAAZwBhAG0AbQBhACAAMgAsADIA
AGMAaQAgAGcAYQBtAG0AYQAgADIALAAyBB4EMQRJBDAETwAgBEEENQRABDAETwAgBDMEMAQ8BDwEMAAg RwBlAG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQBwAHIAbwBmAGkAbGZukBpw
ADIALAAyAC0EPwRABD4ERAQ4BDsETABHAGUAbgBlAHIAaQBjACAARwByAGEAeQAgAEcAYQBtAG0AYQAg cF6mfPtlcAAyAC4AMmPPj/Blh072TgCCLDCwMOwwpDCsMPMw3gAgADIALgAyACAw1zDtMNUwoTCkMOsD
ADIALgAyACAAUAByAG8AZgBpAGwAZQY6BicGRQYnACAAMgAuADIAIAZEBkgGRgAgBjEGRQYnBi8GSgAg kwO1A70DuQO6A8wAIAOTA7oDwQO5ACADkwOsA7wDvAOxACAAMgAuADIAUABlAHIAZgBpAGwAIABnAGUA
BjkGJwZFdGV4dAAAAABDb3B5cmlnaHQgQXBwbGUgSW5jLiwgMjAxMgAAWFlaIAAAAAAAAPNRAAEAAAAB bgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBtAG0AYQAgADIA
FsxjdXJ2AAAAAAAABAAAAAAFAAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABt LAAyAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8A
AHIAdwB8AIEAhgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEH ZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGcAYQBtAG0AYQAgAGQA
AQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB0QHZ ZQAgAGcAcgBpAHMAZQBzACAAMgAsADIOIw4xDgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4iDkwOFw4xDkgO
AeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2AsECywLVAuAC6wL1 Jw5EDhsAIAAyAC4AMgBHAGUAbgBlAGwAIABHAHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZAGwAZQBpAG4A
AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRj ZQBuACAAaABhAHIAbQBhAGEAbgAgAGcAYQBtAG0AYQAgADIALAAyACAALQBwAHIAbwBmAGkAaQBsAGkA
BHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYn RwBlAG4AZQByAGkBDQBrAGkAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABwAHIAbwBmAGkA
BjcGSAZZBmoGewaMBp0GrwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghG bABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAHMAegBhAHIAbwFbAGMAaQAgAGcA
CFoIbgiCCJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrF YQBtAG0AYQAgADIALAAyBjoGJwZFBicAIAAyAC4AMgAgBkQGSAZGACAGMQZFBicGLwZKACAGOQYnBkUE
CtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYNQA1aDXQNjg2p HgQxBEkEMARPACAEQQQ1BEAEMARPACAEMwQwBDwEPAQwACAAMgAsADIALQQ/BEAEPgREBDgEOwRMAEcA
DcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQJhBDEGEQfhCbELkQ1xD1 ZQBuAGUAcgBpAGMAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABQAHIAbwBmAGkAbABlAAB0
ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixSt ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDEyAABYWVogAAAAAAAA81EAAQAAAAEWzGN1cnYA
FM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjV AAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwA
GPoZIBlFGWsZkRm3Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1w gQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkB
HZkdwx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7IiciVSKC HwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB
Iq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtyboJxgnSSd6J6sn3CgN +gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYD
KD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5LG4soizXLQwtQS12Last4S4W IQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwE
Lkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSe mgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkG
NNg1EzVNNYc1wjX9Njc2cjauNuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuq agZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIII
O+g8JzxlPKQ84z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6 lgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsL
Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRLDEtT Igs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgO
S5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIxUnxSx1MTU19TqlP2 Ew4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8R
VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0n bRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIV
XXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmbo NBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZ
Zz1nk2fpaD9olmjsaUNpmmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6 axmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHewe
cZVx8HJLcqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh Fh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwoj
fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauGDoZyhteHO4ef OCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo
iASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q1pE/kaiSEZJ6kuOTTZO2 1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu
lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBp 7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01
oNihR6G2oiailqMGo3aj5qRWpMelOKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24 hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8
ri2uoa8Wr4uwALB1sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7Lrun pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANE
vCG8m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4 R0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpM
yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjXXNfg2GTY6Nls cky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtV
2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG KFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpe
6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH bF69Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+lo
+lf65/t3/Af8mP0p/br+S/7c/23//4AN0iIjRUZdTlNNdXRhYmxlRGF0YaNFRydWTlNEYXRh0iIjSUpc P2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwckty
TlNDb2xvclNwYWNloksnXE5TQ29sb3JTcGFjZdIiI01OV05TQ29sb3KiTSfSIiNQUVdOU0ltYWdlolAn pnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9
XxAPTlNLZXllZEFyY2hpdmVy0VRVVHJvb3SAAQAIABEAGgAjAC0AMgA3AEsAUQBaAGEAbgB1AH0AfwCE oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6J
AIYAiACNAJgAmgCcAJ4AowCmAKgAqgCsAK4AswDKAMwAzgMQAxUDIAMpAzwDQANLA1QDWQNhA2QDaQN4 M4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSV
A3wDhwOPA5wDqQO+A8MDxwPJA8sDzQPWA9sD4QPpA+sD7QPvA/ED9gP+FWoVbBVxFX8VgxWKFY8VnBWf X5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobai
FawVsRW5FbwVwRXJFcwV3hXhFeYAAAAAAAACAQAAAAAAAABWAAAAAAAAAAAAAAAAAAAV6A JqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxav
i7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9
j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bM
Ncy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvb
gNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXr
cOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8
B/yY/Sn9uv5L/tz/bf//0igpSElcTlNDb2xvclNwYWNlokotXE5TQ29sb3JTcGFjZdIoKUxNV05TQ29s
b3KiTC3SKClPUFdOU0ltYWdlok8tAAgAEQAaACQAKQAyADcASQBMAFEAUwBmAGwAdwB+AI0AmgChAKkA
qwCtALIAtAC2ALsAxgDIAMoAzADRANQA1gDYANoA4QD4ARQBFgEYA1oDXwNqA3MDhgOKA5UDngOjA6sD
rgOzA8IDxgPRA9kD5gPzBAgEDQQRBBMEFQQXBCAEJQQrBDMENQQ3BDkEOxXbFeAV7RXwFf0WAhYKFg0W
EhYaAAAAAAAAAgEAAAAAAAAAUQAAAAAAAAAAAAAAAAAAFh0
</mutableData> </mutableData>
</image> </image>
<image name="linphone_user.png" width="41.599998474121094" height="42.400001525878906"/> <image name="linphone_user.png" width="41.599998474121094" height="42.400001525878906"/>
<image name="security_toogle_icon_green.png" width="33.599998474121094" height="38.400001525878906"/> <image name="security_toogle_icon_green.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> </resources>
</document> </document>

View file

@ -29,15 +29,15 @@
@property(nonatomic, strong) NSMutableArray *addresses; @property(nonatomic, strong) NSMutableArray *addresses;
@property(nonatomic, strong) NSMutableArray *phoneOrAddr; @property(nonatomic, strong) NSMutableArray *phoneOrAddr;
@property(nonatomic, strong) NSMutableArray *addressesCached; @property(nonatomic, strong) NSMutableArray *addressesCached;
@property(readonly, nonatomic) NSMutableDictionary *ldapContactAddressBookMap; @property(readonly, nonatomic) NSMutableDictionary *ldapAndProvisioningContactAddressBookMap;
@end @end
@implementation ChatConversationCreateTableView @implementation ChatConversationCreateTableView
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
if (!_ldapContactAddressBookMap) { if (!_ldapAndProvisioningContactAddressBookMap) {
_ldapContactAddressBookMap = [NSMutableDictionary dictionary]; _ldapAndProvisioningContactAddressBookMap = [NSMutableDictionary dictionary];
} }
[super viewWillAppear:animated]; [super viewWillAppear:animated];
@ -57,7 +57,7 @@
_addresses = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count]; _addresses = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
_phoneOrAddr = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count]; _phoneOrAddr = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
_addressesCached = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count]; _addressesCached = [[NSMutableArray alloc] initWithCapacity:LinphoneManager.instance.fastAddressBook.addressBookMap.allKeys.count];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserver:self addObserver:self
@ -82,10 +82,47 @@
[self searchBar:_searchBar textDidChange:_searchBar.text]; [self searchBar:_searchBar textDidChange:_searchBar.text];
self.tableView.accessibilityIdentifier = @"Suggested addresses"; self.tableView.accessibilityIdentifier = @"Suggested addresses";
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
NSDictionary* userInfo = notification.userInfo;
NSString* friend = (NSString*)userInfo[@"friend"];
for (int i = 0; i < _addresses.count; i++)
{
NSString *key = [_addresses objectAtIndex:i];
Contact *contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:key use_prefix:[CallManager.instance applyInternationalPrefix]]];
if (!contact) {
contact = [_ldapAndProvisioningContactAddressBookMap objectForKey:key];
}
if (contact.friend != nil && linphone_friend_get_address(contact.friend) != nil) {
char *curi = linphone_address_as_string_uri_only(linphone_friend_get_address(contact.friend));
NSString *uri = [NSString stringWithUTF8String:curi];
if([uri isEqual:friend]){
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:0];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}
}
}
}
} }
- (void) viewWillDisappear:(BOOL)animated { - (void) viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self]; [[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
[AvatarBridge removeAllObserver];
_notFirstTime = FALSE; _notFirstTime = FALSE;
} }
@ -97,64 +134,76 @@
_loadingView.hidden = TRUE; _loadingView.hidden = TRUE;
} }
-(BOOL) isSecureChatable:(const LinphoneFriend*)friend {
if (!friend)
return false;
const LinphonePresenceModel *model = linphone_friend_get_presence_model(friend);
return model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh);
}
- (void) buildChatContactTable { - (void) buildChatContactTable {
bctbx_list_t *results = [MagicSearchSingleton.instance getLastSearchResults]; bctbx_list_t *result_list = [MagicSearchSingleton.instance getLastSearchResults];
while (results) { bctbx_list_t *it;
LinphoneAccount *account = linphone_core_get_default_account(LC);
LinphoneSearchResult *result = results->data;
for (it = result_list; it != NULL; it = it->next) {
LinphoneSearchResult *result = it->data;
const LinphoneAddress *addr = linphone_search_result_get_address(result); const LinphoneAddress *addr = linphone_search_result_get_address(result);
const LinphoneFriend* friend = linphone_search_result_get_friend(result);
const char *phoneNumber = linphone_search_result_get_phone_number(result);
if (([LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"] && ![self isSecureChatable:friend]) || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"]) {
continue;
}
const char *phoneNumber = NULL;
Contact *contact = nil; Contact *contact = nil;
char *uri = nil; char *uri = nil;
NSString *address = nil; NSString *address = nil;
if (addr) { if (addr) {
uri = linphone_address_as_string_uri_only(addr); uri = linphone_address_as_string_uri_only(addr);
address = [NSString stringWithUTF8String:uri]; address = [NSString stringWithUTF8String:uri];
contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:address]]; contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:address use_prefix:[CallManager.instance applyInternationalPrefix]]];
}
if (!contact && friend) {
const LinphoneFriend* friend = linphone_search_result_get_friend(result); contact = [[Contact alloc] initWithFriend:friend];
if (!addr || (!contact && friend)) { [contact setCreatedFromLdapOrProvisioning:TRUE];
phoneNumber = linphone_search_result_get_phone_number(result); [_ldapAndProvisioningContactAddressBookMap setObject:contact forKey:address];
}
} else if (friend){
if (!phoneNumber) { if (!phoneNumber) {
results = results->next;
continue; continue;
} }
LinphoneAccount *account = linphone_core_get_default_account(LC);
if (account) { if (account) {
const char *normalizedPhoneNumber = linphone_account_normalize_phone_number(account, phoneNumber); char *normalizedPhoneNumber = linphone_account_normalize_phone_number(account, phoneNumber);
if (!normalizedPhoneNumber) { if (!normalizedPhoneNumber) {
// get invalid phone number, continue // get invalid phone number, continue
results = results->next;
continue; continue;
} }
addr = linphone_account_normalize_sip_uri(account, normalizedPhoneNumber); addr = linphone_account_normalize_sip_uri(account, normalizedPhoneNumber);
bctbx_free(normalizedPhoneNumber);
uri = linphone_address_as_string_uri_only(addr); uri = linphone_address_as_string_uri_only(addr);
address = [NSString stringWithUTF8String:uri]; address = [NSString stringWithUTF8String:uri];
contact = [[Contact alloc] initWithFriend:friend]; contact = [[Contact alloc] initWithFriend:friend];
[contact setCreatedFromLdap:TRUE]; [contact setCreatedFromLdapOrProvisioning:TRUE];
[_ldapContactAddressBookMap setObject:contact forKey:address]; [_ldapAndProvisioningContactAddressBookMap setObject:contact forKey:address];
linphone_address_unref(addr);
} }
} }
if (uri) ms_free(uri);
if (!addr) { if (!addr) {
results = results->next;
continue; continue;
} }
ms_free(uri);
[_addresses addObject:address]; [_addresses addObject:address];
[_phoneOrAddr addObject:phoneNumber ? [NSString stringWithUTF8String:phoneNumber] : address]; [_phoneOrAddr addObject:phoneNumber ? [NSString stringWithUTF8String:phoneNumber] : address];
[_addressesCached addObject:[NSString stringWithFormat:@"%d",linphone_search_result_get_capabilities(result)]]; [_addressesCached addObject:[NSString stringWithFormat:@"%d",linphone_search_result_get_capabilities(result)]];
results = results->next;
} }
bctbx_list_free(result_list);
[self.tableView reloadData]; [self.tableView reloadData];
_reloadMagicSearch = FALSE; _reloadMagicSearch = FALSE;
} }
@ -168,7 +217,7 @@
[_addresses removeAllObjects]; [_addresses removeAllObjects];
[_phoneOrAddr removeAllObjects]; [_phoneOrAddr removeAllObjects];
[_addressesCached removeAllObjects]; [_addressesCached removeAllObjects];
[_ldapContactAddressBookMap removeAllObjects]; [_ldapAndProvisioningContactAddressBookMap removeAllObjects];
[self.tableView reloadData]; [self.tableView reloadData];
_reloadMagicSearch = _reloadMagicSearch || [filter length]==0 || ![[MagicSearchSingleton.instance currentFilter] isEqualToString:filter]; _reloadMagicSearch = _reloadMagicSearch || [filter length]==0 || ![[MagicSearchSingleton.instance currentFilter] isEqualToString:filter];
@ -192,7 +241,7 @@
} }
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
return 60.0; return 60.0;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -203,12 +252,12 @@
NSString *key = [_addresses objectAtIndex:indexPath.row]; NSString *key = [_addresses objectAtIndex:indexPath.row];
NSString *phoneOrAddr = [_phoneOrAddr objectAtIndex:indexPath.row]; NSString *phoneOrAddr = [_phoneOrAddr objectAtIndex:indexPath.row];
Contact *contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:key]]; Contact *contact = [LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:[FastAddressBook normalizeSipURI:key use_prefix:[CallManager.instance applyInternationalPrefix]]];
if (!contact) { if (!contact) {
contact = [_ldapContactAddressBookMap objectForKey:key]; contact = [_ldapAndProvisioningContactAddressBookMap objectForKey:key];
} }
const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model(contact.friend) : NULL; const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model(contact.friend) : NULL;
Boolean linphoneContact = [FastAddressBook contactHasValidSipDomain:contact] Boolean linphoneContact = [FastAddressBook contactHasValidSipDomain:contact]
|| (model && linphone_presence_model_get_basic_status(model) == LinphonePresenceBasicStatusOpen); || (model && linphone_presence_model_get_basic_status(model) == LinphonePresenceBasicStatusOpen);
LinphoneAddress *addr = [LinphoneUtils normalizeSipOrPhoneAddress:key]; LinphoneAddress *addr = [LinphoneUtils normalizeSipOrPhoneAddress:key];
@ -216,30 +265,32 @@
return cell; return cell;
cell.linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] || !linphoneContact; cell.linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] || !linphoneContact;
cell.securityImage.hidden = !(model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh)); cell.securityImage.hidden = !(model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh));
int capabilities = [[_addressesCached objectAtIndex:indexPath.row] intValue]; int capabilities = [[_addressesCached objectAtIndex:indexPath.row] intValue];
BOOL greyCellForEncryptedChat = _isEncrypted ? capabilities > 1 : TRUE; BOOL greyCellForEncryptedChat = _isEncrypted ? capabilities > 1 : TRUE;
BOOL greyCellForGroupChat = _isGroupChat ? capabilities > 0 : TRUE; BOOL greyCellForGroupChat = _isGroupChat ? capabilities > 0 : TRUE;
cell.userInteractionEnabled = cell.greyView.hidden = greyCellForEncryptedChat && greyCellForGroupChat; cell.userInteractionEnabled = cell.greyView.hidden = greyCellForEncryptedChat && greyCellForGroupChat;
cell.displayNameLabel.text = [contact createdFromLdap] ? [contact displayName] : [FastAddressBook displayNameForAddress:addr]; cell.displayNameLabel.text = [contact createdFromLdapOrProvisioning] ? [contact displayName] : [FastAddressBook displayNameForAddress:addr];
char *str = linphone_address_as_string(addr); char *str = linphone_address_as_string(addr);
cell.addressLabel.text = linphoneContact ? [NSString stringWithUTF8String:str] : phoneOrAddr; cell.addressLabel.text = linphoneContact ? [NSString stringWithUTF8String:str] : phoneOrAddr;
ms_free(str); ms_free(str);
cell.selectedImage.hidden = ![_contactsGroup containsObject:cell.addressLabel.text]; cell.selectedImage.hidden = ![_contactsGroup containsObject:cell.addressLabel.text];
[cell.avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES]; [cell.avatarImage setImage:[FastAddressBook imageForAddress:addr]];
cell.contentView.userInteractionEnabled = false; cell.contentView.userInteractionEnabled = false;
cell.contentView.backgroundColor = UIColor.clearColor;
cell.backgroundColor = UIColor.clearColor;
return cell; return cell;
} }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIChatCreateCell *cell = [tableView cellForRowAtIndexPath:indexPath]; UIChatCreateCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (!cell.userInteractionEnabled) if (!cell.userInteractionEnabled)
return; return;
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC); LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount))) || !_isGroupChat) { if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount))) || !_isGroupChat) {
LinphoneAddress *addr = linphone_address_new(cell.addressLabel.text.UTF8String); LinphoneAddress *addr = linphone_address_new(cell.addressLabel.text.UTF8String);
[PhoneMainView.instance getOrCreateOneToOneChatRoom:addr waitView:_waitView isEncrypted:_isEncrypted]; [PhoneMainView.instance getOrCreateOneToOneChatRoom:addr waitView:_waitView isEncrypted:_isEncrypted];
if (!addr) { if (!addr) {
LOGE(@"Chat room could not be created on server, because null address."); LOGE(@"Chat room could not be created on server, because null address.");
[ChatConversationInfoView displayCreationError]; [ChatConversationInfoView displayCreationError];
@ -251,8 +302,6 @@
[tableView deselectRowAtIndexPath:indexPath animated:YES]; [tableView deselectRowAtIndexPath:indexPath animated:YES];
NSInteger index = 0; NSInteger index = 0;
_searchBar.text = @"";
[self searchBar:_searchBar textDidChange:@""];
if(cell.selectedImage.hidden) { if(cell.selectedImage.hidden) {
if(![_contactsGroup containsObject:cell.addressLabel.text]) { if(![_contactsGroup containsObject:cell.addressLabel.text]) {
[_contactsGroup addObject:cell.addressLabel.text]; [_contactsGroup addObject:cell.addressLabel.text];

View file

@ -69,7 +69,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[self.view addGestureRecognizer:tap]; [self.view addGestureRecognizer:tap];
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(100.0 , 50.0); layout.estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize;
_collectionController.collectionView = _collectionView; _collectionController.collectionView = _collectionView;
_collectionController = (ChatConversationCreateCollectionViewController *)[[UICollectionViewController alloc] initWithCollectionViewLayout:layout]; _collectionController = (ChatConversationCreateCollectionViewController *)[[UICollectionViewController alloc] initWithCollectionViewLayout:layout];
_collectionView.dataSource = self; _collectionView.dataSource = self;
@ -90,6 +90,10 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(viewUpdateEvent:) selector:@selector(viewUpdateEvent:)
name:kLinphoneChatCreateViewChange name:kLinphoneChatCreateViewChange
object:nil]; object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC); LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
_chiffreOptionView.hidden = !(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount))); _chiffreOptionView.hidden = !(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)));
if ([LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"]) { if ([LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"]) {
@ -98,8 +102,17 @@ static UICompositeViewDescription *compositeDescription = nil;
CGRect frame = _allButton.frame; CGRect frame = _allButton.frame;
frame.origin.x = _linphoneButton.frame.origin.x; frame.origin.x = _linphoneButton.frame.origin.x;
_allButton.frame = frame; _allButton.frame = frame;
} }
if ([LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"] || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"]) {
_chiffreOptionView.hidden = true;
_isEncrypted = true;
_tableController.isEncrypted = true;
_allButton.hidden = true;
_linphoneButton.hidden = true;
_selectedButtonImage.hidden = true;
}
if (_isForVoipConference) { if (_isForVoipConference) {
_switchView.hidden = true; _switchView.hidden = true;
@ -110,13 +123,28 @@ static UICompositeViewDescription *compositeDescription = nil;
} else { } else {
[_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal]; [_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal];
} }
_topBar.backgroundColor = VoipTheme.toolbar_color;
} else { } else {
_voipTitle.hidden = true; _voipTitle.hidden = true;
[_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal]; [_nextButton setImage:[UIImage imageNamed:@"next_default"] forState:UIControlStateNormal];
_topBar.backgroundColor = UIColor.secondarySystemBackgroundColor;
} }
[self displayModeChanged];
}
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
if (_isForVoipConference) {
_topBar.backgroundColor = [VoipTheme.voipToolbarBackgroundColor get];
self.view.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
_tableController.tableView.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
_tableController.searchBar.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
_tableController.collectionView.backgroundColor = [VoipTheme.voipBackgroundBWColor get];
} else {
_topBar.backgroundColor = UIColor.secondarySystemBackgroundColor;
self.view.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
_tableController.tableView.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
_tableController.searchBar.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
_tableController.collectionView.backgroundColor = [VoipTheme.backgroundWhiteBlack get];
}
} }
- (void)viewUpdateEvent:(NSNotification *)notif { - (void)viewUpdateEvent:(NSNotification *)notif {
@ -131,14 +159,14 @@ static UICompositeViewDescription *compositeDescription = nil;
frame.origin.x = self.view.frame.size.width * 0.192; frame.origin.x = self.view.frame.size.width * 0.192;
} }
_chiffreOptionView.frame = frame; _chiffreOptionView.frame = frame;
_isEncrypted = FALSE; _isEncrypted = [LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"] || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"]; // false by default
CGRect buttonFrame = _chiffreButton.frame; CGRect buttonFrame = _chiffreButton.frame;
_tableController.isEncrypted = _isEncrypted;
// no encrypted by default if (!_isEncrypted) {
buttonFrame.origin.x = 2; buttonFrame.origin.x = 2;
[_chiffreImage setImage:[UIImage imageNamed:@"security_toogle_background_grey.png"]]; [_chiffreImage setImage:[UIImage imageNamed:@"security_toogle_background_grey.png"]];
_chiffreButton.frame = buttonFrame; _chiffreButton.frame = buttonFrame;
}
_waitView.hidden = YES; _waitView.hidden = YES;
_backButton.hidden = IPAD && !(_isForVoipConference||_isForOngoingVoipConference); _backButton.hidden = IPAD && !(_isForVoipConference||_isForOngoingVoipConference);
@ -164,9 +192,8 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
if (IPAD) [NSNotificationCenter.defaultCenter removeObserver:self];
[NSNotificationCenter.defaultCenter removeObserver:self];
} }
#pragma mark - Chat room functions #pragma mark - Chat room functions
@ -184,7 +211,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[_tableController.contactsGroup removeAllObjects]; [_tableController.contactsGroup removeAllObjects];
if (_isForVoipConference) { if (_isForVoipConference) {
if (_isForOngoingVoipConference) { if (_isForOngoingVoipConference) {
[PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription]; [PhoneMainView.instance popToView:VIEW(ConferenceCallView).compositeViewDescription];
[ControlsViewModelBridge showParticipants]; [ControlsViewModelBridge showParticipants];
} else { } else {
[PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription]; [PhoneMainView.instance popToView:ConferenceSchedulingView.compositeViewDescription];
@ -200,7 +227,7 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onNextClick:(id)sender { - (IBAction)onNextClick:(id)sender {
if (_isForVoipConference) { if (_isForVoipConference) {
if (_isForOngoingVoipConference) { if (_isForOngoingVoipConference) {
[PhoneMainView.instance changeCurrentView:VIEW(ActiveCallOrConferenceView).compositeViewDescription]; [PhoneMainView.instance popToView:VIEW(ConferenceCallView).compositeViewDescription];
[ConferenceViewModelBridge updateParticipantsListWithAddresses:_tableController.contactsGroup]; [ConferenceViewModelBridge updateParticipantsListWithAddresses:_tableController.contactsGroup];
} else { } else {
[PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription]; [PhoneMainView.instance changeCurrentView:VIEW(ConferenceSchedulingSummaryView).compositeViewDescription];
@ -280,7 +307,7 @@ typedef enum { ContactsAll, ContactsLinphone, ContactsMAX } ContactsCategory;
return NO; return NO;
} }
#pragma mark - UICollectionViewDataSource #pragma mark - UICollectionViewDataSource & Delegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _tableController.contactsGroup.count; return _tableController.contactsGroup.count;
} }
@ -302,9 +329,10 @@ typedef enum { ContactsAll, ContactsLinphone, ContactsMAX } ContactsCategory;
ms_free(phone); ms_free(phone);
} else } else
addr = linphone_address_new(uri.UTF8String); addr = linphone_address_new(uri.UTF8String);
cell = [cell initWithName:[FastAddressBook displayNameForAddress:addr]]; [cell.nameLabel setText:[FastAddressBook displayNameForAddress:addr]];
linphone_address_unref(addr); linphone_address_unref(addr);
return cell; return cell;
} }
@end @end

View file

@ -32,7 +32,7 @@
NSString *messageText; NSString *messageText;
} }
@property(nonatomic) LinphoneChatMessage *msg; @property(nonatomic) LinphoneEventLog *event;
@property(nonatomic) bctbx_list_t *displayedList; @property(nonatomic) bctbx_list_t *displayedList;
@property(nonatomic) bctbx_list_t *receivedList; @property(nonatomic) bctbx_list_t *receivedList;
@property(nonatomic) bctbx_list_t *notReceivedList; @property(nonatomic) bctbx_list_t *notReceivedList;

View file

@ -48,17 +48,12 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)viewDidLoad { - (void)viewDidLoad {
[super viewDidLoad]; [super viewDidLoad];
_msg = NULL;
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
int index = [VIEW(ChatConversationView).tableController indexOfMesssage:_msg]; _cell = [VIEW(ChatConversationView).tableController buildMessageCell:_event];
if (index < 0)
[PhoneMainView.instance popToView:ChatConversationView.compositeViewDescription];
_cell = (UIChatBubbleTextCell *)[VIEW(ChatConversationView).tableController tableView:VIEW(ChatConversationView).tableController.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
_cell.frame = CGRectMake(-10,0,_msgView.frame.size.width,_msgView.frame.size.height); _cell.frame = CGRectMake(-10,0,_msgView.frame.size.width,_msgView.frame.size.height);
_cell.isFirst = true; _cell.isFirst = true;
_cell.isLast = true; _cell.isLast = true;
@ -90,23 +85,33 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (void)updateImdnList { - (void)updateImdnList {
if (_msg && linphone_chat_message_get_chat_room(_msg)) { if (_event) {
_displayedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDisplayed); LinphoneChatMessage *_msg = linphone_event_log_get_chat_message(_event);
_receivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDeliveredToUser); if (_msg) {
_notReceivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDelivered); _displayedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDisplayed);
_errorList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateNotDelivered); _receivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDeliveredToUser);
_notReceivedList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateDelivered);
[_tableView reloadData]; _errorList = linphone_chat_message_get_participants_by_imdn_state(_msg, LinphoneChatMessageStateNotDelivered);
[_tableView reloadData];
}
} }
} }
- (void)fitContent { - (void)fitContent {
LinphoneChatMessage *_msg = linphone_event_log_get_chat_message(_event);
CGSize messageSize = [UIChatBubbleTextCell ViewHeightForMessage:_msg withWidth:self.view.frame.size.width]; CGSize messageSize = [UIChatBubbleTextCell ViewHeightForMessage:_msg withWidth:self.view.frame.size.width];
[_msgView setFrame:CGRectMake(_msgView.frame.origin.x, if (messageSize.height > self.view.bounds.size.height/2) {
_msgView.frame.origin.y, [_msgView setFrame:CGRectMake(_msgView.frame.origin.x,
self.view.frame.size.width, _msgView.frame.origin.y,
messageSize.height+5)]; self.view.frame.size.width,
self.view.bounds.size.height/2 +5)];
} else {
[_msgView setFrame:CGRectMake(_msgView.frame.origin.x,
_msgView.frame.origin.y,
self.view.frame.size.width,
messageSize.height+5)];
}
[_tableView setFrame:CGRectMake(_tableView.frame.origin.x, [_tableView setFrame:CGRectMake(_tableView.frame.origin.x,
_msgView.frame.origin.y + _msgView.frame.size.height + 10, _msgView.frame.origin.y + _msgView.frame.size.height + 10,
@ -299,7 +304,8 @@ static UICompositeViewDescription *compositeDescription = nil;
f.unitsStyle = NSDateComponentsFormatterUnitsStylePositional; f.unitsStyle = NSDateComponentsFormatterUnitsStylePositional;
f.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad; f.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
if (linphone_chat_message_is_ephemeral(_msg)) { LinphoneChatMessage *_msg = _event ? linphone_event_log_get_chat_message(_event) : nil;
if (_msg && linphone_chat_message_is_ephemeral(_msg)) {
long duration = linphone_chat_message_get_ephemeral_expire_time(_msg) == 0 ? long duration = linphone_chat_message_get_ephemeral_expire_time(_msg) == 0 ?
linphone_chat_room_get_ephemeral_lifetime(linphone_chat_message_get_chat_room(_msg)) : linphone_chat_room_get_ephemeral_lifetime(linphone_chat_message_get_chat_room(_msg)) :
linphone_chat_message_get_ephemeral_expire_time(_msg)-[NSDate date].timeIntervalSince1970; linphone_chat_message_get_ephemeral_expire_time(_msg)-[NSDate date].timeIntervalSince1970;

View file

@ -35,6 +35,7 @@
@property(nonatomic) LinphoneChatRoom *room; @property(nonatomic) LinphoneChatRoom *room;
@property(nonatomic) LinphoneChatRoomCbs *chatRoomCbs; @property(nonatomic) LinphoneChatRoomCbs *chatRoomCbs;
@property(nonatomic) const char *peerAddress; @property(nonatomic) const char *peerAddress;
@property(nonatomic) const char *localAddress;
@property (weak, nonatomic) IBOutlet UIIconButton *nextButton; @property (weak, nonatomic) IBOutlet UIIconButton *nextButton;
@property (weak, nonatomic) IBOutlet UIRoundBorderedButton *quitButton; @property (weak, nonatomic) IBOutlet UIRoundBorderedButton *quitButton;

View file

@ -22,6 +22,7 @@
#import "ChatConversationInfoView.h" #import "ChatConversationInfoView.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "UIChatConversationInfoTableViewCell.h" #import "UIChatConversationInfoTableViewCell.h"
#import "linphoneapp-Swift.h"
#import "linphone/core.h" #import "linphone/core.h"
@ -84,6 +85,7 @@ static UICompositeViewDescription *compositeDescription = nil;
_room = NULL; _room = NULL;
_chatRoomCbs = NULL; _chatRoomCbs = NULL;
_peerAddress = NULL; _peerAddress = NULL;
_localAddress = NULL;
} }
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
@ -95,10 +97,45 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(onLinphoneCoreReady:) selector:@selector(onLinphoneCoreReady:)
name:kLinphoneGlobalStateUpdate name:kLinphoneGlobalStateUpdate
object:nil]; object:nil];
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
NSDictionary* userInfo = notification.userInfo;
NSString* friend = (NSString*)userInfo[@"friend"];
for (int i = 0; i < _contacts.count; i++)
{
NSString *uri = _contacts[i];
LinphoneAddress *addr = linphone_address_new(uri.UTF8String);
if (addr != nil) {
char *curi = linphone_address_as_string_uri_only(addr);
NSString *uri = [NSString stringWithUTF8String:curi];
if([uri isEqual:friend]){
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:0];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}
}
}
}
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
[NSNotificationCenter.defaultCenter removeObserver:self]; [NSNotificationCenter.defaultCenter removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
[AvatarBridge removeAllObserver];
if (!_room || !_chatRoomCbs) if (!_room || !_chatRoomCbs)
return; return;
@ -153,10 +190,11 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)onLinphoneCoreReady:(NSNotification *)notif { - (void)onLinphoneCoreReady:(NSNotification *)notif {
if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) { if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) {
if (!_create && _peerAddress) { if (!_create && _peerAddress && _localAddress) {
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress); LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
if (peerAddr) { LinphoneAddress *localAddr = linphone_core_create_address([LinphoneManager getLc], _localAddress);
_room = linphone_core_get_chat_room([LinphoneManager getLc], peerAddr); if (peerAddr && localAddr) {
_room = linphone_core_search_chat_room([LinphoneManager getLc], NULL, localAddr, peerAddr, NULL);
} }
[self configure]; [self configure];
} }
@ -181,7 +219,7 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (void)onValidate { - (void)onValidate {
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
// Change subject if necessary // Change subject if necessary
if (![_oldSubject isEqualToString:_nameLabel.text]) if (![_oldSubject isEqualToString:_nameLabel.text])
linphone_chat_room_set_subject(_room, _nameLabel.text.UTF8String); linphone_chat_room_set_subject(_room, _nameLabel.text.UTF8String);
@ -277,7 +315,7 @@ static UICompositeViewDescription *compositeDescription = nil;
view.isForVoipConference = FALSE; view.isForVoipConference = FALSE;
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} else { } else {
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} }
} }
@ -326,7 +364,7 @@ static UICompositeViewDescription *compositeDescription = nil;
cell.uri = _contacts[indexPath.row]; cell.uri = _contacts[indexPath.row];
LinphoneAddress *addr = linphone_address_new(cell.uri.UTF8String); LinphoneAddress *addr = linphone_address_new(cell.uri.UTF8String);
cell.nameLabel.text = (addr == nil? cell.uri : [FastAddressBook displayNameForAddress:addr]); cell.nameLabel.text = (addr == nil? cell.uri : [FastAddressBook displayNameForAddress:addr]);
[cell.avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:YES withRoundedRadius:YES]; [cell.avatarImage setImage:[FastAddressBook imageForAddress:addr]];
cell.controllerView = self; cell.controllerView = self;
if(![_admins containsObject:cell.uri]) { if(![_admins containsObject:cell.uri]) {
cell.adminLabel.enabled = FALSE; cell.adminLabel.enabled = FALSE;

View file

@ -25,6 +25,7 @@
#import "UICheckBoxTableView.h" #import "UICheckBoxTableView.h"
@interface FileContext : NSObject @interface FileContext : NSObject
@property NSMutableArray <NSString *> *typesArray; @property NSMutableArray <NSString *> *typesArray;
@property NSMutableArray <NSData *> *datasArray; @property NSMutableArray <NSData *> *datasArray;
@ -60,6 +61,7 @@
@property(nonatomic) NSTimer *ephemeralDisplayTimer; @property(nonatomic) NSTimer *ephemeralDisplayTimer;
@property (nullable, nonatomic) UIButton *floatingScrollButton; @property (nullable, nonatomic) UIButton *floatingScrollButton;
@property (nullable, nonatomic) UILabel *scrollBadge; @property (nullable, nonatomic) UILabel *scrollBadge;
@property (nullable, nonatomic) UIButton *floatingScrollBackground;
- (void)addEventEntry:(LinphoneEventLog *)event; - (void)addEventEntry:(LinphoneEventLog *)event;
- (void)scrollToBottom:(BOOL)animated; - (void)scrollToBottom:(BOOL)animated;
@ -70,5 +72,6 @@
- (void) dismissMessagesPopups; - (void) dismissMessagesPopups;
- (void) scrollToMessage:(LinphoneChatMessage *)message; - (void) scrollToMessage:(LinphoneChatMessage *)message;
- (int) indexOfMesssage:(LinphoneChatMessage *)message; - (int) indexOfMesssage:(LinphoneChatMessage *)message;
- (void *)buildMessageCell:(LinphoneEventLog *) event;
@end @end

View file

@ -20,7 +20,6 @@
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "ChatConversationTableView.h" #import "ChatConversationTableView.h"
#import "ChatConversationImdnView.h" #import "ChatConversationImdnView.h"
#import "UIChatBubbleTextCell.h"
#import "UIChatBubblePhotoCell.h" #import "UIChatBubblePhotoCell.h"
#import "UIChatNotifiedEventCell.h" #import "UIChatNotifiedEventCell.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
@ -84,7 +83,6 @@
LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(_chatRoom); LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(_chatRoom);
bool oneToOne = capabilities & LinphoneChatRoomCapabilitiesOneToOne; bool oneToOne = capabilities & LinphoneChatRoomCapabilitiesOneToOne;
bctbx_list_t *chatRoomEvents = linphone_chat_room_get_history_events(_chatRoom, 0); bctbx_list_t *chatRoomEvents = linphone_chat_room_get_history_events(_chatRoom, 0);
int unread_count = 0; int unread_count = 0;
bctbx_list_t *head = chatRoomEvents; bctbx_list_t *head = chatRoomEvents;
@ -142,12 +140,11 @@
- (void)addEventEntry:(LinphoneEventLog *)event { - (void)addEventEntry:(LinphoneEventLog *)event {
[eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]]; [eventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
[totalEventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]]; [totalEventList addObject:[NSValue valueWithPointer:linphone_event_log_ref(event)]];
int pos = (int)eventList.count - 1; int pos = (int)eventList.count - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:pos inSection:0];
[self.tableView beginUpdates]; [self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade]; [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView reloadData];
[self.tableView endUpdates]; [self.tableView endUpdates];
} }
@ -171,9 +168,9 @@
//[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]]; //[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0] [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:(count - 1) inSection:0]
atScrollPosition:UITableViewScrollPositionBottom atScrollPosition:UITableViewScrollPositionBottom
animated:YES]; animated:animated];
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
[ChatConversationView markAsRead:_chatRoom]; [ChatConversationViewSwift markAsRead:_chatRoom];
} }
- (void)scrollToLastUnread:(BOOL)animated { - (void)scrollToLastUnread:(BOOL)animated {
@ -203,7 +200,7 @@
index = (int)count - 1; index = (int)count - 1;
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
[ChatConversationView markAsRead:_chatRoom]; [ChatConversationViewSwift markAsRead:_chatRoom];
// Scroll to unread // Scroll to unread
if (index < 0) if (index < 0)
@ -331,28 +328,32 @@ static const int BASIC_EVENT_LIST=15;
} }
} }
-(UIChatBubbleTextCell *)buildMessageCell:(LinphoneEventLog *) event {
NSString *kCellId = nil;
LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event);
BOOL isConferenceIcs = [ICSBubbleView isConferenceInvitationMessageWithCmessage:chat];
if (!isConferenceIcs && (linphone_chat_message_get_file_transfer_information(chat) || linphone_chat_message_get_external_body_url(chat)))
kCellId = NSStringFromClass(UIChatBubblePhotoCell.class);
else
kCellId = NSStringFromClass(UIChatBubbleTextCell.class);
// To use less memory and to avoid overlapping. To be improved.
UIChatBubbleTextCell *cell = [self.tableView dequeueReusableCellWithIdentifier:kCellId];
cell = [[NSClassFromString(kCellId) alloc] initWithIdentifier:kCellId];
[cell setEvent:event];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *kCellId = nil; NSString *kCellId = nil;
LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue]; LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue];
if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) { if (linphone_event_log_get_type(event) == LinphoneEventLogTypeConferenceChatMessage) {
UIChatBubbleTextCell *cell = [self buildMessageCell:event];
LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event); LinphoneChatMessage *chat = linphone_event_log_get_chat_message(event);
BOOL isConferenceIcs = [ICSBubbleView isConferenceInvitationMessageWithCmessage:chat]; if (chat) {
if (!isConferenceIcs && (linphone_chat_message_get_file_transfer_information(chat) || linphone_chat_message_get_external_body_url(chat))) cell.isFirst = [self isFirstIndexInTableView:indexPath chat:chat];
kCellId = NSStringFromClass(UIChatBubblePhotoCell.class); cell.isLast = [self isLastIndexInTableView:indexPath chat:chat];
else [cell update];
kCellId = NSStringFromClass(UIChatBubbleTextCell.class); }
// To use less memory and to avoid overlapping. To be improved.
UIChatBubbleTextCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
cell = [[NSClassFromString(kCellId) alloc] initWithIdentifier:kCellId];
[cell setEvent:event];
if (chat) {
cell.isFirst = [self isFirstIndexInTableView:indexPath chat:chat];
cell.isLast = [self isLastIndexInTableView:indexPath chat:chat];
[cell update];
}
[cell setChatRoomDelegate:_chatRoomDelegate]; [cell setChatRoomDelegate:_chatRoomDelegate];
[super accessoryForCell:cell atPath:indexPath]; [super accessoryForCell:cell atPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.selectionStyle = UITableViewCellSelectionStyleNone;
@ -378,6 +379,13 @@ static const int BASIC_EVENT_LIST=15;
[_chatRoomDelegate tableViewIsScrolling]; [_chatRoomDelegate tableViewIsScrolling];
} }
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
[ChatConversationViewSwift markAsRead:_chatRoom];
}
}
static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f; static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -423,7 +431,7 @@ static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f;
title:NSLocalizedString(@"Reply", nil) title:NSLocalizedString(@"Reply", nil)
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
LinphoneChatMessage *msg = linphone_event_log_get_chat_message(event); LinphoneChatMessage *msg = linphone_event_log_get_chat_message(event);
[VIEW(ChatConversationView) initiateReplyViewForMessage:msg]; [VIEW(ChatConversationViewSwift) initiateReplyViewForMessage:msg];
[self scrollToBottom:TRUE]; [self scrollToBottom:TRUE];
}]; }];
@ -443,12 +451,11 @@ static const CGFloat MESSAGE_SPACING_PERCENTAGE = 1.f;
LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue]; LinphoneEventLog *event = [[eventList objectAtIndex:indexPath.row] pointerValue];
UIContextualAction *imdnAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal UIContextualAction *imdnAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal
title:NSLocalizedString(@"Info", nil) title:NSLocalizedString(@"Info", nil)
handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
LinphoneChatMessage *msg = linphone_event_log_get_chat_message(event); ChatConversationImdnView *view = VIEW(ChatConversationImdnView);
ChatConversationImdnView *view = VIEW(ChatConversationImdnView); view.event = event;
view.msg = msg; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}]; }];
UISwipeActionsConfiguration *swipeActionConfig; UISwipeActionsConfiguration *swipeActionConfig;

View file

@ -63,6 +63,7 @@
@property(nonatomic) LinphoneChatRoomCbs *chatRoomCbs; @property(nonatomic) LinphoneChatRoomCbs *chatRoomCbs;
@property(nonatomic) Boolean markAsRead; @property(nonatomic) Boolean markAsRead;
@property(nonatomic) const char *peerAddress; @property(nonatomic) const char *peerAddress;
@property(nonatomic) const char *localAddress;
@property (strong, nonatomic) FileDataSource *FileDataSource; @property (strong, nonatomic) FileDataSource *FileDataSource;
@ -112,6 +113,7 @@
@property LinphonePlayer *sharedVoicePlayer; @property LinphonePlayer *sharedVoicePlayer;
@property BOOL showVoiceRecorderView; @property BOOL showVoiceRecorderView;
@property BOOL preservePendingActions; @property BOOL preservePendingActions;
@property BOOL *sharingMedia;
// Reply // Reply
@property (weak, nonatomic) IBOutlet UIView *replyView; @property (weak, nonatomic) IBOutlet UIView *replyView;

View file

@ -213,10 +213,17 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
if (!_chatRoom)
[self onLinphoneCoreReady:nil];
[NSNotificationCenter.defaultCenter addObserver:self [NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(applicationWillEnterBackground) selector:@selector(applicationDidEnterBackground)
name:UIApplicationDidEnterBackgroundNotification name:UIApplicationDidEnterBackgroundNotification
object:nil]; object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(applicationWillEnterForeground)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self [NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(keyboardWillShow:) selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification name:UIKeyboardWillShowNotification
@ -233,10 +240,10 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(callUpdateEvent:) selector:@selector(callUpdateEvent:)
name:kLinphoneCallUpdate name:kLinphoneCallUpdate
object:nil]; object:nil];
[NSNotificationCenter.defaultCenter addObserver:self [NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(onLinphoneCoreReady:) selector:@selector(onLinphoneCoreReady:)
name:kLinphoneGlobalStateUpdate name:kLinphoneGlobalStateUpdate
object:nil]; object:nil];
[NSNotificationCenter.defaultCenter addObserver:self [NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(endVoicePlayingIfDoingSO:) selector:@selector(endVoicePlayingIfDoingSO:)
@ -247,24 +254,23 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(endVoicePlayingIfDoingSO:) selector:@selector(endVoicePlayingIfDoingSO:)
name:kLinphoneVoiceMessagePlayerEOF name:kLinphoneVoiceMessagePlayerEOF
object:nil]; object:nil];
if ([_fileContext count] > 0) { if ([_fileContext count] > 0) {
[UIView animateWithDuration:0 [UIView animateWithDuration:0
delay:0 delay:0
options:UIViewAnimationOptionBeginFromCurrentState options:UIViewAnimationOptionBeginFromCurrentState
animations:^{ animations:^{
// resizing imagesView // resizing imagesView
CGRect imagesFrame = [_imagesView frame]; CGRect imagesFrame = [_imagesView frame];
imagesFrame.origin.y = [_messageView frame].origin.y - 120; imagesFrame.origin.y = [_messageView frame].origin.y - 120;
imagesFrame.size.height = 120; imagesFrame.size.height = 120;
[_imagesView setFrame:imagesFrame]; [_imagesView setFrame:imagesFrame];
// resizing chatTable // resizing chatTable
CGRect tableViewFrame = [_tableController.tableView frame]; CGRect tableViewFrame = [_tableController.tableView frame];
tableViewFrame.size.height -= 120; tableViewFrame.size.height -= 120;
[_tableController.tableView setFrame:tableViewFrame]; [_tableController.tableView setFrame:tableViewFrame];
[self updateFramesInclRecordingAndReplyView]; [self updateFramesInclRecordingAndReplyView];
} } completion:nil];
completion:nil]; }
}
[self configureForRoom:self.editing]; [self configureForRoom:self.editing];
// Resize the popup table depending on wether ephemeral messages are enabled or not. // Resize the popup table depending on wether ephemeral messages are enabled or not.
@ -309,6 +315,8 @@ static UICompositeViewDescription *compositeDescription = nil;
[NSNotificationCenter.defaultCenter removeObserver:self]; [NSNotificationCenter.defaultCenter removeObserver:self];
PhoneMainView.instance.currentRoom = NULL; PhoneMainView.instance.currentRoom = NULL;
[[UIApplication sharedApplication] setIdleTimerDisabled:false]; [[UIApplication sharedApplication] setIdleTimerDisabled:false];
_chatRoom = NULL;
_tableController.chatRoom = nil;
} }
- (void)removeCallBacks { - (void)removeCallBacks {
@ -325,27 +333,33 @@ static UICompositeViewDescription *compositeDescription = nil;
return; return;
} }
composingVisible = !composingVisible; composingVisible = !composingVisible;
[self setComposingVisible:!composingVisible withDelay:0];
// force offset recomputing // force offset recomputing
[_messageField refreshHeight]; [_messageField refreshHeight];
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress); LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
if (peerAddr) { LinphoneAddress *localAddr = linphone_core_create_address([LinphoneManager getLc], _localAddress);
_chatRoom = linphone_core_get_chat_room([LinphoneManager getLc], peerAddr); if (peerAddr && localAddr) {
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne; _chatRoom = linphone_core_search_chat_room([LinphoneManager getLc], NULL, localAddr, peerAddr, NULL);
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted; if (_chatRoom) {
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne;
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted;
[self setComposingVisible:!composingVisible withDelay:0];
[self configureForRoom:true];
[_tableController scrollToBottom:true];
}
} }
[self configureForRoom:true]; if (peerAddr) linphone_address_unref(peerAddr);
_backButton.hidden = _tableController.isEditing; if (localAddr) linphone_address_unref(localAddr);
[_tableController scrollToBottom:true];
[self refreshImageDrawer];
[self stopAllPlays];
_backButton.hidden = _tableController.isEditing;
[self refreshImageDrawer];
[self stopAllPlays];
[self keyboardWillHide:nil];
} }
#pragma mark - #pragma mark -
- (void)applicationWillEnterBackground{ - (void)applicationDidEnterBackground{
if (!_preservePendingActions) if (!_preservePendingActions)
[self cancelVoiceRecording]; [self cancelVoiceRecording];
else if (_isVoiceRecording) else if (_isVoiceRecording)
@ -353,9 +367,18 @@ static UICompositeViewDescription *compositeDescription = nil;
if (!_preservePendingActions) if (!_preservePendingActions)
[self closePendingReply]; [self closePendingReply];
[self stopAllPlays]; [self stopAllPlays];
_chatRoom = nil;
[_messageField resignFirstResponder];
} }
- (void)applicationWillEnterForeground{
if (_chatRoom == nil) {
if (linphone_core_get_calls_nb(LC) == 0)
[SVProgressHUD show];
else
[self onLinphoneCoreReady:nil];
}
}
- (void)configureForRoom:(BOOL)editing { - (void)configureForRoom:(BOOL)editing {
if (!_chatRoom) { if (!_chatRoom) {
@ -375,7 +398,7 @@ static UICompositeViewDescription *compositeDescription = nil;
linphone_chat_room_cbs_set_is_composing_received(_chatRoomCbs, on_chat_room_is_composing_received); linphone_chat_room_cbs_set_is_composing_received(_chatRoomCbs, on_chat_room_is_composing_received);
linphone_chat_room_cbs_set_conference_joined(_chatRoomCbs, on_chat_room_conference_joined); linphone_chat_room_cbs_set_conference_joined(_chatRoomCbs, on_chat_room_conference_joined);
linphone_chat_room_cbs_set_conference_left(_chatRoomCbs, on_chat_room_conference_left); linphone_chat_room_cbs_set_conference_left(_chatRoomCbs, on_chat_room_conference_left);
linphone_chat_room_cbs_set_security_event(_chatRoomCbs, on_chat_room_conference_alert); linphone_chat_room_cbs_set_security_event(_chatRoomCbs, on_chat_room_conference_alert);
linphone_chat_room_cbs_set_user_data(_chatRoomCbs, (__bridge void*)self); linphone_chat_room_cbs_set_user_data(_chatRoomCbs, (__bridge void*)self);
linphone_chat_room_add_callbacks(_chatRoom, _chatRoomCbs); linphone_chat_room_add_callbacks(_chatRoom, _chatRoomCbs);
} }
@ -397,6 +420,7 @@ static UICompositeViewDescription *compositeDescription = nil;
LinphoneParticipant *firstParticipant = participants ? (LinphoneParticipant *)participants->data : NULL; LinphoneParticipant *firstParticipant = participants ? (LinphoneParticipant *)participants->data : NULL;
const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(_chatRoom); const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(_chatRoom);
[ContactDisplay setDisplayNameLabel:_addressLabel forAddress:addr]; [ContactDisplay setDisplayNameLabel:_addressLabel forAddress:addr];
bctbx_list_free(participants);
} else } else
_addressLabel.text = [NSString stringWithUTF8String:linphone_chat_room_get_subject(_chatRoom) ?: LINPHONE_DUMMY_SUBJECT]; _addressLabel.text = [NSString stringWithUTF8String:linphone_chat_room_get_subject(_chatRoom) ?: LINPHONE_DUMMY_SUBJECT];
@ -428,7 +452,7 @@ static UICompositeViewDescription *compositeDescription = nil;
if (!room) if (!room)
return true; return true;
LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(room); LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(room);
return capabilities & LinphoneChatRoomCapabilitiesBasic; return capabilities & LinphoneChatRoomCapabilitiesEncrypted;
} }
@ -485,23 +509,26 @@ static UICompositeViewDescription *compositeDescription = nil;
// reload the chatroom after the core starts // reload the chatroom after the core starts
- (void)onLinphoneCoreReady:(NSNotification *)notif { - (void)onLinphoneCoreReady:(NSNotification *)notif {
if ((LinphoneGlobalState)[[[notif userInfo] valueForKey:@"state"] integerValue] == LinphoneGlobalOn) { if (linphone_core_get_global_state(LC) == LinphoneGlobalOn) {
LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress); LinphoneAddress *peerAddr = linphone_core_create_address([LinphoneManager getLc], _peerAddress);
if (peerAddr) { LinphoneAddress *localAddr = linphone_core_create_address([LinphoneManager getLc], _localAddress);
_chatRoom = linphone_core_get_chat_room([LinphoneManager getLc], peerAddr); if (peerAddr && localAddr) {
isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne; _chatRoom = linphone_core_search_chat_room([LinphoneManager getLc], NULL, localAddr, peerAddr, NULL);
isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted; if (_chatRoom) {
} isOneToOne = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesOneToOne;
[self configureForRoom:self.editing]; isEncrypted = linphone_chat_room_get_capabilities(_chatRoom) & LinphoneChatRoomCapabilitiesEncrypted;
if (_chatRoom && _markAsRead) { [self configureForRoom:self.editing];
if (IPAD) { if (_chatRoom && _markAsRead) {
[VIEW(ChatsListView).tableController loadData]; if (IPAD) {
[VIEW(ChatsListView).tableController loadData];
}
[ChatConversationView markAsRead:_chatRoom];
}
_markAsRead = TRUE;
} }
}
[ChatConversationView markAsRead:_chatRoom]; [SVProgressHUD dismiss];
} }
_markAsRead = TRUE;
}
} }
- (void)callUpdateEvent:(NSNotification *)notif { - (void)callUpdateEvent:(NSNotification *)notif {
@ -600,9 +627,9 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (void)confirmShare:(NSData *)data url:(NSString *)url fileName:(NSString *)fileName { - (void)confirmShare:(NSData *)data url:(NSString *)url fileName:(NSString *)fileName {
DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:@""]; DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select or create a conversation to share the file(s)", nil)];
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[sheet addButtonWithTitle:NSLocalizedString(@"send to this conversation", nil) [sheet addButtonWithTitle:NSLocalizedString(@"Send to this conversation", nil)
block:^() { block:^() {
if (![[self.messageField text] isEqualToString:@""]) { if (![[self.messageField text] isEqualToString:@""]) {
[self sendMessageInMessageField:linphone_chat_room_create_empty_message(_chatRoom)]; [self sendMessageInMessageField:linphone_chat_room_create_empty_message(_chatRoom)];
@ -659,6 +686,11 @@ static UICompositeViewDescription *compositeDescription = nil;
completion:^(BOOL finished) { completion:^(BOOL finished) {
_composeIndicatorView.hidden = !visible; _composeIndicatorView.hidden = !visible;
}]; }];
if (visible) {
if (_tableController.tableView.contentOffset.y + newComposingFrame.size.height >= (_tableController.tableView.contentSize.height - _tableController.tableView.frame.size.height - 1)) {
[_tableController scrollToBottom:TRUE];
}
}
} }
- (BOOL) groupCallAvailable { - (BOOL) groupCallAvailable {
@ -773,9 +805,9 @@ static UICompositeViewDescription *compositeDescription = nil;
// if we're showing the compose message, update it position // if we're showing the compose message, update it position
if (![_composeLabel isHidden]) { if (![_composeLabel isHidden]) {
CGRect frame = [_composeLabel frame]; CGRect frame = [_composeIndicatorView frame];
frame.origin.y -= diff; frame.origin.y -= diff;
[_composeLabel setFrame:frame]; [_composeIndicatorView setFrame:frame];
} }
} }
} }
@ -783,7 +815,15 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions #pragma mark - Action Functions
- (IBAction)onBackClick:(id)event { - (IBAction)onBackClick:(id)event {
[PhoneMainView.instance popCurrentView]; NSString *previousViewName = [PhoneMainView.instance getPreviousViewName];
_sharingMedia = nil;
if ([previousViewName isEqualToString:@"ContactDetailsView"]) {
ContactDetailsView *view = VIEW(ContactDetailsView);
[PhoneMainView.instance popToView:view.compositeViewDescription];
} else {
ChatsListView *view = VIEW(ChatsListView);
[PhoneMainView.instance popToView:view.compositeViewDescription];
}
} }
- (IBAction)onEditClick:(id)event { - (IBAction)onEditClick:(id)event {
@ -939,6 +979,7 @@ static UICompositeViewDescription *compositeDescription = nil;
view.oldSubject = [NSString stringWithUTF8String:linphone_chat_room_get_subject(_chatRoom) ?: LINPHONE_DUMMY_SUBJECT]; view.oldSubject = [NSString stringWithUTF8String:linphone_chat_room_get_subject(_chatRoom) ?: LINPHONE_DUMMY_SUBJECT];
view.room = _chatRoom; view.room = _chatRoom;
view.peerAddress = _peerAddress; view.peerAddress = _peerAddress;
view.localAddress = _localAddress;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
} }
@ -1051,84 +1092,86 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
+ (void)writeMediaToGallery:(NSString *)name fileType:(NSString *)fileType { + (void)writeMediaToGallery:(NSString *)name fileType:(NSString *)fileType {
NSString *filePath = [LinphoneManager validFilePath:name]; if (![LinphoneManager.instance lpConfigBoolForKey:@"vfs_enabled_mode"]) {
NSFileManager *fileManager = [NSFileManager defaultManager]; NSString *filePath = [LinphoneManager validFilePath:name];
if ([fileManager fileExistsAtPath:filePath]) { NSFileManager *fileManager = [NSFileManager defaultManager];
NSData* data = [NSData dataWithContentsOfFile:filePath]; if ([fileManager fileExistsAtPath:filePath]) {
NSData* data = [NSData dataWithContentsOfFile:filePath];
// define a block , not called immediately. To avoid crash when saving photo before PHAuthorizationStatusNotDetermined. // define a block , not called immediately. To avoid crash when saving photo before PHAuthorizationStatusNotDetermined.
void (^block)(void)= ^ { void (^block)(void)= ^ {
if ([fileType isEqualToString:@"image"] ) { if ([fileType isEqualToString:@"image"] ) {
// we're finished, save the image and update the message // we're finished, save the image and update the message
UIImage *image = [UIImage imageWithData:data]; UIImage *image = [UIImage imageWithData:data];
if (!image) { if (!image) {
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationView *view = VIEW(ChatConversationView);
[view showFileDownloadError]; [view showFileDownloadError];
return; return;
}
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]);
}
});
}];
} else if([fileType isEqualToString:@"video"]) {
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:filePath]];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save video data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write video to photo library", nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"video saved to [%@]", [placeHolder localIdentifier]);
}
});
}];
} }
__block PHObjectPlaceholder *placeHolder; };
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromImage:image]; // When you save an image or video to a photo library, make sure that it is allowed. Otherwise, there will be a backup error.
placeHolder = [request placeholderForCreatedAsset]; if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
} completionHandler:^(BOOL success, NSError *error) { block();
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (error) { if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
LOGE(@"Cannot save image data downloaded [%@]", [error localizedDescription]); block();
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write image to photo library",nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else { } else {
LOGI(@"Image saved to [%@]", [placeHolder localIdentifier]); [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
});
}];
} else if([fileType isEqualToString:@"video"]) {
__block PHObjectPlaceholder *placeHolder;
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
PHAssetCreationRequest *request = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:filePath]];
placeHolder = [request placeholderForCreatedAsset];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
LOGE(@"Cannot save video data downloaded [%@]", [error localizedDescription]);
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Transfer error", nil)
message:NSLocalizedString(@"Cannot write video to photo library", nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
} else {
LOGI(@"video saved to [%@]", [placeHolder localIdentifier]);
} }
}); });
}]; }];
} }
};
// When you save an image or video to a photo library, make sure that it is allowed. Otherwise, there will be a backup error.
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block();
} else {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
block();
} else {
[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
}
});
}];
} }
} }
} }
@ -1417,12 +1460,11 @@ void on_chat_room_chat_message_received(LinphoneChatRoom *cr, const LinphoneEven
bool isDisplayingBottomOfTable = [view.tableController.tableView indexPathsForVisibleRows].lastObject.row == [view.tableController totalNumberOfItems] - 1; bool isDisplayingBottomOfTable = [view.tableController.tableView indexPathsForVisibleRows].lastObject.row == [view.tableController totalNumberOfItems] - 1;
[view.tableController addEventEntry:(LinphoneEventLog *)event_log]; [view.tableController addEventEntry:(LinphoneEventLog *)event_log];
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:view];
if (isDisplayingBottomOfTable) { if (isDisplayingBottomOfTable) {
[view.tableController scrollToBottom:TRUE]; [view.tableController scrollToBottom:TRUE];
} else { } else {
[[view.tableController scrollBadge] setHidden:FALSE];
int unread_msg = linphone_chat_room_get_unread_messages_count(cr); int unread_msg = linphone_chat_room_get_unread_messages_count(cr);
[[view.tableController scrollBadge] setText:[NSString stringWithFormat:@"%d", unread_msg]]; [[view.tableController scrollBadge] setText:[NSString stringWithFormat:@"%d", unread_msg]];
} }
@ -1723,13 +1765,6 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
} }
} }
-(BOOL) isConversationMuted {
return FALSE; // TODO
}
-(void) toggleMuteConversation {
// TODO
}
-(void) showAddressAndIdentityPopup { -(void) showAddressAndIdentityPopup {
char *localAddress = linphone_address_as_string(linphone_chat_room_get_local_address(_chatRoom)); char *localAddress = linphone_address_as_string(linphone_chat_room_get_local_address(_chatRoom));
@ -1756,8 +1791,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
} }
-(BOOL) canAdminEphemeral:(const LinphoneChatRoom *)cr { -(BOOL) canAdminEphemeral:(const LinphoneChatRoom *)cr {
if (!cr) return FALSE; if (!cr || !isEncrypted) return FALSE;
if ([ChatConversationView isBasicChatRoom:_chatRoom]) return FALSE;
// If ephemeral mode is DeviceManaged, then we don't need to check anything else // If ephemeral mode is DeviceManaged, then we don't need to check anything else
return (linphone_chat_room_params_get_ephemeral_mode(linphone_chat_room_get_current_params(cr)) == LinphoneChatRoomEphemeralModeDeviceManaged) return (linphone_chat_room_params_get_ephemeral_mode(linphone_chat_room_get_current_params(cr)) == LinphoneChatRoomEphemeralModeDeviceManaged)
@ -1775,12 +1809,12 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
_popupMenu.layer.masksToBounds = false; _popupMenu.layer.masksToBounds = false;
_toggleMenuButton.hidden = false; _toggleMenuButton.hidden = false;
_popupMenu.tableFooterView = [UIView new]; _popupMenu.tableFooterView = [UIView new];
_popupMenu.separatorStyle = UITableViewCellSeparatorStyleNone;
[_popupMenu reloadData]; [_popupMenu reloadData];
} }
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self onToggleMenu:nil]; [self onToggleMenu:nil];
BOOL isEncrypted = ![ChatConversationView isBasicChatRoom:_chatRoom];
int firstIndex = isOneToOne ? 0 : 1; int firstIndex = isOneToOne ? 0 : 1;
@ -1791,7 +1825,14 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
if (indexPath.row == firstIndex) { if (indexPath.row == firstIndex) {
if (isOneToOne) { if (isOneToOne) {
[self addOrGoToContact:linphone_chat_room_get_peer_address(_chatRoom)]; if (isEncrypted) {
LinphoneAddress* contactAddress = linphone_address_clone(linphone_participant_get_address(bctbx_list_nth_data(linphone_chat_room_get_participants(_chatRoom), 0)));
linphone_address_clean(contactAddress);
[self addOrGoToContact:contactAddress];
linphone_address_unref(contactAddress);
} else {
[self addOrGoToContact:linphone_chat_room_get_peer_address(_chatRoom)];
}
} else { } else {
[self displayGroupInfo]; [self displayGroupInfo];
} }
@ -1808,7 +1849,8 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} }
if ((!isEncrypted && indexPath.row == 1+firstIndex) || (isEncrypted && indexPath.row == 3+firstIndex)) { if ((!isEncrypted && indexPath.row == 1+firstIndex) || (isEncrypted && indexPath.row == 3+firstIndex)) {
[self toggleMuteConversation]; [LinphoneManager setChatroomPushEnabled:_chatRoom withPushEnabled:![LinphoneManager getChatroomPushEnabled:_chatRoom]];
[_popupMenu reloadData];
} }
if ((!isEncrypted && indexPath.row == 2+firstIndex) || (isEncrypted && indexPath.row == 4+firstIndex)) { if ((!isEncrypted && indexPath.row == 2+firstIndex) || (isEncrypted && indexPath.row == 4+firstIndex)) {
@ -1849,31 +1891,44 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] init]; UITableViewCell *cell = [[UITableViewCell alloc] init];
if (!_chatRoom) {
// Workaround to avoid crash in background for release 4.7. This shouldn't happen though, so there may be a deeper issue not found yet
return cell;
}
int firstIndex = isOneToOne ? 0 : 1; int firstIndex = isOneToOne ? 0 : 1;
if (!isOneToOne && indexPath.row == 0) { if (!isOneToOne && indexPath.row == 0) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_voip_meeting_schedule"] newSize:CGSizeMake(25, 25)]; cell.imageView.image = [UIImage imageNamed:@"menu_voip_meeting_schedule"];
cell.textLabel.text = NSLocalizedString(@"Schedule a meeting",nil); cell.textLabel.text = NSLocalizedString(@"Schedule a meeting",nil);
} }
if (indexPath.row == firstIndex) { if (indexPath.row == firstIndex) {
if (isOneToOne) { if (isOneToOne) {
Contact *contact = [FastAddressBook getContactWithAddress:linphone_chat_room_get_peer_address(_chatRoom)]; Contact *contact;
if (isEncrypted) {
LinphoneAddress * contactAddress = linphone_address_clone(linphone_participant_get_address(bctbx_list_nth_data(linphone_chat_room_get_participants(_chatRoom), 0)));
linphone_address_clean(contactAddress);
contact = [FastAddressBook getContactWithAddress:contactAddress];
linphone_address_unref(contactAddress);
} else {
contact = [FastAddressBook getContactWithAddress:linphone_chat_room_get_peer_address(_chatRoom)];
}
if (contact == nil) { if (contact == nil) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"contact_add_default.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [UIImage imageNamed:@"contact_add_default.png"];
cell.textLabel.text = NSLocalizedString(@"Add to contacts",nil); cell.textLabel.text = NSLocalizedString(@"Add to contacts",nil);
} else { } else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"contacts_all_default.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [UIImage imageNamed:@"contacts_all_default.png"];
cell.textLabel.text = NSLocalizedString(@"Go to contact",nil); cell.textLabel.text = NSLocalizedString(@"Go to contact",nil);
} }
} else { } else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(25, 25)]; cell.imageView.image = [UIImage imageNamed:@"chat_group_informations.png"];
cell.textLabel.text = NSLocalizedString(@"Group infos",nil); cell.textLabel.text = NSLocalizedString(@"Group infos",nil);
} }
} }
if (isEncrypted && indexPath.row == 1+firstIndex) { if (isEncrypted && indexPath.row == 1+firstIndex) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_security_default.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [UIImage imageNamed:@"menu_security_default.png"];
cell.textLabel.text = NSLocalizedString(@"Conversation's devices",nil); cell.textLabel.text = NSLocalizedString(@"Conversation's devices",nil);
} }
@ -1884,27 +1939,32 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
} }
if ((isEncrypted && indexPath.row == 3+firstIndex) || (!isEncrypted && indexPath.row == 1+firstIndex)) { if ((isEncrypted && indexPath.row == 3+firstIndex) || (!isEncrypted && indexPath.row == 1+firstIndex)) {
if ([self isConversationMuted]) { if ([LinphoneManager getChatroomPushEnabled:_chatRoom]) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_notifications_off.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [UIImage imageNamed:@"menu_notifications_off.png"];
cell.textLabel.text = NSLocalizedString(@"NOT IMPLEMENTED",nil); cell.textLabel.text = NSLocalizedString(@"Mute notifications",nil);
} else { } else {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"menu_notifications_on.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [UIImage imageNamed:@"menu_notifications_on.png"];
cell.textLabel.text = NSLocalizedString(@"NOT IMPLEMENTED",nil); cell.textLabel.text = NSLocalizedString(@"Un-mute notifications",nil);
} }
} }
if ((isEncrypted && indexPath.row == 4+firstIndex) || (!isEncrypted && indexPath.row == 2+firstIndex)) { if ((isEncrypted && indexPath.row == 4+firstIndex) || (!isEncrypted && indexPath.row == 2+firstIndex)) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"delete_default.png"] newSize:CGSizeMake(20, 25)]; cell.imageView.image = [UIImage imageNamed:@"delete_default.png"];
cell.textLabel.text = NSLocalizedString(@"Delete messages",nil); cell.textLabel.text = NSLocalizedString(@"Delete messages",nil);
} }
if ((isEncrypted && ((!canEphemeral && indexPath.row == 4+firstIndex)||(canEphemeral && indexPath.row == 5+firstIndex))) if ((isEncrypted && ((!canEphemeral && indexPath.row == 4+firstIndex)||(canEphemeral && indexPath.row == 5+firstIndex)))
|| (!isEncrypted && indexPath.row == 3+firstIndex)) { || (!isEncrypted && indexPath.row == 3+firstIndex)) {
cell.imageView.image = [LinphoneUtils resizeImage:[UIImage imageNamed:@"chat_group_informations.png"] newSize:CGSizeMake(25, 25)]; cell.imageView.image = [UIImage imageNamed:@"chat_group_informations.png"];
cell.textLabel.text = NSLocalizedString(@"Show address and identity",nil); cell.textLabel.text = NSLocalizedString(@"Debug infos",nil);
} }
cell.imageView.contentMode = UIViewContentModeScaleAspectFit; UIImageView * icon = [[UIImageView alloc] initWithFrame:CGRectMake(tableView.frame.size.width-37, 7, 30, 30)];
icon.contentMode = UIViewContentModeScaleAspectFit;
icon.image = cell.imageView.image;
[cell.contentView addSubview:icon];
cell.imageView.image = nil;
return cell; return cell;
} }
- (IBAction)onToggleMenu:(id)sender { - (IBAction)onToggleMenu:(id)sender {
@ -2213,6 +2273,7 @@ void on_shared_player_eof_reached(LinphonePlayer *p) {
_showReplyView = true; _showReplyView = true;
[self updateFramesInclRecordingAndReplyView]; [self updateFramesInclRecordingAndReplyView];
[self.tableController scrollToMessage:message]; [self.tableController scrollToMessage:message];
[self.messageField becomeFirstResponder];
} }
-(void) handlePendingTransferIfAny { -(void) handlePendingTransferIfAny {

View file

@ -31,4 +31,5 @@
- (void)loadData; - (void)loadData;
- (void)markCellAsRead:(LinphoneChatRoom *)chatRoom; - (void)markCellAsRead:(LinphoneChatRoom *)chatRoom;
- (void)updateEventEntry:(LinphoneChatMessage *)msg;
@end @end

View file

@ -20,7 +20,7 @@
#import "ChatsListTableView.h" #import "ChatsListTableView.h"
#import "UIChatCell.h" #import "UIChatCell.h"
#import "FileTransferDelegate.h" #import "FileTransferDelegate.h"
#import "linphoneapp-Swift.h"
#import "linphone/linphonecore.h" #import "linphone/linphonecore.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "Utils.h" #import "Utils.h"
@ -41,6 +41,11 @@
return self; return self;
} }
- (void)dealloc {
bctbx_list_free(_data);
}
#pragma mark - ViewController Functions #pragma mark - ViewController Functions
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
@ -48,15 +53,53 @@
self.tableView.accessibilityIdentifier = @"Chat list"; self.tableView.accessibilityIdentifier = @"Chat list";
[self loadData]; [self loadData];
_chatRooms = NULL; _chatRooms = NULL;
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
NSDictionary* userInfo = notification.userInfo;
NSString* friend = (NSString*)userInfo[@"friend"];
for (int i = 0; i < bctbx_list_size(_data); i++)
{
LinphoneChatRoom *chatRoom = (LinphoneChatRoom *)bctbx_list_nth_data(_data, i);
bctbx_list_t *participants = linphone_chat_room_get_participants(chatRoom);
if (linphone_chat_room_get_nb_participants(chatRoom) == 1) {
LinphoneParticipant *firstParticipant = participants ? (LinphoneParticipant *)participants->data : NULL;
char *curi = linphone_address_as_string_uri_only(linphone_participant_get_address(firstParticipant));
NSString *uri = [NSString stringWithUTF8String:curi];
LinphoneChatRoomCapabilitiesMask capabilities = linphone_chat_room_get_capabilities(chatRoom);
bool oneToOne = capabilities & LinphoneChatRoomCapabilitiesOneToOne;
if(oneToOne && [uri isEqual:friend]){
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:0];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}
}
}
}
} }
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]; [super viewDidAppear:animated];
// we cannot do that in viewWillAppear because we will change view while previous transition // we cannot do that in viewWillAppear because we will change view while previous transition
// was not finished, leading to "[CALayer retain]: message sent to deallocated instance" error msg // was not finished, leading to "[CALayer retain]: message sent to deallocated instance" error msg
/*
if (IPAD && [self totalNumberOfItems] > 0) { if (IPAD && [self totalNumberOfItems] > 0) {
[PhoneMainView.instance changeCurrentView:ChatConversationView.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:ChatConversationView.compositeViewDescription];
} }
*/
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
@ -72,6 +115,9 @@
} }
_chatRooms = _chatRooms->next; _chatRooms = _chatRooms->next;
} }
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
[AvatarBridge removeAllObserver];
} }
- (void)layoutSubviews { - (void)layoutSubviews {
@ -114,6 +160,7 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
_data = [self sortChatRooms]; _data = [self sortChatRooms];
[super loadData]; [super loadData];
/*
if (IPAD) { if (IPAD) {
int idx = bctbx_list_index(_data, VIEW(ChatConversationView).chatRoom); int idx = bctbx_list_index(_data, VIEW(ChatConversationView).chatRoom);
// if conversation view is using a chatroom that does not exist anymore, update it // if conversation view is using a chatroom that does not exist anymore, update it
@ -131,15 +178,28 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
} }
} }
*/
}
- (void)updateEventEntry:(LinphoneChatMessage *)msg {
int idx = bctbx_list_index(_data, linphone_chat_message_get_chat_room(msg));
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
if (idx < 0) {
LOGW(@"event entry doesn't exist");
return;
}
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:FALSE];
} }
- (void)markCellAsRead:(LinphoneChatRoom *)chatRoom { - (void)markCellAsRead:(LinphoneChatRoom *)chatRoom {
int idx = bctbx_list_index(_data, VIEW(ChatConversationView).chatRoom); int idx = bctbx_list_index(_data, VIEW(ChatConversationView).chatRoom);
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:0];
/*
if (IPAD) { if (IPAD) {
UIChatCell *cell = (UIChatCell *)[self.tableView cellForRowAtIndexPath:indexPath]; UIChatCell *cell = (UIChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
[cell updateUnreadBadge]; [cell updateUnreadBadge];
} }
*/
} }
#pragma mark - UITableViewDataSource Functions #pragma mark - UITableViewDataSource Functions
@ -164,7 +224,7 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
[cell setChatRoom:(LinphoneChatRoom *)bctbx_list_nth_data(_data, (int)[indexPath row])]; [cell setChatRoom:(LinphoneChatRoom *)bctbx_list_nth_data(_data, (int)[indexPath row])];
[super accessoryForCell:cell atPath:indexPath]; [super accessoryForCell:cell atPath:indexPath];
BOOL forwardMode = VIEW(ChatConversationView).pendingForwardMessage != nil; BOOL forwardMode = VIEW(ChatConversationViewSwift).pendingForwardMessage != nil;
cell.forwardIcon.hidden = !forwardMode; cell.forwardIcon.hidden = !forwardMode;
if (forwardMode) { if (forwardMode) {
cell.ephemeral.hidden = true; cell.ephemeral.hidden = true;
@ -186,12 +246,12 @@ static int sorted_history_comparison(LinphoneChatRoom *to_insert, LinphoneChatRo
return; return;
LinphoneChatRoom *chatRoom = (LinphoneChatRoom *)bctbx_list_nth_data(_data, (int)[indexPath row]); LinphoneChatRoom *chatRoom = (LinphoneChatRoom *)bctbx_list_nth_data(_data, (int)[indexPath row]);
[PhoneMainView.instance goToChatRoom:chatRoom]; [PhoneMainView.instance goToChatRoomSwift:chatRoom];
} }
void deletion_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) { void deletion_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) {
LinphoneChatRoomCbs *cbs = linphone_chat_room_get_current_callbacks(cr); LinphoneChatRoomCbs *cbs = linphone_chat_room_get_current_callbacks(cr);
ChatsListTableView *view = (__bridge ChatsListTableView *)linphone_chat_room_cbs_get_user_data(cbs) ?: NULL; ChatsListTableView *view =cbs ? ((__bridge ChatsListTableView *)linphone_chat_room_cbs_get_user_data(cbs) ?: NULL) : NULL;
if (!view) if (!view)
return; return;
@ -232,7 +292,9 @@ void deletion_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomStat
} }
} }
[ftdToDelete cancel]; [ftdToDelete cancel];
// Re-enable push notification after deleting the chatroom, in order to get the notification if we are re-invited, or for secure 1-to-1 chatrooms.
[LinphoneManager setChatroomPushEnabled:chatRoom withPushEnabled:TRUE];
linphone_core_delete_chat_room(LC, chatRoom); linphone_core_delete_chat_room(LC, chatRoom);
chatRooms = chatRooms->next; chatRooms = chatRooms->next;
} }

View file

@ -40,7 +40,10 @@
- (IBAction)onAddGroupChatClick:(id)event; - (IBAction)onAddGroupChatClick:(id)event;
- (IBAction)onAddClick:(id)event; - (IBAction)onAddClick:(id)event;
- (IBAction)onChatRoomSwiftClick:(id)event;
- (IBAction)onEditionChangeClick:(id)sender; - (IBAction)onEditionChangeClick:(id)sender;
- (IBAction)onDeleteClick:(id)sender; - (IBAction)onDeleteClick:(id)sender;
-(void) mediaSharing;
@end @end

View file

@ -21,6 +21,7 @@
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "ChatConversationCreateView.h" #import "ChatConversationCreateView.h"
#import "linphoneapp-Swift.h"
@implementation ChatsListView @implementation ChatsListView
#pragma mark - ViewController Functions #pragma mark - ViewController Functions
@ -39,6 +40,10 @@
selector:@selector(ephemeralDeleted:) selector:@selector(ephemeralDeleted:)
name:kLinphoneEphemeralMessageDeletedInRoom name:kLinphoneEphemeralMessageDeletedInRoom
object:nil]; object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
[_backToCallButton update]; [_backToCallButton update];
self.tableController.waitView = _waitView; self.tableController.waitView = _waitView;
[self setEditing:NO]; [self setEditing:NO];
@ -54,20 +59,43 @@
forControlEvents:UIControlEventTouchUpInside]; forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];*/ [self.view addSubview:button];*/
BOOL forwardMode = VIEW(ChatConversationView).pendingForwardMessage != nil; [self mediaSharing];
}
- (void)mediaSharing{
BOOL forwardMode;
NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName];
NSDictionary *dict = [defaults valueForKey:@"photoData"];
NSDictionary *dictFile = [defaults valueForKey:@"icloudData"];
NSDictionary *dictUrl = [defaults valueForKey:@"url"];
if(dict||dictFile||dictUrl) VIEW(ChatConversationViewSwift).sharingMedia = TRUE;
if(VIEW(ChatConversationViewSwift).sharingMedia == false){
forwardMode = VIEW(ChatConversationViewSwift).pendingForwardMessage != nil;
}else{
forwardMode = VIEW(ChatConversationViewSwift).sharingMedia != false;
}
_tableController.editButton.hidden = forwardMode; _tableController.editButton.hidden = forwardMode;
_forwardTitle.text = NSLocalizedString(@"Select a discussion or create a new one",nil); if(VIEW(ChatConversationViewSwift).sharingMedia == false){
_forwardTitle.text = NSLocalizedString(@"Select a discussion or create a new one",nil);
}
else{
_forwardTitle.text = NSLocalizedString(@"Select or create a conversation to share the file(s)",nil);
}
_forwardTitle.hidden = !forwardMode; _forwardTitle.hidden = !forwardMode;
_cancelForwardButton.hidden = !forwardMode; _cancelForwardButton.hidden = !forwardMode;
_tableController.tableView.frame = CGRectMake(0, 66 + (forwardMode ? _forwardTitle.frame.size.height : 0), _tableController.tableView.frame.size.width, self.view.frame.size.height - 66 - ( forwardMode ? _forwardTitle.frame.size.height : 0 )); _tableController.tableView.frame = CGRectMake(0, 66 + (forwardMode ? _forwardTitle.frame.size.height : 0), _tableController.tableView.frame.size.width, self.view.frame.size.height - 66 - ( forwardMode ? _forwardTitle.frame.size.height : 0 ));
_addButton.frame = CGRectMake(forwardMode ? 82 : 0 , _addButton.frame.origin.y, _addButton.frame.size.width, _addButton.frame.size.height); _addButton.frame = CGRectMake(forwardMode ? 82 : 0 , _addButton.frame.origin.y, _addButton.frame.size.width, _addButton.frame.size.height);
_addGroupChatButton.frame = CGRectMake(forwardMode ? 164 : 82 , _addGroupChatButton.frame.origin.y, _addGroupChatButton.frame.size.width, _addGroupChatButton.frame.size.height); _addGroupChatButton.frame = CGRectMake(forwardMode ? 164 : 82 , _addGroupChatButton.frame.origin.y, _addGroupChatButton.frame.size.width, _addGroupChatButton.frame.size.height);
} }
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
}
- (void)ephemeralDeleted:(NSNotification *)notif { - (void)ephemeralDeleted:(NSNotification *)notif {
//LinphoneChatRoom *r =[[notif.userInfo objectForKey:@"room"] intValue]; //LinphoneChatRoom *r =[[notif.userInfo objectForKey:@"room"] intValue];
@ -128,14 +156,18 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onAddGroupChatClick:(id)event { - (IBAction)onAddGroupChatClick:(id)event {
[self newChatCreate:TRUE]; [self newChatCreate:TRUE];
if (IPAD) //if (IPAD)
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneChatCreateViewChange object:VIEW(ChatConversationCreateView) userInfo:nil]; //[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneChatCreateViewChange object:VIEW(ChatConversationCreateView) userInfo:nil];
}
- (IBAction)onChatRoomSwiftClick:(id)event {
[PhoneMainView.instance changeCurrentView:ChatConversationViewSwift.compositeViewDescription];
} }
- (IBAction)onAddClick:(id)event { - (IBAction)onAddClick:(id)event {
[self newChatCreate:FALSE]; [self newChatCreate:FALSE];
if (IPAD) //if (IPAD)
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneChatCreateViewChange object:VIEW(ChatConversationCreateView) userInfo:nil]; //[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneChatCreateViewChange object:VIEW(ChatConversationCreateView) userInfo:nil];
} }
- (IBAction)onEditionChangeClick:(id)sender { - (IBAction)onEditionChangeClick:(id)sender {
@ -173,8 +205,16 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (IBAction)onCancelForwardClicked:(id)sender { - (IBAction)onCancelForwardClicked:(id)sender {
VIEW(ChatConversationView).pendingForwardMessage = nil; VIEW(ChatConversationViewSwift).sharingMedia = false;
VIEW(ChatConversationViewSwift).pendingForwardMessage = nil;
NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName];
[defaults removeObjectForKey:@"photoData"];
[defaults removeObjectForKey:@"icloudData"];
[defaults removeObjectForKey:@"url"];
[PhoneMainView.instance popCurrentView]; [PhoneMainView.instance popCurrentView];
} }
@end @end

View file

@ -31,11 +31,12 @@
@property(nonatomic, retain) NSString *identifier; @property(nonatomic, retain) NSString *identifier;
@property(nonatomic, retain) NSString *firstName; @property(nonatomic, retain) NSString *firstName;
@property(nonatomic, retain) NSString *lastName; @property(nonatomic, retain) NSString *lastName;
@property(nonatomic, retain) NSString *organizationName;
@property(nonatomic, retain) NSString *displayName; @property(nonatomic, retain) NSString *displayName;
@property(nonatomic, strong) NSMutableArray *sipAddresses; @property(nonatomic, strong) NSMutableArray *sipAddresses;
@property(nonatomic, strong) NSMutableArray *emails; @property(nonatomic, strong) NSMutableArray *emails;
@property(nonatomic, strong) NSMutableArray *phones; @property(nonatomic, strong) NSMutableArray *phones;
@property BOOL createdFromLdap; @property BOOL createdFromLdapOrProvisioning;
@property BOOL added; @property BOOL added;
- (void)setAvatar:(UIImage *)avatar; - (void)setAvatar:(UIImage *)avatar;
@ -58,4 +59,6 @@
- (BOOL)removeSipAddressAtIndex:(NSInteger)index; - (BOOL)removeSipAddressAtIndex:(NSInteger)index;
- (BOOL)removePhoneNumberAtIndex:(NSInteger)index; - (BOOL)removePhoneNumberAtIndex:(NSInteger)index;
- (BOOL)removeEmailAtIndex:(NSInteger)index; - (BOOL)removeEmailAtIndex:(NSInteger)index;
- (NSMutableArray*)getSipAddressesWithoutDuplicatePhoneNumbers;
@end @end

View file

@ -36,7 +36,7 @@
_person = acncontact; _person = acncontact;
_friend = afriend ? linphone_friend_ref(afriend) : NULL; _friend = afriend ? linphone_friend_ref(afriend) : NULL;
_added = FALSE; _added = FALSE;
_createdFromLdap = FALSE; _createdFromLdapOrProvisioning = FALSE;
_phones = [[NSMutableArray alloc] init]; _phones = [[NSMutableArray alloc] init];
_sipAddresses = [[NSMutableArray alloc] init]; _sipAddresses = [[NSMutableArray alloc] init];
_emails = [[NSMutableArray alloc] init]; _emails = [[NSMutableArray alloc] init];
@ -44,6 +44,7 @@
_identifier = _person.identifier; _identifier = _person.identifier;
_firstName = _person.givenName; _firstName = _person.givenName;
_lastName = _person.familyName; _lastName = _person.familyName;
_organizationName = _person.organizationName;
_displayName = [NSString stringWithFormat:@"%@ %@", _firstName, _lastName]; _displayName = [NSString stringWithFormat:@"%@ %@", _firstName, _lastName];
for (CNLabeledValue<CNPhoneNumber *> *phoneNumber in _person.phoneNumbers) { for (CNLabeledValue<CNPhoneNumber *> *phoneNumber in _person.phoneNumbers) {
[_phones addObject:phoneNumber.value.stringValue]; [_phones addObject:phoneNumber.value.stringValue];
@ -64,13 +65,17 @@
} }
const char *key = [NSString stringWithFormat:@"ab%@", acncontact.identifier].UTF8String; const char *key = [NSString stringWithFormat:@"ab%@", acncontact.identifier].UTF8String;
// try to find friend associated with that person // try to find friend associated with that person
if (_friend){
linphone_friend_unref(_friend);
_friend = nil;
}
_friend = linphone_friend_list_find_friend_by_ref_key(linphone_core_get_default_friend_list(LC), key); _friend = linphone_friend_list_find_friend_by_ref_key(linphone_core_get_default_friend_list(LC), key);
if (!_friend) { if (!_friend) {
_friend = linphone_friend_ref(linphone_core_create_friend(LC)); _friend = linphone_core_create_friend(LC);
linphone_friend_set_ref_key(_friend, key); linphone_friend_set_ref_key(_friend, key);
linphone_friend_set_name(_friend, [NSString stringWithFormat:@"%@%@", _firstName ? _firstName : @"", _lastName ? [_firstName ? @" " : @"" stringByAppendingString:_lastName] : @""] .UTF8String); linphone_friend_set_name(_friend, [NSString stringWithFormat:@"%@%@", _firstName ? _firstName : @"", _lastName ? [_firstName ? @" " : @"" stringByAppendingString:_lastName] : @""] .UTF8String);
for (NSString *sipAddr in _sipAddresses) { for (NSString *sipAddr in _sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sipAddr.UTF8String); LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sipAddr.UTF8String, true);
if (addr) { if (addr) {
linphone_address_set_display_name(addr, [self displayName].UTF8String); linphone_address_set_display_name(addr, [self displayName].UTF8String);
linphone_friend_add_address(_friend, addr); linphone_friend_add_address(_friend, addr);
@ -80,14 +85,15 @@
for (NSString *phone in _phones) { for (NSString *phone in _phones) {
linphone_friend_add_phone_number(_friend, phone.UTF8String); linphone_friend_add_phone_number(_friend, phone.UTF8String);
} }
if (_organizationName) {
linphone_friend_set_organization(_friend, [_organizationName UTF8String]);
}
if (_friend) { if (_friend) {
linphone_friend_enable_subscribes(_friend, FALSE); linphone_friend_enable_subscribes(_friend, FALSE);
linphone_friend_set_inc_subscribe_policy(_friend, LinphoneSPDeny); linphone_friend_set_inc_subscribe_policy(_friend, LinphoneSPDeny);
linphone_core_add_friend(LC, _friend); linphone_core_add_friend(LC, _friend);
} }
} }else linphone_friend_ref(_friend);
linphone_friend_ref(_friend);
} else if (_friend) { } else if (_friend) {
[self loadFriend]; [self loadFriend];
} else { } else {
@ -287,7 +293,7 @@
[_person setValue:tmpSipAddresses forKey:CNContactInstantMessageAddressesKey]; [_person setValue:tmpSipAddresses forKey:CNContactInstantMessageAddressesKey];
ret = TRUE; ret = TRUE;
} else { } else {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sip.UTF8String) ?: linphone_address_new(sip.UTF8String); LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sip.UTF8String, true) ?: linphone_address_new(sip.UTF8String);
if (!addr) if (!addr)
return FALSE; return FALSE;
@ -370,7 +376,7 @@
} }
ret = TRUE; ret = TRUE;
} else { } else {
LinphoneAddress *addr = linphone_core_interpret_url(LC, ((NSString *)_sipAddresses[index]).UTF8String); LinphoneAddress *addr = linphone_core_interpret_url_2(LC, ((NSString *)_sipAddresses[index]).UTF8String, true);
if (!addr) if (!addr)
return FALSE; return FALSE;
@ -461,11 +467,11 @@
// try to find friend associated with that person // try to find friend associated with that person
_friend = linphone_friend_list_find_friend_by_ref_key(linphone_core_get_default_friend_list(LC), key); _friend = linphone_friend_list_find_friend_by_ref_key(linphone_core_get_default_friend_list(LC), key);
if (!_friend) { if (!_friend) {
_friend = linphone_friend_ref(linphone_core_create_friend(LC)); _friend = linphone_core_create_friend(LC);
linphone_friend_set_ref_key(_friend, key); linphone_friend_set_ref_key(_friend, key);
linphone_friend_set_name(_friend, [NSString stringWithFormat:@"%@%@", _firstName ? _firstName : @"", _lastName ? [_firstName ? @" " : @"" stringByAppendingString:_lastName] : @""] .UTF8String); linphone_friend_set_name(_friend, [NSString stringWithFormat:@"%@%@", _firstName ? _firstName : @"", _lastName ? [_firstName ? @" " : @"" stringByAppendingString:_lastName] : @""] .UTF8String);
for (NSString *sipAddr in _sipAddresses) { for (NSString *sipAddr in _sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sipAddr.UTF8String); LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sipAddr.UTF8String, true);
if (addr) { if (addr) {
linphone_address_set_display_name(addr, [self displayName].UTF8String); linphone_address_set_display_name(addr, [self displayName].UTF8String);
linphone_friend_add_address(_friend, addr); linphone_friend_add_address(_friend, addr);
@ -480,12 +486,31 @@
linphone_friend_set_inc_subscribe_policy(_friend, LinphoneSPDeny); linphone_friend_set_inc_subscribe_policy(_friend, LinphoneSPDeny);
linphone_core_add_friend(LC, _friend); linphone_core_add_friend(LC, _friend);
} }
} } else linphone_friend_ref(_friend);
linphone_friend_ref(_friend);
} }
- (void)clearFriend { - (void)clearFriend {
if (_friend) linphone_friend_unref(_friend);
_friend = NULL; _friend = NULL;
} }
- (NSMutableArray*)getSipAddressesWithoutDuplicatePhoneNumbers {
NSMutableArray* resAdresses = [[NSMutableArray alloc] init];
for (NSString *address in _sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url_2(LC, [address UTF8String], YES);
bool isFoundInPhones = false;
if (addr && linphone_address_get_username(addr)) {
for (NSString *phoneNb in _phones) {
if ([phoneNb isEqualToString:[NSString stringWithUTF8String:linphone_address_get_username(addr)]]) {
isFoundInPhones = true;
break;
}
}
}
if (!isFoundInPhones) [resAdresses addObject:address];
}
return resAdresses;
}
@end @end

View file

@ -27,6 +27,7 @@ typedef enum _ContactSections {
ContactSections_None = 0, // first section is empty because we cannot set header for first section ContactSections_None = 0, // first section is empty because we cannot set header for first section
ContactSections_FirstName, ContactSections_FirstName,
ContactSections_LastName, ContactSections_LastName,
ContactSections_Organization,
ContactSections_Sip, ContactSections_Sip,
ContactSections_Number, ContactSections_Number,
ContactSections_Email, ContactSections_Email,

View file

@ -139,7 +139,7 @@
} }
- (BOOL)isValid { - (BOOL)isValid {
BOOL hasName = (_contact.firstName.length + _contact.lastName.length > 0); BOOL hasName = (_contact.firstName.length + _contact.lastName.length + _contact.organizationName.length > 0);
BOOL hasAddr = BOOL hasAddr =
(_contact.phones.count + _contact.sipAddresses.count) > 0; (_contact.phones.count + _contact.sipAddresses.count) > 0;
return hasName && hasAddr; return hasName && hasAddr;
@ -156,19 +156,19 @@
} }
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == ContactSections_FirstName || section == ContactSections_LastName) { if (section == ContactSections_FirstName || section == ContactSections_LastName || section == ContactSections_Organization) {
/*first and last name only when editting */ /*first and last name only when editting */
return (self.tableView.isEditing) ? 1 : 0; return (self.tableView.isEditing) ? 1 : 0;
} else if (section == ContactSections_Sip) { } else if (section == ContactSections_Sip) {
return _contact.createdFromLdap ? 0 : _contact.sipAddresses.count; return [_contact getSipAddressesWithoutDuplicatePhoneNumbers].count;
} else if (section == ContactSections_Number) { } else if (section == ContactSections_Number) {
return _contact.phones.count; return _contact.phones.count;
} else if (section == ContactSections_Email) { } else if (section == ContactSections_Email) {
BOOL showEmails = [LinphoneManager.instance BOOL showEmails = [LinphoneManager.instance
lpConfigBoolForKey:@"show_contacts_emails_preference"]; lpConfigBoolForKey:@"show_contacts_emails_preference"];
return showEmails ? _contact.emails.count : 0; return showEmails ? _contact.emails.count : 0;
} }
return 0; return 0;
} }
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
@ -192,6 +192,9 @@
} else if (indexPath.section == ContactSections_LastName) { } else if (indexPath.section == ContactSections_LastName) {
value = _contact.lastName; value = _contact.lastName;
[cell hideDeleteButton:YES]; [cell hideDeleteButton:YES];
} else if (indexPath.section == ContactSections_Organization) {
value = _contact.organizationName;
[cell hideDeleteButton:YES];
} else if ([indexPath section] == ContactSections_Number) { } else if ([indexPath section] == ContactSections_Number) {
value = _contact.phones[indexPath.row]; value = _contact.phones[indexPath.row];
[cell.editTextfield setKeyboardType:UIKeyboardTypePhonePad]; [cell.editTextfield setKeyboardType:UIKeyboardTypePhonePad];
@ -200,7 +203,7 @@
LinphoneAddress *addr = NULL; LinphoneAddress *addr = NULL;
if ([LinphoneManager.instance if ([LinphoneManager.instance
lpConfigBoolForKey:@"contact_display_username_only"] && lpConfigBoolForKey:@"contact_display_username_only"] &&
(addr = linphone_core_interpret_url(LC, [value UTF8String]))) { (addr = linphone_core_interpret_url_2(LC, [value UTF8String], YES))) {
value = value =
[NSString stringWithCString:linphone_address_get_username(addr) [NSString stringWithCString:linphone_address_get_username(addr)
encoding:[NSString defaultCStringEncoding]]; encoding:[NSString defaultCStringEncoding]];
@ -279,11 +282,14 @@
} else if (section == ContactSections_LastName && self.tableView.isEditing) { } else if (section == ContactSections_LastName && self.tableView.isEditing) {
text = NSLocalizedString(@"Last name", nil); text = NSLocalizedString(@"Last name", nil);
canAddEntry = NO; canAddEntry = NO;
} else if (section == ContactSections_Organization && self.tableView.isEditing) {
text = NSLocalizedString(@"Organization", nil);
canAddEntry = NO;
} else if ([self getSectionData:section].count > 0 || self.tableView.isEditing) { } else if ([self getSectionData:section].count > 0 || self.tableView.isEditing) {
if (section == ContactSections_Number) { if (section == ContactSections_Number) {
text = NSLocalizedString(@"Phone numbers", nil); text = NSLocalizedString(@"Phone numbers", nil);
addEntryName = NSLocalizedString(@"Add new phone number", nil); addEntryName = NSLocalizedString(@"Add new phone number", nil);
} else if (section == ContactSections_Sip && !_contact.createdFromLdap) { } else if (section == ContactSections_Sip && !_contact.createdFromLdapOrProvisioning) {
text = NSLocalizedString(@"SIP addresses", nil); text = NSLocalizedString(@"SIP addresses", nil);
addEntryName = NSLocalizedString(@"Add new SIP address", nil); addEntryName = NSLocalizedString(@"Add new SIP address", nil);
} else if (section == ContactSections_Email && } else if (section == ContactSections_Email &&
@ -358,7 +364,7 @@
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
if (section == 0 || if (section == 0 ||
(!self.tableView.isEditing && (section == ContactSections_FirstName || section == ContactSections_LastName))) { (!self.tableView.isEditing && (section == ContactSections_FirstName || section == ContactSections_LastName || section == ContactSections_LastName))) {
return 1e-5; return 1e-5;
} }
return [self tableView:tableView viewForHeaderInSection:section].frame.size.height; return [self tableView:tableView viewForHeaderInSection:section].frame.size.height;
@ -390,6 +396,9 @@
case ContactSections_LastName: case ContactSections_LastName:
_contact.lastName = value; _contact.lastName = value;
break; break;
case ContactSections_Organization:
_contact.organizationName = value;
break;
case ContactSections_Sip: case ContactSections_Sip:
[_contact setSipAddress:value atIndex:path.row]; [_contact setSipAddress:value atIndex:path.row];
value = _contact.sipAddresses[path.row]; // in case of reformatting value = _contact.sipAddresses[path.row]; // in case of reformatting

View file

@ -35,8 +35,9 @@
@property(nonatomic, strong) IBOutlet UIToggleButton *editButton; @property(nonatomic, strong) IBOutlet UIToggleButton *editButton;
@property(nonatomic, strong) IBOutlet UIButton *backButton; @property(nonatomic, strong) IBOutlet UIButton *backButton;
@property(nonatomic, strong) IBOutlet UIButton *cancelButton; @property(nonatomic, strong) IBOutlet UIButton *cancelButton;
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage; @property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UILabel *nameLabel; @property(weak, nonatomic) IBOutlet UILabel *nameLabel;
@property(weak, nonatomic) IBOutlet UILabel *organizationLabel;
@property(weak, nonatomic) IBOutlet UIToggleButton *deleteButton; @property(weak, nonatomic) IBOutlet UIToggleButton *deleteButton;
@property(weak, nonatomic) IBOutlet UIScrollView *contentView; @property(weak, nonatomic) IBOutlet UIScrollView *contentView;
@property(weak, nonatomic) IBOutlet UILabel *emptyLabel; @property(weak, nonatomic) IBOutlet UILabel *emptyLabel;

View file

@ -20,6 +20,7 @@
#import "ContactDetailsView.h" #import "ContactDetailsView.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "UIContactDetailsCell.h" #import "UIContactDetailsCell.h"
#import "linphoneapp-Swift.h"
@implementation ContactDetailsView @implementation ContactDetailsView
@ -63,7 +64,7 @@
} }
LOGI(@"Reset data to contact %p", _contact); LOGI(@"Reset data to contact %p", _contact);
[_avatarImage setImage:[FastAddressBook imageForContact:_contact] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForContact:_contact]];
[_tableController setContact:_contact]; [_tableController setContact:_contact];
_emptyLabel.hidden = YES; _emptyLabel.hidden = YES;
_avatarImage.hidden = !_emptyLabel.hidden; _avatarImage.hidden = !_emptyLabel.hidden;
@ -91,24 +92,25 @@
[PhoneMainView.instance popCurrentView]; [PhoneMainView.instance popCurrentView];
return; return;
} }
PhoneMainView.instance.currentName = _contact.displayName; PhoneMainView.instance.currentName = _contact.displayName;
_nameLabel.text = PhoneMainView.instance.currentName; _nameLabel.text = PhoneMainView.instance.currentName;
_organizationLabel.text = _contact.organizationName;
// fix no sipaddresses in contact.friend
const MSList *sips = linphone_friend_get_addresses(_contact.friend); // fix no sipaddresses in contact.friend
while (sips) { const MSList *sips = linphone_friend_get_addresses(_contact.friend);
linphone_friend_remove_address(_contact.friend, sips->data); while (sips) {
sips = sips->next; linphone_friend_remove_address(_contact.friend, sips->data);
} sips = sips->next;
}
for (NSString *sipAddr in _contact.sipAddresses) {
LinphoneAddress *addr = linphone_core_interpret_url(LC, sipAddr.UTF8String); for (NSString *sipAddr in _contact.sipAddresses) {
if (addr) { LinphoneAddress *addr = linphone_core_interpret_url_2(LC, sipAddr.UTF8String, true);
linphone_friend_add_address(_contact.friend, addr); if (addr) {
linphone_address_destroy(addr); linphone_friend_add_address(_contact.friend, addr);
} linphone_address_destroy(addr);
} }
[LinphoneManager.instance.fastAddressBook saveContact:_contact]; }
[LinphoneManager.instance.fastAddressBook saveContact:_contact];
} }
- (void)selectContact:(Contact *)acontact andReload:(BOOL)reload { - (void)selectContact:(Contact *)acontact andReload:(BOOL)reload {
@ -119,11 +121,12 @@
_contact = acontact; _contact = acontact;
_emptyLabel.hidden = (_contact != NULL); _emptyLabel.hidden = (_contact != NULL);
_avatarImage.hidden = !_emptyLabel.hidden; _avatarImage.hidden = !_emptyLabel.hidden;
_deleteButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdap]; _deleteButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdapOrProvisioning];
_editButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdap]; _editButton.hidden = !_emptyLabel.hidden || [_contact createdFromLdapOrProvisioning];
[_avatarImage setImage:[FastAddressBook imageForContact:_contact] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForContact:_contact]];
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact]; [ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact];
_organizationLabel.text = _contact.organizationName;
[_tableController setContact:_contact]; [_tableController setContact:_contact];
if (reload) { if (reload) {
@ -146,7 +149,7 @@
} }
- (void)addCurrentContactContactField:(NSString *)address { - (void)addCurrentContactContactField:(NSString *)address {
LinphoneAddress *linphoneAddress = linphone_core_interpret_url(LC, address.UTF8String); LinphoneAddress *linphoneAddress = linphone_core_interpret_url_2(LC, address.UTF8String, true);
NSString *username = NSString *username =
linphoneAddress ? [NSString stringWithUTF8String:linphone_address_get_username(linphoneAddress)] : address; linphoneAddress ? [NSString stringWithUTF8String:linphone_address_get_username(linphoneAddress)] : address;
@ -284,6 +287,9 @@
if (IPAD && self.contact == NULL) { if (IPAD && self.contact == NULL) {
_editButton.hidden = TRUE; _editButton.hidden = TRUE;
_deleteButton.hidden = TRUE; _deleteButton.hidden = TRUE;
} else if (self.contact != NULL && self.contact.createdFromLdapOrProvisioning) {
_editButton.hidden = TRUE;
_deleteButton.hidden = TRUE;
} }
PhoneMainView.instance.currentName = _nameLabel.text; PhoneMainView.instance.currentName = _nameLabel.text;
// Update presence for contact // Update presence for contact
@ -296,6 +302,21 @@
[_editButton setImage:[UIImage imageNamed:@"valid_default.png"] forState:UIControlStateSelected]; [_editButton setImage:[UIImage imageNamed:@"valid_default.png"] forState:UIControlStateSelected];
[self updateBackOrCancelButton]; [self updateBackOrCancelButton];
[self recomputeTableViewSize:FALSE];
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
[self resetData];
}
} }
- (void)deviceOrientationDidChange:(NSNotification*)notif { - (void)deviceOrientationDidChange:(NSNotification*)notif {
@ -310,9 +331,14 @@
} }
} }
if (self.contact != NULL && self.contact.createdFromLdapOrProvisioning) {
_editButton.hidden = TRUE;
_deleteButton.hidden = TRUE;
}
_nameLabel.hidden = self.tableController.isEditing;
_organizationLabel.hidden = self.tableController.isEditing;
[self updateBackOrCancelButton]; [self updateBackOrCancelButton];
[self recomputeTableViewSize:self.tableController.isEditing];
[self recomputeTableViewSize:_editButton.hidden];
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
@ -343,6 +369,9 @@
if (rm) { if (rm) {
[LinphoneManager.instance.fastAddressBook deleteContact:_contact]; [LinphoneManager.instance.fastAddressBook deleteContact:_contact];
} }
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
[AvatarBridge removeAllObserver];
} }
#pragma mark - UICompositeViewDelegate Functions #pragma mark - UICompositeViewDelegate Functions
@ -397,7 +426,9 @@ static UICompositeViewDescription *compositeDescription = nil;
_cancelButton.hidden = !editing; _cancelButton.hidden = !editing;
_backButton.hidden = editing; _backButton.hidden = editing;
_nameLabel.hidden = editing; _nameLabel.hidden = editing;
_organizationLabel.hidden = editing;
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact]; [ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact];
_organizationLabel.text = _contact.organizationName;
[self recomputeTableViewSize:editing]; [self recomputeTableViewSize:editing];
@ -408,10 +439,11 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)recomputeTableViewSize:(BOOL)editing { - (void)recomputeTableViewSize:(BOOL)editing {
CGRect frame = _tableController.tableView.frame; CGRect frame = _tableController.tableView.frame;
frame.origin.y = _avatarImage.frame.size.height + _avatarImage.frame.origin.y;
if ([self viewIsCurrentlyPortrait] && !editing) { if ([self viewIsCurrentlyPortrait] && !editing) {
frame.origin.y += _nameLabel.frame.size.height; frame.origin.y = _organizationLabel.frame.origin.y + _organizationLabel.frame.size.height;
} } else {
frame.origin.y = _avatarImage.frame.size.height + _avatarImage.frame.origin.y;
}
frame.size.height = _tableController.tableView.contentSize.height; frame.size.height = _tableController.tableView.contentSize.height;
_tableController.tableView.frame = frame; _tableController.tableView.frame = frame;
@ -442,7 +474,7 @@ static UICompositeViewDescription *compositeDescription = nil;
_contact.firstName = _tmpContact.firstName.copy; _contact.firstName = _tmpContact.firstName.copy;
_contact.lastName = _tmpContact.lastName.copy; _contact.lastName = _tmpContact.lastName.copy;
_contact.avatar = _tmpContact.avatar.copy; _contact.avatar = _tmpContact.avatar.copy;
[_avatarImage setImage:[FastAddressBook imageForContact:_contact] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForContact:_contact]];
while (_contact.sipAddresses.count > 0) { while (_contact.sipAddresses.count > 0) {
[_contact removeSipAddressAtIndex:0]; [_contact removeSipAddressAtIndex:0];
@ -514,11 +546,11 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
NSString* previous = [PhoneMainView.instance getPreviousViewName]; NSString* previous = [PhoneMainView.instance getPreviousViewName];
if ([previous isEqualToString:@"ContactsListView"] || [previous isEqualToString:@"ImagePickerView"]) { if ([previous isEqualToString:@"HistoryDetailsView"]) {
ContactsListView *view = VIEW(ContactsListView); HistoryDetailsView *view = VIEW(HistoryDetailsView);
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} else { } else {
HistoryDetailsView *view = VIEW(HistoryDetailsView); ContactsListView *view = VIEW(ContactsListView);
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} }
} }
@ -588,7 +620,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[[store unifiedContactWithIdentifier:contactToCheck.identifier keysToFetch:keysToFetch error:nil] mutableCopy]; [[store unifiedContactWithIdentifier:contactToCheck.identifier keysToFetch:keysToFetch error:nil] mutableCopy];
if(mCNContact == NULL){ if(mCNContact == NULL){
for(NSString *address in contactToCheck.sipAddresses){ for(NSString *address in contactToCheck.sipAddresses){
NSString *name = [FastAddressBook normalizeSipURI:address]; NSString *name = [FastAddressBook normalizeSipURI:address use_prefix:TRUE];
if([LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:name]){ if([LinphoneManager.instance.fastAddressBook.addressBookMap objectForKey:name]){
return TRUE; return TRUE;
} }
@ -619,7 +651,7 @@ static UICompositeViewDescription *compositeDescription = nil;
[_contact setAvatar:image]; [_contact setAvatar:image];
[_avatarImage setImage:[FastAddressBook imageForContact:_contact] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForContact:_contact]];
} }
- (void)imagePickerDelegateVideo:(NSURL*)url info:(NSDictionary *)info { - (void)imagePickerDelegateVideo:(NSURL*)url info:(NSDictionary *)info {

View file

@ -60,6 +60,60 @@
[view setContact:nil]; [view setContact:nil];
} }
} }
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[AvatarBridge removeAllObserver];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
NSDictionary* userInfo = notification.userInfo;
NSString* friend = (NSString*)userInfo[@"friend"];
NSArray<NSIndexPath *> *indexPathsVisible = self.tableView.indexPathsForVisibleRows;
for (int i = 0; i < indexPathsVisible.count; i++)
{
NSMutableArray *subAr = [addressBookMap objectForKey:[addressBookMap keyAtIndex:indexPathsVisible[i].section]];
Contact *contact = subAr[indexPathsVisible[i].row];
if (contact.sipAddresses.count > 0){
for (NSString *sip in contact.sipAddresses) {
NSString *uri = [@"sip:" stringByAppendingString:sip];
if([uri isEqual:friend]){
NSIndexPath* indexPath = indexPathsVisible[i];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}
}
}else if (contact.phones.count > 0){
for (NSString *phone in contact.phones) {
NSString *uri = phone;
if([uri isEqual:friend]){
NSIndexPath* indexPath = indexPathsVisible[i];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}
}
}
}
}
} }
- (id)init { - (id)init {
@ -231,7 +285,7 @@ static int ms_strcmpfuz(const char *fuzzy_word, const char *sentence) {
// Cached avatar // Cached avatar
UIImage *image = [FastAddressBook imageForContact:contact]; UIImage *image = [FastAddressBook imageForContact:contact];
[cell.avatarImage setImage:image bordered:NO withRoundedRadius:YES]; [cell.avatarImage setImage:image];
[cell setContact:contact]; [cell setContact:contact];
[super accessoryForCell:cell atPath:indexPath]; [super accessoryForCell:cell atPath:indexPath];
cell.contentView.userInteractionEnabled = false; cell.contentView.userInteractionEnabled = false;

View file

@ -50,14 +50,17 @@ typedef enum _ContactSelectionMode { ContactSelectionModeNone, ContactSelectionM
@property(strong, nonatomic) IBOutlet ContactsListTableView *tableController; @property(strong, nonatomic) IBOutlet ContactsListTableView *tableController;
@property(strong, nonatomic) IBOutlet UIView *topBar; @property(strong, nonatomic) IBOutlet UIView *topBar;
@property(strong, nonatomic) IBOutlet UIView *switchView;
@property(nonatomic, strong) IBOutlet UIButton *allButton; @property(nonatomic, strong) IBOutlet UIButton *allButton;
@property(nonatomic, strong) IBOutlet UIButton *linphoneButton; @property(nonatomic, strong) IBOutlet UIButton *linphoneButton;
@property(nonatomic, strong) IBOutlet UIButton *addButton; @property(nonatomic, strong) IBOutlet UIButton *addButton;
@property(nonatomic, strong) IBOutlet UIButton *deleteButton;
@property(strong, nonatomic) IBOutlet UISearchBar *searchBar; @property(strong, nonatomic) IBOutlet UISearchBar *searchBar;
@property(weak, nonatomic) IBOutlet UIImageView *selectedButtonImage; @property(weak, nonatomic) IBOutlet UIImageView *selectedButtonImage;
@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton; @property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *toggleSelectionButton;
@property (weak, nonatomic) IBOutlet UILabel *loadingLabel; @property (weak, nonatomic) IBOutlet UILabel *loadingLabel;
@property (weak, nonatomic) IBOutlet UIView *loadingView; @property (weak, nonatomic) IBOutlet UIView *loadingView;
@property (weak, nonatomic) IBOutlet UILabel *ldapMoreResultsLabel;
- (IBAction)onAllClick:(id)event; - (IBAction)onAllClick:(id)event;
- (IBAction)onLinphoneClick:(id)event; - (IBAction)onLinphoneClick:(id)event;

View file

@ -60,6 +60,7 @@ static BOOL addAddressFromOthers = FALSE;
@synthesize allButton; @synthesize allButton;
@synthesize linphoneButton; @synthesize linphoneButton;
@synthesize addButton; @synthesize addButton;
@synthesize deleteButton;
@synthesize topBar; @synthesize topBar;
typedef enum { ContactsAll, ContactsLinphone, ContactsMAX } ContactsCategory; typedef enum { ContactsAll, ContactsLinphone, ContactsMAX } ContactsCategory;
@ -96,7 +97,16 @@ static UICompositeViewDescription *compositeDescription = nil;
if (![[PhoneMainView.instance getPreviousViewName] isEqualToString:@"ContactDetailsView"]) { if (![[PhoneMainView.instance getPreviousViewName] isEqualToString:@"ContactDetailsView"]) {
_searchBar.text = @""; _searchBar.text = @"";
} }
[self changeView:ContactsAll];
if ([LinphoneManager.instance lpConfigBoolForKey:@"only_show_sip_contacts_list"]) {
_switchView.hidden = true;
[self changeView:ContactsLinphone];
} else if ([LinphoneManager.instance lpConfigBoolForKey:@"hide_sip_contacts_list"]){
_switchView.hidden = true;
[self changeView:ContactsAll];
} else {
[self changeView:ContactsAll];
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self initWithTarget:self
@ -142,6 +152,15 @@ static UICompositeViewDescription *compositeDescription = nil;
selector:@selector(onMagicSearchFinished:) selector:@selector(onMagicSearchFinished:)
name:kLinphoneMagicSearchFinished name:kLinphoneMagicSearchFinished
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(onMagicSearchMoreAvailable:)
name:kLinphoneMagicSearchMoreAvailable
object:nil];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
} }
- (void)onMagicSearchStarted:(NSNotification *)k { - (void)onMagicSearchStarted:(NSNotification *)k {
@ -150,6 +169,9 @@ static UICompositeViewDescription *compositeDescription = nil;
- (void)onMagicSearchFinished:(NSNotification *)k { - (void)onMagicSearchFinished:(NSNotification *)k {
_loadingView.hidden = TRUE; _loadingView.hidden = TRUE;
} }
- (void)onMagicSearchMoreAvailable:(NSNotification *)k {
_ldapMoreResultsLabel.hidden = FALSE;
}
- (void)viewDidAppear:(BOOL)animated { - (void)viewDidAppear:(BOOL)animated {
NSLog(@"Debuglog viewDidAppear"); NSLog(@"Debuglog viewDidAppear");
@ -225,7 +247,11 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (void)refreshButtons { - (void)refreshButtons {
[addButton setHidden:FALSE]; [addButton setHidden:![LinphoneManager.instance lpConfigBoolForKey:@"enable_native_address_book"] || [LinphoneManager.instance lpConfigBoolForKey:@"read_only_native_address_book"]];
if ([LinphoneManager.instance lpConfigBoolForKey:@"read_only_native_address_book"]) {
addButton.hidden = true;
deleteButton.hidden = true;
}
[self changeView:[ContactSelection getSipFilterEnabled] ? ContactsLinphone : ContactsAll]; [self changeView:[ContactSelection getSipFilterEnabled] ? ContactsLinphone : ContactsAll];
} }
@ -250,6 +276,10 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
} }
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
}
- (IBAction)onDeleteClick:(id)sender { - (IBAction)onDeleteClick:(id)sender {
NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"Do you want to delete selected contacts?\nThey will also be deleted from your phone's address book.", nil)]; NSString *msg = [NSString stringWithFormat:NSLocalizedString(@"Do you want to delete selected contacts?\nThey will also be deleted from your phone's address book.", nil)];
[LinphoneManager.instance setContactsUpdated:TRUE]; [LinphoneManager.instance setContactsUpdated:TRUE];
@ -268,6 +298,13 @@ static UICompositeViewDescription *compositeDescription = nil;
- (IBAction)onEditionChangeClick:(id)sender { - (IBAction)onEditionChangeClick:(id)sender {
allButton.hidden = linphoneButton.hidden = _selectedButtonImage.hidden = addButton.hidden = self.tableController.isEditing; allButton.hidden = linphoneButton.hidden = _selectedButtonImage.hidden = addButton.hidden = self.tableController.isEditing;
if ([LinphoneManager.instance lpConfigBoolForKey:@"enable_native_address_book"]) {
addButton.hidden = self.tableController.isEditing;
}
if ([LinphoneManager.instance lpConfigBoolForKey:@"read_only_native_address_book"]) {
addButton.hidden = true;
deleteButton.hidden = true;
}
} }
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
@ -292,6 +329,7 @@ static UICompositeViewDescription *compositeDescription = nil;
if (searchText.length == 0) { if (searchText.length == 0) {
[LinphoneManager.instance setContactsUpdated:TRUE]; [LinphoneManager.instance setContactsUpdated:TRUE];
} }
_ldapMoreResultsLabel.hidden = TRUE;
[tableController loadDataWithFilter:searchText]; [tableController loadDataWithFilter:searchText];
} }
} }

View file

@ -103,7 +103,7 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions #pragma mark - Action Functions
- (IBAction)onBackClick:(id)sender { - (IBAction)onBackClick:(id)sender {
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} }

View file

@ -145,9 +145,17 @@ static UICompositeViewDescription *compositeDescription = nil;
[_videoCameraSwitch setHidden:FALSE]; [_videoCameraSwitch setHidden:FALSE];
} }
} }
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit; LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
_addContactButton.enabled = true; if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)))){
[_addContactButton setImage:[UIImage imageNamed:@"contact_add_default"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
}else{
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
_addContactButton.enabled = true;
}
} }
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
@ -277,16 +285,10 @@ static UICompositeViewDescription *compositeDescription = nil;
- (BOOL)displayDebugPopup:(NSString *)address { - (BOOL)displayDebugPopup:(NSString *)address {
LinphoneManager *mgr = LinphoneManager.instance; LinphoneManager *mgr = LinphoneManager.instance;
NSString *debugAddress = [mgr lpConfigStringForKey:@"debug_popup_magic" withDefault:@""]; NSString *debugAddress = [mgr lpConfigStringForKey:@"debug_popup_magic" withDefault:@""];
if (![debugAddress isEqualToString:@""] && [address isEqualToString:debugAddress]) { if ((![debugAddress isEqualToString:@""] && [address isEqualToString:debugAddress]) || [_addressField.text isEqual: @"#1234#"]) {
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Debug", nil) UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Debug", nil)
message:NSLocalizedString(@"Choose an action", nil) message:NSLocalizedString(@"Choose an action", nil)
preferredStyle:UIAlertControllerStyleAlert]; preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[errView addAction:defaultAction];
int debugLevel = [LinphoneManager.instance lpConfigIntForKey:@"debugenable_preference"]; int debugLevel = [LinphoneManager.instance lpConfigIntForKey:@"debugenable_preference"];
BOOL debugEnabled = (debugLevel >= ORTP_DEBUG && debugLevel < ORTP_ERROR); BOOL debugEnabled = (debugLevel >= ORTP_DEBUG && debugLevel < ORTP_ERROR);
@ -314,31 +316,24 @@ static UICompositeViewDescription *compositeDescription = nil;
}]; }];
[errView addAction:logAction]; [errView addAction:logAction];
UIAlertAction* remAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Remove account(s) and self destruct", nil) UIAlertAction* configAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"View config file", nil)
style:UIAlertActionStyleDefault style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) { handler:^(UIAlertAction * action) {
linphone_core_clear_accounts([LinphoneManager getLc]); TextViewer *view = VIEW(TextViewer);
linphone_core_clear_all_auth_info([LinphoneManager getLc]); LpConfig *conf = LinphoneManager.instance.configDb;
@try { char *config = linphone_config_dump(conf);
[LinphoneManager.instance destroyLinphoneCore]; view.textViewer = [NSString stringWithUTF8String: config];
} @catch (NSException *e) { view.textNameViewer = @"";
LOGW(@"Exception while destroying linphone core: %@", e); [PhoneMainView.instance popToView:view.compositeViewDescription];
} @finally { }];
if ([NSFileManager.defaultManager
isDeletableFileAtPath:[LinphoneManager preferenceFile:@"linphonerc"]] == YES) { [errView addAction:configAction];
[NSFileManager.defaultManager
removeItemAtPath:[LinphoneManager preferenceFile:@"linphonerc"] UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil)
error:nil]; style:UIAlertActionStyleDefault
} handler:^(UIAlertAction * action) {}];
#ifdef DEBUG
[LinphoneManager instanceRelease]; [errView addAction:defaultAction];
#endif
}
[UIApplication sharedApplication].keyWindow.rootViewController = nil;
// make the application crash to be sure that user restart it properly
LOGF(@"Self-destructing in 3..2..1..0!");
}];
[errView addAction:remAction];
[self presentViewController:errView animated:YES completion:nil]; [self presentViewController:errView animated:YES completion:nil];
return true; return true;
@ -393,19 +388,39 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions #pragma mark - Action Functions
- (IBAction)onAddContactClick:(id)event { - (IBAction)onAddContactClick:(id)event {
ConferenceSchedulingView *view = VIEW(ConferenceSchedulingView); LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
[view resetViewModel]; if (!(defaultAccount && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(defaultAccount)))){
[PhoneMainView.instance changeCurrentView:ConferenceSchedulingView.compositeViewDescription]; [ContactSelection setSelectionMode:ContactSelectionModeEdit];
[ContactSelection setAddAddress:[_addressField text]];
[ContactSelection enableSipFilter:FALSE];
[PhoneMainView.instance changeCurrentView:ContactsListView.compositeViewDescription];
}else{
ConferenceSchedulingView *view = VIEW(ConferenceSchedulingView);
[view resetViewModel];
[PhoneMainView.instance changeCurrentView:ConferenceSchedulingView.compositeViewDescription];
}
} }
- (IBAction)onBackClick:(id)event { - (IBAction)onBackClick:(id)event {
[PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription]; [PhoneMainView.instance popToView:[CallsViewModelBridge callViewToDisplay]];
} }
- (IBAction)onAddressChange:(id)sender { - (IBAction)onAddressChange:(id)sender {
if ([self displayDebugPopup:_addressField.text]) { if ([_addressField.text isEqual: @"#1234#"]) {
[self displayDebugPopup:_addressField.text];
_addressField.text = @""; _addressField.text = @"";
} }
LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
if (!(defaultAccount && linphone_account_params_get_audio_video_conference_factory_address(linphone_account_get_params(defaultAccount)))){
[_addContactButton setImage:[UIImage imageNamed:@"contact_add_default"] forState:UIControlStateNormal];
_addContactButton.enabled = ([[_addressField text] length] > 0);
if ([_addressField.text length] == 0) {
[self.view endEditing:YES];
}
}else{
[_addContactButton setImage:[UIImage imageNamed:@"voip_conference_new"] forState:UIControlStateNormal];
_addContactButton.enabled = true;
}
} }
- (IBAction)onBackspaceClick:(id)sender { - (IBAction)onBackspaceClick:(id)sender {

View file

@ -20,6 +20,7 @@
#import "EphemeralSettingsView.h" #import "EphemeralSettingsView.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "UIDeviceCell.h" #import "UIDeviceCell.h"
#import "linphoneapp-Swift.h"
@ -74,14 +75,14 @@ static UICompositeViewDescription *compositeDescription = nil;
#pragma mark - Action Functions #pragma mark - Action Functions
- (IBAction)onBackClick:(id)sender { - (IBAction)onBackClick:(id)sender {
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} }
- (IBAction)onSaveClick:(id)sender { - (IBAction)onSaveClick:(id)sender {
[self setRoomSettingsBasedOnIndex]; [self setRoomSettingsBasedOnIndex];
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
[PhoneMainView.instance popToView:view.compositeViewDescription]; [PhoneMainView.instance popToView:view.compositeViewDescription];
} }

View file

@ -69,14 +69,15 @@ static UICompositeViewDescription *compositeDescription = nil;
object:nil]; object:nil];
// Update on show // Update on show
const MSList *list = linphone_core_get_account_list([LinphoneManager getLc]); MSList *list = [LinphoneManager.instance createAccountsNotHiddenList];
if (list != NULL) { if (list != NULL) {
LinphoneAccount *account = (LinphoneAccount *)list->data; LinphoneAccount *account = (LinphoneAccount *)list->data;
if (account) { if (account) {
[self registrationUpdate:linphone_account_get_state(account)]; [self registrationUpdate:linphone_account_get_state(account)];
} }
} }
bctbx_list_free(list);
if (account_creator) { if (account_creator) {
linphone_account_creator_unref(account_creator); linphone_account_creator_unref(account_creator);
} }

View file

@ -8,66 +8,90 @@
import Foundation import Foundation
import UIKit import UIKit
public extension ChatConversationTableView { extension ChatConversationTableViewSwift {
private enum Constants { private enum Constants {
static let trailingValue: CGFloat = 20.0 static let trailingValue: CGFloat = 30.0
static let leadingValue: CGFloat = 85.0 static let leadingValue: CGFloat = 85.0
static let buttonHeight: CGFloat = 40.0 static let buttonHeight: CGFloat = 16.0
static let buttonWidth: CGFloat = 40.0 static let buttonWidth: CGFloat = 16.0
} }
/*
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
tableView.tableFooterView = UIView() tableView.tableFooterView = UIView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
createFloatingButton() createFloatingButton()
} }
*/
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { override func viewDidDisappear(_ animated: Bool) {
if let lastCellRowIndex = tableView.indexPathsForVisibleRows?.last?.row { super.viewDidAppear(animated)
if( lastCellRowIndex != self.totalNumberOfItems() - 1) { self.floatingScrollButton?.removeFromSuperview()
self.floatingScrollButton?.isHidden = false self.floatingScrollBackground?.removeFromSuperview()
self.scrollBadge?.isHidden = (self.scrollBadge?.text == nil)
} else {
self.floatingScrollButton?.isHidden = true
self.scrollBadge?.text = nil
}
}
} }
private func createFloatingButton() { func createFloatingButton() {
self.floatingScrollButton = UIButton(type: .custom) self.floatingScrollButton = UIButton(type: .custom)
self.floatingScrollBackground = UIButton(type: .custom)
self.floatingScrollButton?.translatesAutoresizingMaskIntoConstraints = false self.floatingScrollButton?.translatesAutoresizingMaskIntoConstraints = false
self.floatingScrollBackground?.translatesAutoresizingMaskIntoConstraints = false
constrainFloatingButtonToWindow() constrainFloatingButtonToWindow()
self.floatingScrollButton?.setImage(UIImage(named: "scroll_to_bottom_default"), for: .normal) var imageFloatingScrollButton = UIImage()
self.floatingScrollButton?.addTarget(self, action: #selector(scrollToBottomButtonAction(_:)), for: .touchUpInside) if #available(iOS 13.0, *) {
imageFloatingScrollButton = UIImage(named: "scroll_to_bottom_default")!.withTintColor(.darkGray)
} else {
imageFloatingScrollButton = UIImage(named: "scroll_to_bottom_default")!
}
self.floatingScrollButton?.setImage(imageFloatingScrollButton, for: .normal)
self.floatingScrollButton?.isHidden = true; self.floatingScrollButton?.isHidden = true;
addBadgeToButon(badge: nil) self.floatingScrollBackground?.backgroundColor = .lightGray
self.floatingScrollBackground?.layer.cornerRadius = 25
self.floatingScrollBackground?.isHidden = true;
self.floatingScrollButton?.onClick(action: {
self.scrollToBottomButtonAction()
})
self.floatingScrollBackground?.onClick(action: {
self.scrollToBottomButtonAction()
})
addBadgeToButton(badge: nil)
} }
private func constrainFloatingButtonToWindow() { private func constrainFloatingButtonToWindow() {
DispatchQueue.main.async { DispatchQueue.main.async {
guard let keyWindow = UIApplication.shared.keyWindow, guard let keyWindow = self.view,
let floatingButton = self.floatingScrollButton else { return } let floatingButton = self.floatingScrollButton else { return }
keyWindow.addSubview(self.floatingScrollBackground!)
keyWindow.addSubview(floatingButton) keyWindow.addSubview(floatingButton)
keyWindow.trailingAnchor.constraint(equalTo: floatingButton.trailingAnchor, keyWindow.trailingAnchor.constraint(equalTo: floatingButton.trailingAnchor, constant: Constants.trailingValue).isActive = true
constant: Constants.trailingValue).isActive = true
keyWindow.bottomAnchor.constraint(equalTo: floatingButton.bottomAnchor, floatingButton.bottomAnchor.constraint(equalTo: keyWindow.bottomAnchor, constant: -25).isActive = true
constant: Constants.leadingValue).isActive = true
floatingButton.widthAnchor.constraint(equalToConstant: floatingButton.widthAnchor.constraint(equalToConstant:
Constants.buttonWidth).isActive = true Constants.buttonWidth).isActive = true
floatingButton.heightAnchor.constraint(equalToConstant: floatingButton.heightAnchor.constraint(equalToConstant:
Constants.buttonHeight).isActive = true Constants.buttonHeight).isActive = true
self.floatingScrollBackground?.centerYAnchor.constraint(equalTo: floatingButton.centerYAnchor).isActive = true
self.floatingScrollBackground?.centerXAnchor.constraint(equalTo: floatingButton.centerXAnchor).isActive = true
self.floatingScrollBackground!.widthAnchor.constraint(equalToConstant:
Constants.buttonHeight*3).isActive = true
self.floatingScrollBackground!.heightAnchor.constraint(equalToConstant:
Constants.buttonHeight*3).isActive = true
} }
} }
@IBAction private func scrollToBottomButtonAction(_ sender: Any) { @IBAction private func scrollToBottomButtonAction() {
scroll(toBottom: true) scrollToBottom(animated: false)
} }
private func addBadgeToButon(badge: String?) { private func addBadgeToButton(badge: String?) {
self.scrollBadge = UILabel() self.scrollBadge = UILabel()
self.scrollBadge?.text = badge self.scrollBadge?.text = badge
self.scrollBadge?.textColor = UIColor.white self.scrollBadge?.textColor = UIColor.white
@ -86,8 +110,8 @@ public extension ChatConversationTableView {
vertical = Double(badgeInset.top) - Double(badgeInset.bottom) vertical = Double(badgeInset.top) - Double(badgeInset.bottom)
horizontal = Double(badgeInset.left) - Double(badgeInset.right) horizontal = Double(badgeInset.left) - Double(badgeInset.right)
let x = (Double(scrollButton.bounds.size.width) - 10 + horizontal!) let x = (Double(scrollButton.bounds.size.width) + 34 + horizontal!)
let y = -(Double(badgeSize.height) / 2) - 10 + vertical! let y = -(Double(badgeSize.height) / 2) - 38 + vertical!
self.scrollBadge?.frame = CGRect(x: x, y: y, width: width, height: height) self.scrollBadge?.frame = CGRect(x: x, y: y, width: width, height: height)
self.scrollBadge!.layer.cornerRadius = self.scrollBadge!.frame.height/2 self.scrollBadge!.layer.cornerRadius = self.scrollBadge!.frame.height/2

View file

@ -31,7 +31,7 @@
} }
@property(weak, nonatomic) IBOutlet UIButton *backButton; @property(weak, nonatomic) IBOutlet UIButton *backButton;
@property(weak, nonatomic) IBOutlet UILabel *contactLabel; @property(weak, nonatomic) IBOutlet UILabel *contactLabel;
@property(nonatomic, strong) IBOutlet UIRoundedImageView *avatarImage; @property(nonatomic, strong) IBOutlet UIImageView *avatarImage;
@property(nonatomic, strong) IBOutlet UILabel *addressLabel; @property(nonatomic, strong) IBOutlet UILabel *addressLabel;
@property(nonatomic, strong) IBOutlet UIButton *addContactButton; @property(nonatomic, strong) IBOutlet UIButton *addContactButton;
@property(nonatomic, copy, setter=setCallLogId:) NSString *callLogId; @property(nonatomic, copy, setter=setCallLogId:) NSString *callLogId;
@ -41,6 +41,7 @@
@property (weak, nonatomic) IBOutlet UIView *waitView; @property (weak, nonatomic) IBOutlet UIView *waitView;
@property (weak, nonatomic) IBOutlet UIRoundedImageView *linphoneImage; @property (weak, nonatomic) IBOutlet UIRoundedImageView *linphoneImage;
@property (weak, nonatomic) IBOutlet UIView *optionsView; @property (weak, nonatomic) IBOutlet UIView *optionsView;
@property(weak, nonatomic) IBOutlet UIButton *chatButton;
@property (weak, nonatomic) IBOutlet UIView *encryptedChatView; @property (weak, nonatomic) IBOutlet UIView *encryptedChatView;
- (IBAction)onBackClick:(id)event; - (IBAction)onBackClick:(id)event;

View file

@ -20,6 +20,7 @@
#import "HistoryDetailsView.h" #import "HistoryDetailsView.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "FastAddressBook.h" #import "FastAddressBook.h"
#import "linphoneapp-Swift.h"
@implementation HistoryDetailsView @implementation HistoryDetailsView
@ -68,6 +69,12 @@ static UICompositeViewDescription *compositeDescription = nil;
[_headerView addGestureRecognizer:headerTapGesture]; [_headerView addGestureRecognizer:headerTapGesture];
} }
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
_chatButton.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"] || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"];
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
[self update];
}
- (void)viewWillAppear:(BOOL)animated { - (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated]; [super viewWillAppear:animated];
_waitView.hidden = YES; _waitView.hidden = YES;
@ -87,11 +94,27 @@ static UICompositeViewDescription *compositeDescription = nil;
selector: @selector(deviceOrientationDidChange:) selector: @selector(deviceOrientationDidChange:)
name: UIDeviceOrientationDidChangeNotification name: UIDeviceOrientationDidChangeNotification
object: nil]; object: nil];
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
[self update];
}
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]; [super viewWillDisappear:animated];
[NSNotificationCenter.defaultCenter removeObserver:self]; [NSNotificationCenter.defaultCenter removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
[AvatarBridge removeAllObserver];
} }
#pragma mark - Event Functions #pragma mark - Event Functions
@ -144,7 +167,7 @@ static UICompositeViewDescription *compositeDescription = nil;
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog); const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);
_addContactButton.hidden = ([FastAddressBook getContactWithAddress:addr] != nil); _addContactButton.hidden = ([FastAddressBook getContactWithAddress:addr] != nil);
[ContactDisplay setDisplayNameLabel:_contactLabel forAddress:addr withAddressLabel:_addressLabel]; [ContactDisplay setDisplayNameLabel:_contactLabel forAddress:addr withAddressLabel:_addressLabel];
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForAddress:addr]];
Contact *contact = [FastAddressBook getContactWithAddress:addr]; Contact *contact = [FastAddressBook getContactWithAddress:addr];
const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model(contact.friend) : NULL; const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model(contact.friend) : NULL;
_linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] || _linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] ||
@ -158,7 +181,7 @@ static UICompositeViewDescription *compositeDescription = nil;
} }
- (void)shouldHideEncryptedChatView:(BOOL)hasLime { - (void)shouldHideEncryptedChatView:(BOOL)hasLime {
_encryptedChatView.hidden = !hasLime; _encryptedChatView.hidden = !hasLime || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"];
CGRect newFrame = _optionsView.frame; CGRect newFrame = _optionsView.frame;
if (!hasLime) { if (!hasLime) {
newFrame.origin.x = _encryptedChatView.frame.size.width * 2/3; newFrame.origin.x = _encryptedChatView.frame.size.width * 2/3;

View file

@ -71,6 +71,36 @@
name:kLinphoneCoreUpdate name:kLinphoneCoreUpdate
object:nil]; object:nil];
[self loadData]; [self loadData];
NSDictionary* userInfo;
[NSNotificationCenter.defaultCenter addObserver:self
selector: @selector(receivePresenceNotification:)
name: @"LinphoneFriendPresenceUpdate"
object: userInfo];
}
-(void) receivePresenceNotification:(NSNotification*)notification
{
if ([notification.name isEqualToString:@"LinphoneFriendPresenceUpdate"])
{
NSDictionary* userInfo = notification.userInfo;
NSString* friend = (NSString*)userInfo[@"friend"];
const MSList *list = linphone_core_get_call_logs(LC);
int i = 0;
while (list != NULL) {
LinphoneCallLog *log = (LinphoneCallLog *)list->data;
const char *curi = linphone_address_as_string_uri_only(linphone_call_log_get_remote_address(log));
NSString *uri = [NSString stringWithUTF8String:curi];
if([uri isEqual:friend]){
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:0];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationFade];
}
i = i + 1;
list = list->next;
}
}
} }
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
@ -79,6 +109,8 @@
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneAddressBookUpdate object:nil]; [NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneAddressBookUpdate object:nil];
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCoreUpdate object:nil]; [NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCoreUpdate object:nil];
[NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCallUpdate object:nil]; [NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCallUpdate object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"LinphoneFriendPresenceUpdate" object:nil];
[AvatarBridge removeAllObserver];
} }
#pragma mark - Event Functions #pragma mark - Event Functions
@ -272,9 +304,9 @@
[ConferenceViewModelBridge showCancelledMeetingWithCConferenceInfo:confInfo]; [ConferenceViewModelBridge showCancelledMeetingWithCConferenceInfo:confInfo];
return; return;
} }
ConferenceWaitingRoomFragment *view = VIEW(ConferenceWaitingRoomFragment); ConferenceWaitingRoomView *view = VIEW(ConferenceWaitingRoomView);
[view setDetailsWithSubject:[NSString stringWithUTF8String:linphone_conference_info_get_subject(confInfo)] url:[NSString stringWithUTF8String:linphone_address_as_string(linphone_conference_info_get_uri(confInfo))]]; [view setDetailsWithSubject:[NSString stringWithUTF8String:linphone_conference_info_get_subject(confInfo)] url:[NSString stringWithUTF8String:linphone_address_as_string(linphone_conference_info_get_uri(confInfo))]];
[PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomFragment.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomView.compositeViewDescription];
} else { } else {
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog); const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);
[tableView deselectRowAtIndexPath:indexPath animated:NO]; [tableView deselectRowAtIndexPath:indexPath animated:NO];

View file

@ -67,10 +67,15 @@ static UICompositeViewDescription *compositeDescription = nil;
// Fake event // Fake event
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
[_toggleSelectionButton setImage:[UIImage imageNamed:@"select_all_default.png"] forState:UIControlStateSelected]; [_toggleSelectionButton setImage:[UIImage imageNamed:@"select_all_default.png"] forState:UIControlStateSelected];
[NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(displayModeChanged)
name:kDisplayModeChanged
object:nil];
} }
- (void) viewWillDisappear:(BOOL)animated { - (void) viewWillDisappear:(BOOL)animated {
self.view = NULL; self.view = NULL;
[NSNotificationCenter.defaultCenter removeObserver:self];
} }
#pragma mark - #pragma mark -
@ -100,6 +105,10 @@ static UICompositeViewDescription *compositeDescription = nil;
_selectedButtonImage.frame = frame; _selectedButtonImage.frame = frame;
} }
- (void)displayModeChanged{
[self.tableController.tableView reloadData];
}
#pragma m ~ark - Action Functions #pragma m ~ark - Action Functions
- (IBAction)onAllClick:(id)event { - (IBAction)onAllClick:(id)event {

View file

@ -35,6 +35,8 @@
#import <IntentsUI/IntentsUI.h> #import <IntentsUI/IntentsUI.h>
#import "linphoneapp-Swift.h" #import "linphoneapp-Swift.h"
#import "SVProgressHUD.h"
#ifdef USE_CRASHLYTICS #ifdef USE_CRASHLYTICS
#include "FIRApp.h" #include "FIRApp.h"
@ -61,6 +63,9 @@
- (void)applicationDidEnterBackground:(UIApplication *)application { - (void)applicationDidEnterBackground:(UIApplication *)application {
LOGI(@"%@", NSStringFromSelector(_cmd)); LOGI(@"%@", NSStringFromSelector(_cmd));
if([LinphoneManager.instance lpConfigBoolForKey:@"account_push_presence_preference"]){
linphone_core_set_consolidated_presence(LC, LinphoneConsolidatedPresenceOffline);
}
if (linphone_core_get_global_state(LC) != LinphoneGlobalOff) { if (linphone_core_get_global_state(LC) != LinphoneGlobalOff) {
[LinphoneManager.instance enterBackgroundMode]; [LinphoneManager.instance enterBackgroundMode];
[LinphoneManager.instance.fastAddressBook clearFriends]; [LinphoneManager.instance.fastAddressBook clearFriends];
@ -73,6 +78,7 @@
// To avoid crash // To avoid crash
[PhoneMainView.instance changeCurrentView:DialerView.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:DialerView.compositeViewDescription];
} }
[CallManager.instance stopLinphoneCore]; [CallManager.instance stopLinphoneCore];
} }
[SwiftUtil resetCachedAsset]; [SwiftUtil resetCachedAsset];
@ -83,14 +89,24 @@
[LinphoneManager.instance startLinphoneCore]; [LinphoneManager.instance startLinphoneCore];
[LinphoneManager.instance.fastAddressBook reloadFriends]; [LinphoneManager.instance.fastAddressBook reloadFriends];
[AvatarBridge clearFriends];
if([LinphoneManager.instance lpConfigBoolForKey:@"account_push_presence_preference"]){
linphone_core_set_consolidated_presence(LC, LinphoneConsolidatedPresenceOnline);
}
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:nil]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:nil];
} }
- (void)applicationWillResignActive:(UIApplication *)application { - (void)applicationWillResignActive:(UIApplication *)application {
LOGI(@"%@", NSStringFromSelector(_cmd)); LOGI(@"%@", NSStringFromSelector(_cmd));
LinphoneCall *call = linphone_core_get_current_call(LC); LinphoneCall *call = linphone_core_get_current_call(LC);
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
if (defaults) {
[defaults setBool:false forKey:@"appactive"];
}
if (!call) if (!call)
return; return;
@ -113,7 +129,12 @@
} }
LinphoneManager *instance = LinphoneManager.instance; LinphoneManager *instance = LinphoneManager.instance;
[instance becomeActive]; [instance becomeActive];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
if (defaults) {
[defaults setBool:true forKey:@"appactive"];
}
if (instance.fastAddressBook.needToUpdate) { if (instance.fastAddressBook.needToUpdate) {
//Update address book for external changes //Update address book for external changes
if (PhoneMainView.instance.currentView == ContactsListView.compositeViewDescription || PhoneMainView.instance.currentView == ContactDetailsView.compositeViewDescription) { if (PhoneMainView.instance.currentView == ContactsListView.compositeViewDescription || PhoneMainView.instance.currentView == ContactDetailsView.compositeViewDescription) {
@ -137,7 +158,6 @@
if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) { if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) { if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) {
linphone_call_accept(call); linphone_call_accept(call);
[PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
} else { } else {
[PhoneMainView.instance displayIncomingCall:call]; [PhoneMainView.instance displayIncomingCall:call];
} }
@ -156,7 +176,9 @@
[self handleShortcut:_shortcutItem]; [self handleShortcut:_shortcutItem];
_shortcutItem = nil; _shortcutItem = nil;
} }
#if TARGET_IPHONE_SIMULATOR
#else
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError *_Nullable error) { completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (error) if (error)
@ -182,6 +204,16 @@
} }
}]; }];
#endif
if ([UIDeviceBridge switchedDisplayMode]) {
[AvatarBridge prepareIt];
[NSNotificationCenter.defaultCenter postNotificationName:kDisplayModeChanged object:nil];
[PhoneMainView.instance.mainViewController removeEntryFromCache:ChatConversationCreateView.compositeViewDescription.name];
[PhoneMainView.instance.mainViewController changeView:PhoneMainView.instance.currentView];
[UIDeviceBridge notifyDisplayModeSwitch];
}
} }
@ -331,8 +363,9 @@
return NO; return NO;
} }
[PhoneMainView.instance.mainViewController getCachedController:ActiveCallOrConferenceView.compositeViewDescription.name]; // This will create the single instance of the ActiveCallOrConferenceView including listeneres [PhoneMainView.instance.mainViewController getCachedController:SingleCallView.compositeViewDescription.name]; // This will create the single instance of the SingleCallView including listeneres
[PhoneMainView.instance.mainViewController getCachedController:ConferenceCallView.compositeViewDescription.name]; // This will create the single instance of the ConferenceCallView including listeneres
[CallsViewModelBridge setupCallsViewNavigation];
return YES; return YES;
} }
@ -365,31 +398,39 @@
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options{ - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options{
NSString *scheme = [[url scheme] lowercaseString]; NSString *scheme = [[url scheme] lowercaseString];
if ([scheme isEqualToString:@"linphone-config"] || [scheme isEqualToString:@"linphone-config"]) { if ([scheme isEqualToString:@"linphone-config"]) {
NSString *encodedURL = NSString *encodedURL =
[[url absoluteString] stringByReplacingOccurrencesOfString:@"linphone-config://" withString:@""]; [[url absoluteString] stringByReplacingOccurrencesOfString:@"linphone-config:" withString:@""];
self.configURL = [encodedURL stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; self.configURL = [encodedURL stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Remote configuration", nil)
message:NSLocalizedString(@"This operation will load a remote configuration. Continue ?", nil)
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"No", nil) BOOL auto_apply_provisioning = [LinphoneManager.instance lpConfigBoolForKey:@"auto_apply_provisioning_config_uri_handler" inSection:@"app" withDefault:FALSE];
style:UIAlertActionStyleDefault if (auto_apply_provisioning) {
handler:^(UIAlertAction * action) {}]; [SVProgressHUD show];
[self attemptRemoteConfiguration];
UIAlertAction* yesAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", nil) [SVProgressHUD dismiss];
style:UIAlertActionStyleDefault } else {
handler:^(UIAlertAction * action) { NSString *msg = [NSString stringWithFormat:NSLocalizedString(@" Do you want to download and apply configuration from this URL?\n\n%@", nil), encodedURL];
[self showWaitingIndicator]; UIConfirmationDialog* remoteConfigurationDialog =[UIConfirmationDialog ShowWithMessage:msg
[self attemptRemoteConfiguration]; cancelMessage:nil
}]; confirmMessage:NSLocalizedString(@"APPLY", nil)
onCancelClick:^() {}
[errView addAction:defaultAction]; onConfirmationClick:^() {
[errView addAction:yesAction]; [SVProgressHUD show];
[self attemptRemoteConfiguration];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; [SVProgressHUD dismiss];
}];
[remoteConfigurationDialog setSpecialColor];
}
} else if([[url scheme] isEqualToString:@"message-linphone"]) { } else if([[url scheme] isEqualToString:@"message-linphone"]) {
[PhoneMainView.instance popToView:ChatsListView.compositeViewDescription]; if ([[PhoneMainView.instance currentView] equal:ChatsListView.compositeViewDescription]) {
VIEW(ChatConversationViewSwift).sharingMedia = TRUE;
ChatsListView *view = VIEW(ChatsListView);
[view mediaSharing];
}else{
[SVProgressHUD dismiss];
VIEW(ChatConversationViewSwift).sharingMedia = TRUE;
[PhoneMainView.instance popToView:ChatsListView.compositeViewDescription];
}
} else if ([scheme isEqualToString:@"sip"]||[scheme isEqualToString:@"sips"]) { } else if ([scheme isEqualToString:@"sip"]||[scheme isEqualToString:@"sips"]) {
// remove "sip://" from the URI, and do it correctly by taking resourceSpecifier and removing leading and // remove "sip://" from the URI, and do it correctly by taking resourceSpecifier and removing leading and
// trailing "/" // trailing "/"
@ -414,8 +455,8 @@
linphone_address_unref(peer); linphone_address_unref(peer);
linphone_address_unref(local); linphone_address_unref(local);
// TODO : Find a better fix // TODO : Find a better fix
VIEW(ChatConversationView).markAsRead = FALSE; VIEW(ChatConversationViewSwift).markAsRead = FALSE;
[PhoneMainView.instance goToChatRoom:cr]; [PhoneMainView.instance goToChatRoomSwift:cr];
} }
} }
return YES; return YES;
@ -504,33 +545,57 @@
- (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { - (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
// If an app extension launch a user notif while app is in fg, it is catch by the app // If an app extension launch a user notif while app is in fg, it is catch by the app
NSString *category = [[[notification request] content] categoryIdentifier]; NSString *category = [[[notification request] content] categoryIdentifier];
if (category && [category isEqualToString:@"app_active"]) { if (category && [category isEqualToString:@"app_active"]) {
return; return;
} }
if (category && [category isEqualToString:@"msg_cat"] && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) { if (category && [category isEqualToString:@"msg_cat"] && [UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
if ((PhoneMainView.instance.currentView == ChatsListView.compositeViewDescription)) if ((PhoneMainView.instance.currentView == ChatsListView.compositeViewDescription))
return; return;
if (PhoneMainView.instance.currentView == ChatConversationView.compositeViewDescription) { if (PhoneMainView.instance.currentView == ChatConversationViewSwift.compositeViewDescription) {
NSDictionary *userInfo = [[[notification request] content] userInfo]; NSDictionary *userInfo = [[[notification request] content] userInfo];
NSString *peerAddress = userInfo[@"peer_addr"]; NSString *peerAddress = userInfo[@"peer_addr"];
NSString *localAddress = userInfo[@"local_addr"]; NSString *localAddress = userInfo[@"local_addr"];
if (peerAddress && localAddress) { if (peerAddress && localAddress) {
LinphoneAddress *peer = linphone_core_create_address([LinphoneManager getLc], peerAddress.UTF8String); LinphoneAddress *peer = linphone_core_create_address([LinphoneManager getLc], peerAddress.UTF8String);
LinphoneAddress *local = linphone_core_create_address([LinphoneManager getLc], localAddress.UTF8String); LinphoneAddress *local = linphone_core_create_address([LinphoneManager getLc], localAddress.UTF8String);
LinphoneChatRoom *room = linphone_core_find_chat_room([LinphoneManager getLc], peer, local); LinphoneChatRoom *room = linphone_core_search_chat_room([LinphoneManager getLc], NULL, local, peer, NULL);
if (room == PhoneMainView.instance.currentRoom) return; if (room == PhoneMainView.instance.currentRoom) return;
} }
} }
} }
completionHandler(UNNotificationPresentationOptionAlert); completionHandler(UNNotificationPresentationOptionAlert);
} }
-(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { -(void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
LOGD(@"didReceiveRemoteNotification -- backgroundPush");
if (linphone_core_get_global_state(LC) != LinphoneGlobalOn) {
[LinphoneManager.instance startLinphoneCore];
[LinphoneManager.instance.fastAddressBook reloadFriends];
}
const MSList *accounts = linphone_core_get_account_list(LC);
while (accounts) {
LinphoneAccount *account = (LinphoneAccount *)accounts->data;
linphone_account_refresh_register(account);
accounts = accounts->next;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
NSMutableDictionary *chatroomsPushStatus = [[NSMutableDictionary alloc] initWithDictionary:[defaults dictionaryForKey:@"appactive"]];
if ([defaults boolForKey:@"appactive"] != TRUE) {
linphone_core_enter_background(LC);
if (linphone_core_get_calls_nb(LC) == 0) {
linphone_core_stop(LC);
}
}
completionHandler(UIBackgroundFetchResultNewData);
});
} }
- (void)userNotificationCenter:(UNUserNotificationCenter *)center - (void)userNotificationCenter:(UNUserNotificationCenter *)center
@ -570,7 +635,7 @@
LinphoneAddress *local = linphone_address_new(local_address.UTF8String); LinphoneAddress *local = linphone_address_new(local_address.UTF8String);
LinphoneChatRoom *room = linphone_core_find_chat_room(LC, peer, local); LinphoneChatRoom *room = linphone_core_find_chat_room(LC, peer, local);
if (room) if (room)
[ChatConversationView markAsRead:room]; [ChatConversationViewSwift markAsRead:room];
linphone_address_unref(peer); linphone_address_unref(peer);
linphone_address_unref(local); linphone_address_unref(local);
@ -610,14 +675,16 @@
LinphoneAddress *local = linphone_address_new(local_address.UTF8String); LinphoneAddress *local = linphone_address_new(local_address.UTF8String);
LinphoneChatRoom *room = linphone_core_find_chat_room(LC, peer, local); LinphoneChatRoom *room = linphone_core_find_chat_room(LC, peer, local);
if (room) { if (room) {
[PhoneMainView.instance goToChatRoom:room]; [PhoneMainView.instance resetBeforeGoToChatRoomSwift];
[PhoneMainView.instance changeCurrentView:ChatsListView.compositeViewDescription];
[PhoneMainView.instance goToChatRoomSwift:room];
return; return;
} else {
[PhoneMainView.instance changeCurrentView:ChatsListView.compositeViewDescription];
} }
[PhoneMainView.instance changeCurrentView:ChatsListView.compositeViewDescription];
} }
} else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) { } else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) {
if (!call) return; if (!call) return;
[PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
NSTimer *videoDismissTimer = nil; NSTimer *videoDismissTimer = nil;
UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body
cancelMessage:nil cancelMessage:nil
@ -718,7 +785,7 @@
LinphoneAddress *local = linphone_address_new(local_address.UTF8String); LinphoneAddress *local = linphone_address_new(local_address.UTF8String);
LinphoneChatRoom *room = linphone_core_find_chat_room(LC, peer, local); LinphoneChatRoom *room = linphone_core_find_chat_room(LC, peer, local);
if (room) if (room)
[ChatConversationView markAsRead:room]; [ChatConversationViewSwift markAsRead:room];
linphone_address_unref(peer); linphone_address_unref(peer);
linphone_address_unref(local); linphone_address_unref(local);
@ -799,23 +866,7 @@
} }
} }
- (void)showWaitingIndicator {
_waitingIndicator = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Fetching remote configuration...", nil)
message:@""
preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView *progress = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(125, 60, 30, 30)];
progress.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
[_waitingIndicator setValue:progress forKey:@"accessoryView"];
[progress setColor:[UIColor blackColor]];
[progress startAnimating];
[PhoneMainView.instance presentViewController:_waitingIndicator animated:YES completion:nil];
}
- (void)attemptRemoteConfiguration { - (void)attemptRemoteConfiguration {
[NSNotificationCenter.defaultCenter addObserver:self [NSNotificationCenter.defaultCenter addObserver:self
selector:@selector(ConfigurationStateUpdateEvent:) selector:@selector(ConfigurationStateUpdateEvent:)
name:kLinphoneConfiguringStateUpdate name:kLinphoneConfiguringStateUpdate
@ -823,7 +874,7 @@
linphone_core_set_provisioning_uri(LC, [configURL UTF8String]); linphone_core_set_provisioning_uri(LC, [configURL UTF8String]);
[LinphoneManager.instance destroyLinphoneCore]; [LinphoneManager.instance destroyLinphoneCore];
[LinphoneManager.instance launchLinphoneCore]; [LinphoneManager.instance launchLinphoneCore];
[LinphoneManager.instance.fastAddressBook fetchContactsInBackGroundThread]; [LinphoneManager.instance.fastAddressBook fetchContactsInBackGroundThread];
} }
#pragma mark - Prevent ImagePickerView from rotating #pragma mark - Prevent ImagePickerView from rotating

View file

@ -27,6 +27,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@implementation LinphoneCoreSettingsStore @implementation LinphoneCoreSettingsStore
- (id)init { - (id)init {
@ -141,7 +142,9 @@
} }
- (void)transformAccountToKeys:(NSString *)username { - (void)transformAccountToKeys:(NSString *)username {
const MSList *accountList = linphone_core_get_account_list(LC); //const MSList *accountList = linphone_core_get_account_list(LC);
MSList *accountListToBeFreed = [LinphoneManager.instance createAccountsNotHiddenList];
MSList *accountList = accountListToBeFreed;
while (username && accountList && while (username && accountList &&
strcmp(username.UTF8String, strcmp(username.UTF8String,
linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(accountList->data)))) != 0) { linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(accountList->data)))) != 0) {
@ -152,6 +155,7 @@
// default values // default values
{ {
[self setBool:NO forKey:@"account_pushnotification_preference"]; [self setBool:NO forKey:@"account_pushnotification_preference"];
[self setBool:NO forKey:@"account_bundle_mode_preference"];
[self setObject:@"" forKey:@"account_mandatory_username_preference"]; [self setObject:@"" forKey:@"account_mandatory_username_preference"];
[self setObject:@"" forKey:@"account_mandatory_domain_preference"]; [self setObject:@"" forKey:@"account_mandatory_domain_preference"];
[self setCString:"" forKey:@"account_display_name_preference"]; [self setCString:"" forKey:@"account_display_name_preference"];
@ -168,6 +172,7 @@
[self setInteger:-1 forKey:@"account_expire_preference"]; [self setInteger:-1 forKey:@"account_expire_preference"];
[self setInteger:-1 forKey:@"current_proxy_config_preference"]; [self setInteger:-1 forKey:@"current_proxy_config_preference"];
[self setCString:"" forKey:@"account_prefix_preference"]; [self setCString:"" forKey:@"account_prefix_preference"];
[self setBool:YES forKey:@"apply_international_prefix_for_calls_and_chats"];
[self setBool:NO forKey:@"account_substitute_+_by_00_preference"]; [self setBool:NO forKey:@"account_substitute_+_by_00_preference"];
[self setBool:NO forKey:@"account_ice_preference"]; [self setBool:NO forKey:@"account_ice_preference"];
[self setCString:"" forKey:@"account_stun_preference"]; [self setCString:"" forKey:@"account_stun_preference"];
@ -180,10 +185,13 @@
{ {
BOOL pushEnabled = linphone_account_params_get_push_notification_allowed(accountParams); BOOL pushEnabled = linphone_account_params_get_push_notification_allowed(accountParams);
[self setBool:pushEnabled forKey:@"account_pushnotification_preference"]; [self setBool:pushEnabled forKey:@"account_pushnotification_preference"];
BOOL bundleModeEnabled = linphone_account_params_rtp_bundle_enabled(accountParams);
[self setBool:bundleModeEnabled forKey:@"account_bundle_mode_preference"];
const LinphoneAddress *identity_addr = linphone_account_params_get_identity_address(accountParams); const LinphoneAddress *identity_addr = linphone_account_params_get_identity_address(accountParams);
const char *server_addr = linphone_account_params_get_server_addr(accountParams); const char *server_addr = linphone_account_params_get_server_addr(accountParams);
LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, server_addr); LinphoneAddress *proxy_addr = linphone_core_interpret_url_2(LC, server_addr, false);
if (identity_addr && proxy_addr) { if (identity_addr && proxy_addr) {
int port = linphone_address_get_port(proxy_addr); int port = linphone_address_get_port(proxy_addr);
@ -234,9 +242,11 @@
[self setCString:linphone_auth_info_get_algorithm(ai) forKey:@"ha1_algo_preference"]; [self setCString:linphone_auth_info_get_algorithm(ai) forKey:@"ha1_algo_preference"];
} }
MSList *accountsList = [LinphoneManager.instance createAccountsNotHiddenList];
int idx = (int)bctbx_list_index(linphone_core_get_account_list(LC), account); int idx = (int)bctbx_list_index(linphone_core_get_account_list(LC), account);
[self setInteger:idx forKey:@"current_proxy_config_preference"]; [self setInteger:idx forKey:@"current_proxy_config_preference"];
bctbx_list_free(accountsList);
int expires = linphone_account_params_get_expires(accountParams); int expires = linphone_account_params_get_expires(accountParams);
[self setInteger:expires forKey:@"account_expire_preference"]; [self setInteger:expires forKey:@"account_expire_preference"];
@ -251,10 +261,13 @@
{ {
const char *dial_prefix = linphone_account_params_get_international_prefix(accountParams); const char *dial_prefix = linphone_account_params_get_international_prefix(accountParams);
[self setCString:dial_prefix forKey:@"account_prefix_preference"]; [self setCString:dial_prefix forKey:@"account_prefix_preference"];
BOOL apply_prefix = linphone_account_params_get_use_international_prefix_for_calls_and_chats(accountParams);
[self setBool:apply_prefix forKey:@"apply_international_prefix_for_calls_and_chats"];
BOOL dial_escape_plus = linphone_account_params_get_dial_escape_plus_enabled(accountParams); BOOL dial_escape_plus = linphone_account_params_get_dial_escape_plus_enabled(accountParams);
[self setBool:dial_escape_plus forKey:@"account_substitute_+_by_00_preference"]; [self setBool:dial_escape_plus forKey:@"account_substitute_+_by_00_preference"];
} }
} }
bctbx_list_free(accountListToBeFreed);
} }
@ -343,15 +356,17 @@
// root section // root section
{ {
const bctbx_list_t *accounts = linphone_core_get_account_list(LC); MSList *accountsListToBeFreed = [lm createAccountsNotHiddenList];
size_t count = bctbx_list_size(accounts); MSList *accountsList = accountsListToBeFreed;
for (size_t i = 1; i <= count; i++, accounts = accounts->next) { size_t count = bctbx_list_size(accountsList);
for (size_t i = 1; i <= count; i++, accountsList = accountsList->next) {
NSString *key = [NSString stringWithFormat:@"menu_account_%lu", i]; NSString *key = [NSString stringWithFormat:@"menu_account_%lu", i];
LinphoneAccount *account = (LinphoneAccount *)accounts->data; LinphoneAccount *account = (LinphoneAccount *)accountsList->data;
[self setCString:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account))) [self setCString:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)))
forKey:key]; forKey:key];
} }
bctbx_free(accountsListToBeFreed);
[self setBool:linphone_core_video_display_enabled(LC) forKey:@"enable_video_preference"]; [self setBool:linphone_core_video_display_enabled(LC) forKey:@"enable_video_preference"];
[self setBool:[LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"] [self setBool:[LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"]
forKey:@"enable_auto_answer_preference"]; forKey:@"enable_auto_answer_preference"];
@ -430,10 +445,10 @@
{ {
[self setCString:linphone_core_get_file_transfer_server(LC) forKey:@"file_transfer_server_url_preference"]; [self setCString:linphone_core_get_file_transfer_server(LC) forKey:@"file_transfer_server_url_preference"];
int maxSize = linphone_core_get_max_size_for_auto_download_incoming_files(LC); int maxSize = linphone_core_get_max_size_for_auto_download_incoming_files(LC);
[self setObject:maxSize==0 ? @"Always" : (maxSize==-1 ? @"Nerver" : @"Customize") forKey:@"auto_download_mode"]; [self setObject:maxSize==0 ? @"Always" : (maxSize==-1 ? @"Never" : @"Customize") forKey:@"auto_download_mode"];
[self setInteger:maxSize forKey:@"auto_download_incoming_files_max_size"]; [self setInteger:maxSize forKey:@"auto_download_incoming_files_max_size"];
[self setBool:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] forKey:@"vfs_enabled_mode"]; [self setBool:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] forKey:@"vfs_enabled_mode"];
[self setBool:[lm lpConfigBoolForKey:@"auto_write_to_gallery_preference" withDefault:YES] forKey:@"auto_write_to_gallery_mode"]; [self setBool:[lm lpConfigBoolForKey:@"auto_write_to_gallery_preference" withDefault:NO] forKey:@"auto_write_to_gallery_mode"];
} }
// network section // network section
@ -485,7 +500,7 @@
val = "None"; val = "None";
break; break;
} }
[self setCString:val forKey:@"media_encryption_preference"]; [self setCString:val forKey:linphone_core_get_post_quantum_available() ? @"media_encryption_preference_pq_enabled" : @"media_encryption_preference"];
[self setInteger:linphone_core_get_upload_bandwidth(LC) forKey:@"upload_bandwidth_preference"]; [self setInteger:linphone_core_get_upload_bandwidth(LC) forKey:@"upload_bandwidth_preference"];
[self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"]; [self setInteger:linphone_core_get_download_bandwidth(LC) forKey:@"download_bandwidth_preference"];
[self setBool:linphone_core_adaptive_rate_control_enabled(LC) forKey:@"adaptive_rate_control_preference"]; [self setBool:linphone_core_adaptive_rate_control_enabled(LC) forKey:@"adaptive_rate_control_preference"];
@ -509,8 +524,11 @@
} }
// contacts section // contacts section
if (linphone_core_ldap_available(LC)) { {
[self transformLdapToKeys:nil]; [self setInteger:[lm lpConfigBoolForKey:@"account_push_presence_preference" withDefault:YES] forKey:@"account_push_presence_preference"];
if (linphone_core_ldap_available(LC)) {
[self transformLdapToKeys:nil];
}
} }
// advanced section // advanced section
@ -519,6 +537,7 @@
[self setBool:ANIMATED forKey:@"animations_preference"]; [self setBool:ANIMATED forKey:@"animations_preference"];
[self setBool:[lm lpConfigBoolForKey:@"backgroundmode_preference"] forKey:@"backgroundmode_preference"]; [self setBool:[lm lpConfigBoolForKey:@"backgroundmode_preference"] forKey:@"backgroundmode_preference"];
[self setBool:[lm lpConfigBoolForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"]; [self setBool:[lm lpConfigBoolForKey:@"start_at_boot_preference"] forKey:@"start_at_boot_preference"];
[self setBool:[lm lpConfigBoolForKey:@"screenshot_preference" withDefault:NO] forKey:@"screenshot_preference"];
[self setBool:[lm lpConfigBoolForKey:@"autoanswer_notif_preference"] forKey:@"autoanswer_notif_preference"]; [self setBool:[lm lpConfigBoolForKey:@"autoanswer_notif_preference"] forKey:@"autoanswer_notif_preference"];
[self setBool:[lm lpConfigBoolForKey:@"show_msg_in_notif" withDefault:YES] forKey:@"show_msg_in_notif"]; [self setBool:[lm lpConfigBoolForKey:@"show_msg_in_notif" withDefault:YES] forKey:@"show_msg_in_notif"];
[self setBool:[lm lpConfigBoolForKey:@"use_rls_presence" withDefault:YES] forKey:@"use_rls_presence"]; [self setBool:[lm lpConfigBoolForKey:@"use_rls_presence" withDefault:YES] forKey:@"use_rls_presence"];
@ -603,7 +622,9 @@
if (username && [username length] > 0 && domain && [domain length] > 0) { if (username && [username length] > 0 && domain && [domain length] > 0) {
int expire = [self integerForKey:@"account_expire_preference"]; int expire = [self integerForKey:@"account_expire_preference"];
BOOL pushnotification = [self boolForKey:@"account_pushnotification_preference"]; BOOL pushnotification = [self boolForKey:@"account_pushnotification_preference"];
BOOL bundlemode = [self boolForKey:@"account_bundle_mode_preference"];
NSString *prefix = [self stringForKey:@"account_prefix_preference"]; NSString *prefix = [self stringForKey:@"account_prefix_preference"];
BOOL use_prefix = [self boolForKey:@"apply_international_prefix_for_calls_and_chats"];
NSString *proxyAddress = [self stringForKey:@"account_proxy_preference"]; NSString *proxyAddress = [self stringForKey:@"account_proxy_preference"];
if ((!proxyAddress || [proxyAddress length] < 1) && domain) { if ((!proxyAddress || [proxyAddress length] < 1) && domain) {
@ -614,7 +635,7 @@
proxyAddress = [NSString stringWithFormat:@"sip:%@", proxyAddress]; proxyAddress = [NSString stringWithFormat:@"sip:%@", proxyAddress];
} }
LinphoneAddress *proxy_addr = linphone_core_interpret_url(LC, proxyAddress.UTF8String); LinphoneAddress *proxy_addr = linphone_core_interpret_url_2(LC, proxyAddress.UTF8String, false);
if (proxy_addr) { if (proxy_addr) {
LinphoneTransportType type = LinphoneTransportUdp; LinphoneTransportType type = LinphoneTransportUdp;
@ -625,9 +646,11 @@
linphone_address_set_transport(proxy_addr, type); linphone_address_set_transport(proxy_addr, type);
} }
account = bctbx_list_nth_data(linphone_core_get_account_list(LC), MSList *accountList= [LinphoneManager.instance createAccountsNotHiddenList];
account = bctbx_list_nth_data(accountList,
[self integerForKey:@"current_proxy_config_preference"]); [self integerForKey:@"current_proxy_config_preference"]);
bctbx_free(accountList);
// if account was deleted, it is not present anymore // if account was deleted, it is not present anymore
if (account == NULL) if (account == NULL)
@ -670,10 +693,8 @@
linphone_nat_policy_set_stun_server(policy, stun_preference.UTF8String); linphone_nat_policy_set_stun_server(policy, stun_preference.UTF8String);
linphone_account_params_set_nat_policy(newAccountParams, policy); linphone_account_params_set_nat_policy(newAccountParams, policy);
if ([prefix length] > 0) { linphone_account_params_set_international_prefix(newAccountParams, [prefix UTF8String]);
linphone_account_params_set_international_prefix(newAccountParams, [prefix UTF8String]); linphone_account_params_set_use_international_prefix_for_calls_and_chats(newAccountParams, use_prefix);
}
if ([self objectForKey:@"account_substitute_+_by_00_preference"]) { if ([self objectForKey:@"account_substitute_+_by_00_preference"]) {
bool substitute_plus_by_00 = [self boolForKey:@"account_substitute_+_by_00_preference"]; bool substitute_plus_by_00 = [self boolForKey:@"account_substitute_+_by_00_preference"];
linphone_account_params_set_dial_escape_plus_enabled(newAccountParams, substitute_plus_by_00); linphone_account_params_set_dial_escape_plus_enabled(newAccountParams, substitute_plus_by_00);
@ -681,8 +702,10 @@
// use empty string "" instead of NULL to avoid being overwritten by default proxy config values // use empty string "" instead of NULL to avoid being overwritten by default proxy config values
linphone_account_params_set_push_notification_allowed(newAccountParams, pushnotification); linphone_account_params_set_push_notification_allowed(newAccountParams, pushnotification);
linphone_account_params_enable_rtp_bundle(newAccountParams, bundlemode);
linphone_account_params_set_push_notification_allowed(newAccountParams, pushnotification);
linphone_account_params_set_remote_push_notification_allowed(newAccountParams, pushnotification); linphone_account_params_set_remote_push_notification_allowed(newAccountParams, pushnotification);
linphone_account_params_set_register_enabled(newAccountParams, is_enabled); linphone_account_params_set_register_enabled(newAccountParams, is_enabled);
linphone_account_params_set_avpf_mode(newAccountParams, use_avpf); linphone_account_params_set_avpf_mode(newAccountParams, use_avpf);
linphone_account_params_set_expires(newAccountParams, expire); linphone_account_params_set_expires(newAccountParams, expire);
@ -713,7 +736,7 @@
} }
char *identity = linphone_address_as_string(linphoneAddress); char *identity = linphone_address_as_string(linphoneAddress);
LinphoneAddress *from = linphone_core_interpret_url(LC, identity); LinphoneAddress *from = linphone_core_interpret_url_2(LC, identity, false);
ms_free(identity); ms_free(identity);
if (from) { if (from) {
const char *userid_str = (userID != nil) ? [userID UTF8String] : NULL; const char *userid_str = (userID != nil) ? [userID UTF8String] : NULL;
@ -805,7 +828,7 @@
linphone_ldap_params_set_bind_dn(newLdapParams, [self stringForKey:@"ldap_bind_dn"].UTF8String); linphone_ldap_params_set_bind_dn(newLdapParams, [self stringForKey:@"ldap_bind_dn"].UTF8String);
linphone_ldap_params_set_password(newLdapParams, [self stringForKey:@"ldap_password"].UTF8String); linphone_ldap_params_set_password(newLdapParams, [self stringForKey:@"ldap_password"].UTF8String);
LinphoneLdapAuthMethod authMethod = [[self stringForKey:@"ldap_verification_method"] isEqualToString:@"simple"] ? LinphoneLdapAuthMethodSimple : LinphoneLdapAuthMethodAnonymous; LinphoneLdapAuthMethod authMethod = [[self stringForKey:@"ldap_auth_method"] isEqualToString:@"simple"] ? LinphoneLdapAuthMethodSimple : LinphoneLdapAuthMethodAnonymous;
linphone_ldap_params_set_auth_method(newLdapParams, authMethod); linphone_ldap_params_set_auth_method(newLdapParams, authMethod);
linphone_ldap_params_enable_tls(newLdapParams, [self boolForKey:@"ldap_tls_enabled"]); linphone_ldap_params_enable_tls(newLdapParams, [self boolForKey:@"ldap_tls_enabled"]);
@ -829,8 +852,8 @@
// Analysis parameters // Analysis parameters
linphone_ldap_params_set_name_attribute(newLdapParams, [self stringForKey:@"ldap_name_attributes"].UTF8String); linphone_ldap_params_set_name_attribute(newLdapParams, [self stringForKey:@"ldap_name_attribute"].UTF8String);
linphone_ldap_params_set_sip_attribute(newLdapParams, [self stringForKey:@"ldap_sip_attributes"].UTF8String); linphone_ldap_params_set_sip_attribute(newLdapParams, [self stringForKey:@"ldap_sip_attribute"].UTF8String);
linphone_ldap_params_set_sip_domain(newLdapParams, [self stringForKey:@"ldap_sip_domain"].UTF8String); linphone_ldap_params_set_sip_domain(newLdapParams, [self stringForKey:@"ldap_sip_domain"].UTF8String);
// Miscellaneous parameters // Miscellaneous parameters
@ -1018,7 +1041,7 @@
[LinphoneCoreSettingsStore parsePortRange:video_port_preference minPort:&videoMinPort maxPort:&videoMaxPort]; [LinphoneCoreSettingsStore parsePortRange:video_port_preference minPort:&videoMinPort maxPort:&videoMaxPort];
linphone_core_set_video_port_range(LC, videoMinPort, videoMaxPort); linphone_core_set_video_port_range(LC, videoMinPort, videoMaxPort);
NSString *menc = [self stringForKey:@"media_encryption_preference"]; NSString *menc = [self stringForKey:linphone_core_get_post_quantum_available() ? @"media_encryption_preference_pq_enabled" : @"media_encryption_preference"];
if (menc && [menc compare:@"SRTP"] == NSOrderedSame) if (menc && [menc compare:@"SRTP"] == NSOrderedSame)
linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP); linphone_core_set_media_encryption(LC, LinphoneMediaEncryptionSRTP);
else if (menc && [menc compare:@"ZRTP"] == NSOrderedSame) else if (menc && [menc compare:@"ZRTP"] == NSOrderedSame)
@ -1071,7 +1094,13 @@
} }
// contacts section // contacts section
BOOL push_presence = [self boolForKey:@"account_push_presence_preference"];
if (push_presence) {
linphone_core_set_consolidated_presence([LinphoneManager getLc], LinphoneConsolidatedPresenceOnline);
} else {
linphone_core_set_consolidated_presence([LinphoneManager getLc], LinphoneConsolidatedPresenceOffline);
}
[lm lpConfigSetInt:push_presence forKey:@"account_push_presence_preference"];
BOOL ldap_changed = NO; BOOL ldap_changed = NO;
for (NSString *key in self->changedDict) { for (NSString *key in self->changedDict) {
@ -1086,6 +1115,9 @@
// advanced section // advanced section
BOOL animations = [self boolForKey:@"animations_preference"]; BOOL animations = [self boolForKey:@"animations_preference"];
[lm lpConfigSetInt:animations forKey:@"animations_preference"]; [lm lpConfigSetInt:animations forKey:@"animations_preference"];
BOOL screenshot = [self boolForKey:@"screenshot_preference"];
[lm lpConfigSetInt:screenshot forKey:@"screenshot_preference"];
UIDevice *device = [UIDevice currentDevice]; UIDevice *device = [UIDevice currentDevice];
BOOL backgroundSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported]; BOOL backgroundSupported = [device respondsToSelector:@selector(isMultitaskingSupported)] && [device isMultitaskingSupported];
@ -1100,7 +1132,9 @@
NSString *rls_uri = [lm lpConfigStringForKey:@"rls_uri" inSection:@"sip" withDefault:@"sips:rls@sip.linphone.org"]; NSString *rls_uri = [lm lpConfigStringForKey:@"rls_uri" inSection:@"sip" withDefault:@"sips:rls@sip.linphone.org"];
LinphoneAddress *rls_addr = linphone_address_new(rls_uri.UTF8String); LinphoneAddress *rls_addr = linphone_address_new(rls_uri.UTF8String);
const char *rls_domain = linphone_address_get_domain(rls_addr); const char *rls_domain = linphone_address_get_domain(rls_addr);
const MSList *accounts = linphone_core_get_account_list(LC);
MSList *accountListToBeFreed = [LinphoneManager.instance createAccountsNotHiddenList];
const MSList *accounts = accountListToBeFreed;
if (!accounts) // Enable it if no proxy config for first launch of app if (!accounts) // Enable it if no proxy config for first launch of app
[self setInteger:1 forKey:@"use_rls_presence"]; [self setInteger:1 forKey:@"use_rls_presence"];
else { else {
@ -1114,6 +1148,7 @@
} }
} }
linphone_address_unref(rls_addr); linphone_address_unref(rls_addr);
bctbx_free(accountListToBeFreed);
} }
[lm lpConfigSetInt:[self integerForKey:@"use_rls_presence"] forKey:@"use_rls_presence"]; [lm lpConfigSetInt:[self integerForKey:@"use_rls_presence"] forKey:@"use_rls_presence"];
@ -1155,9 +1190,12 @@
} }
- (void)removeAccount { - (void)removeAccount {
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
MSList *accountList = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(accountList,
[self integerForKey:@"current_proxy_config_preference"]); [self integerForKey:@"current_proxy_config_preference"]);
const MSList *lists = linphone_core_get_friends_lists(LC); const MSList *lists = linphone_core_get_friends_lists(LC);
while (lists) { while (lists) {
linphone_friend_list_enable_subscriptions(lists->data, FALSE); linphone_friend_list_enable_subscriptions(lists->data, FALSE);
@ -1178,11 +1216,12 @@
if (isDefault) { if (isDefault) {
// if we removed the default proxy config, set another one instead // if we removed the default proxy config, set another one instead
if (linphone_core_get_account_list(LC) != NULL) { if (accountList != NULL) {
linphone_core_set_default_account(LC, (LinphoneAccount *)(linphone_core_get_account_list(LC)->data)); linphone_core_set_default_account(LC, (LinphoneAccount *)(accountList->data));
} }
} }
[self transformLinphoneCoreToKeys]; [self transformLinphoneCoreToKeys];
bctbx_free(accountList);
} }
- (void)removeLdap { - (void)removeLdap {

View file

@ -65,6 +65,8 @@ extern NSString *const kLinphoneConfStateParticipantListChanged;
extern NSString *const kLinphoneConfStateChanged; extern NSString *const kLinphoneConfStateChanged;
extern NSString *const kLinphoneMagicSearchStarted; extern NSString *const kLinphoneMagicSearchStarted;
extern NSString *const kLinphoneMagicSearchFinished; extern NSString *const kLinphoneMagicSearchFinished;
extern NSString *const kLinphoneMagicSearchMoreAvailable;
extern NSString *const kDisplayModeChanged;
extern NSString *const kLinphoneMsgNotificationAppGroupId; extern NSString *const kLinphoneMsgNotificationAppGroupId;
@ -174,6 +176,7 @@ typedef struct _LinphoneManagerSounds {
- (void)silentPushFailed:(NSTimer*)timer; - (void)silentPushFailed:(NSTimer*)timer;
- (MSList *) createAccountsNotHiddenList; // needs to be unref
- (void)removeAllAccounts; - (void)removeAllAccounts;
+ (BOOL)isMyself:(const LinphoneAddress *)addr; + (BOOL)isMyself:(const LinphoneAddress *)addr;
@ -193,6 +196,8 @@ typedef struct _LinphoneManagerSounds {
- (void)checkLocalNetworkPermission; - (void)checkLocalNetworkPermission;
- (void)setDnsServer; - (void)setDnsServer;
+ (BOOL) getChatroomPushEnabled:(LinphoneChatRoom *)chatroom;
+ (void) setChatroomPushEnabled:(LinphoneChatRoom *)chatroom withPushEnabled:(BOOL)enabled;
@property (readonly) BOOL isTesting; @property (readonly) BOOL isTesting;
@property(readonly, strong) FastAddressBook *fastAddressBook; @property(readonly, strong) FastAddressBook *fastAddressBook;

View file

@ -81,13 +81,14 @@ NSString *const kLinphoneConfStateChanged = @"kLinphoneConfStateChanged";
NSString *const kLinphoneConfStateParticipantListChanged = @"kLinphoneConfStateParticipantListChanged"; NSString *const kLinphoneConfStateParticipantListChanged = @"kLinphoneConfStateParticipantListChanged";
NSString *const kLinphoneMagicSearchStarted = @"LinphoneMagicSearchStarted"; NSString *const kLinphoneMagicSearchStarted = @"LinphoneMagicSearchStarted";
NSString *const kLinphoneMagicSearchFinished = @"LinphoneMagicSearchFinished"; NSString *const kLinphoneMagicSearchFinished = @"LinphoneMagicSearchFinished";
NSString *const kLinphoneMagicSearchMoreAvailable = @"LinphoneMagicSearchMoreAvailable";
NSString *const kDisplayModeChanged = @"DisplayModeChanged";
NSString *const kLinphoneMsgNotificationAppGroupId = @"group.org.linphone.phone.msgNotification"; NSString *const kLinphoneMsgNotificationAppGroupId = @"group.org.linphone.phone.msgNotification";
const int kLinphoneAudioVbrCodecDefaultBitrate = 36; /*you can override this from linphonerc or linphonerc-factory*/ const int kLinphoneAudioVbrCodecDefaultBitrate = 36; /*you can override this from linphonerc or linphonerc-factory*/
extern void libmsamr_init(MSFactory *factory); extern void libmsamr_init(MSFactory *factory);
extern void libmsx264_init(MSFactory *factory);
extern void libmsopenh264_init(MSFactory *factory); extern void libmsopenh264_init(MSFactory *factory);
extern void libmssilk_init(MSFactory *factory); extern void libmssilk_init(MSFactory *factory);
extern void libmswebrtc_init(MSFactory *factory); extern void libmswebrtc_init(MSFactory *factory);
@ -239,7 +240,7 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
NSString *path = [[NSBundle mainBundle] pathForResource:@"msg" ofType:@"wav"]; NSString *path = [[NSBundle mainBundle] pathForResource:@"msg" ofType:@"wav"];
self.messagePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:nil]; self.messagePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:nil];
_sounds.vibrate = kSystemSoundID_Vibrate; //_sounds.vibrate = kSystemSoundID_Vibrate;
_logs = [[NSMutableArray alloc] init]; _logs = [[NSMutableArray alloc] init];
_pushDict = [[NSMutableDictionary alloc] init]; _pushDict = [[NSMutableDictionary alloc] init];
@ -253,6 +254,11 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
[self renameDefaultSettings]; [self renameDefaultSettings];
[self copyDefaultSettings]; [self copyDefaultSettings];
[self overrideDefaultSettings]; [self overrideDefaultSettings];
if (![self lpConfigBoolForKey:@"disable_chat_feature" withDefault:FALSE]) {
_sounds.vibrate = kSystemSoundID_Vibrate;
}
if (![self lpConfigBoolForKey:@"migration_images_done" withDefault:FALSE]) { if (![self lpConfigBoolForKey:@"migration_images_done" withDefault:FALSE]) {
[self migrationAllImages]; [self migrationAllImages];
} }
@ -276,6 +282,14 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
[self lpConfigSetString:@"conflate" forKey:@"handle_content_encoding" inSection:@"misc"]; [self lpConfigSetString:@"conflate" forKey:@"handle_content_encoding" inSection:@"misc"];
#endif #endif
} }
if ([self lpConfigStringForKey:@"display_link_account_popup"] == nil) {
[self lpConfigSetBool:true forKey:@"display_link_account_popup"];
}
if ([self lpConfigStringForKey:@"hide_link_phone_number"] == nil) {
[self lpConfigSetInt:1 forKey:@"hide_link_phone_number"];
}
[self migrateFromUserPrefs]; [self migrateFromUserPrefs];
[self loadAvatar]; [self loadAvatar];
@ -372,6 +386,10 @@ static int check_should_migrate_images(void *data, int argc, char **argv, char *
} }
- (void)migrationLinphoneSettings { - (void)migrationLinphoneSettings {
NSString *appDomain = [LinphoneManager.instance lpConfigStringForKey:@"domain_name"
inSection:@"app"
withDefault:@"sip.linphone.org"];
/* AVPF migration */ /* AVPF migration */
if ([self lpConfigBoolForKey:@"avpf_migration_done"] == FALSE) { if ([self lpConfigBoolForKey:@"avpf_migration_done"] == FALSE) {
const MSList *accounts = linphone_core_get_account_list(theLinphoneCore); const MSList *accounts = linphone_core_get_account_list(theLinphoneCore);
@ -464,6 +482,26 @@ static int check_should_migrate_images(void *data, int argc, char **argv, char *
} }
[self lpConfigSetBool:TRUE forKey:@"push_notification_migration_done"]; [self lpConfigSetBool:TRUE forKey:@"push_notification_migration_done"];
} }
if ([self lpConfigBoolForKey:@"publish_enabled_migration_done"] == FALSE) {
const MSList *accounts = linphone_core_get_account_list(theLinphoneCore);
linphone_core_set_log_collection_upload_server_url(LC, "https://www.linphone.org:444/lft.php");
[self lpConfigSetBool:TRUE forKey:@"update_presence_model_timestamp_before_publish_expires_refresh"];
while (accounts)
{
LinphoneAccount *account = (LinphoneAccount *)accounts->data;
LinphoneAccountParams *newAccountParams = linphone_account_params_clone(linphone_account_get_params(account));
if (strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0) {
linphone_account_params_set_publish_enabled(newAccountParams, true);
linphone_account_params_set_publish_expires(newAccountParams, 120);
linphone_account_set_params(account, newAccountParams);
}
linphone_account_params_unref(newAccountParams);
accounts = accounts->next;
}
[self lpConfigSetBool:TRUE forKey:@"publish_enabled_migration_done"];
}
} }
- (void)migrationPerAccount { - (void)migrationPerAccount {
@ -474,27 +512,33 @@ static int check_should_migrate_images(void *data, int argc, char **argv, char *
while (accounts) { while (accounts) {
LinphoneAccount *account = accounts->data; LinphoneAccount *account = accounts->data;
LinphoneAccountParams *newAccountParams = linphone_account_params_clone(linphone_account_get_params(account)); LinphoneAccountParams *newAccountParams = linphone_account_params_clone(linphone_account_get_params(account));
// can not create group chat without conference factory
if (!linphone_account_params_get_conference_factory_uri(newAccountParams)) { if (strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0) {
if (strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0) { // can not create group chat without conference factory
if (!linphone_account_params_get_conference_factory_uri(newAccountParams)) {
linphone_account_params_set_conference_factory_uri(newAccountParams, "sip:conference-factory@sip.linphone.org"); linphone_account_params_set_conference_factory_uri(newAccountParams, "sip:conference-factory@sip.linphone.org");
linphone_account_set_params(account, newAccountParams); linphone_account_set_params(account, newAccountParams);
} }
}
if (!linphone_account_params_get_audio_video_conference_factory_address(newAccountParams) && strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0) { if (!linphone_account_params_get_audio_video_conference_factory_address(newAccountParams)) {
NSString *uri = [self lpConfigStringForKey:@"default_audio_video_conference_factory_uri" withDefault:@"sip:videoconference-factory2@sip.linphone.org"]; NSString *uri = [self lpConfigStringForKey:@"default_audio_video_conference_factory_uri" withDefault:@"sip:videoconference-factory2@sip.linphone.org"];
LinphoneAddress *a = linphone_factory_create_address(linphone_factory_get(), uri.UTF8String); LinphoneAddress *a = linphone_factory_create_address(linphone_factory_get(), uri.UTF8String);
if (a) { if (a) {
linphone_account_params_set_audio_video_conference_factory_address(newAccountParams, a); linphone_account_params_set_audio_video_conference_factory_address(newAccountParams, a);
linphone_account_set_params(account, newAccountParams); linphone_account_set_params(account, newAccountParams);
}
} }
/*
if (!linphone_account_params_rtp_bundle_enabled(newAccountParams)) {
linphone_account_params_enable_rtp_bundle(newAccountParams, true);
linphone_account_set_params(account,newAccountParams);
}
*/
LOGI(@"Setting the sip 'expires' parameters of existing account to 1 year (31536000 seconds)");
linphone_account_params_set_expires(newAccountParams, 31536000);
} }
if (strcmp(appDomain.UTF8String, linphone_account_params_get_domain(newAccountParams)) == 0 && !linphone_account_params_rtp_bundle_enabled(newAccountParams)) {
linphone_account_params_enable_rtp_bundle(newAccountParams, true);
linphone_account_set_params(account,newAccountParams);
}
linphone_account_params_unref(newAccountParams); linphone_account_params_unref(newAccountParams);
accounts = accounts->next; accounts = accounts->next;
} }
@ -637,6 +681,11 @@ static void linphone_iphone_global_state_changed(LinphoneCore *lc, LinphoneGloba
if (theLinphoneCore && linphone_core_get_global_state(theLinphoneCore) != LinphoneGlobalOff) if (theLinphoneCore && linphone_core_get_global_state(theLinphoneCore) != LinphoneGlobalOff)
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneGlobalStateUpdate object:self userInfo:dict]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneGlobalStateUpdate object:self userInfo:dict];
}); });
if (state == LinphoneGlobalOn) {
// reload friends
[self.fastAddressBook fetchContactsInBackGroundThread];
}
} }
- (void)globalStateChangedNotificationHandler:(NSNotification *)notif { - (void)globalStateChangedNotificationHandler:(NSNotification *)notif {
@ -865,9 +914,9 @@ static void linphone_iphone_popup_password_request(LinphoneCore *lc, LinphoneAut
return; return;
if (hasFile) { if (hasFile) {
if (PhoneMainView.instance.currentView == ChatConversationView.compositeViewDescription && room == PhoneMainView.instance.currentRoom) if (PhoneMainView.instance.currentView == ChatConversationViewSwift.compositeViewDescription && room == PhoneMainView.instance.currentRoom)
return; return;
[ChatConversationView autoDownload:msg]; [self autoDownload:msg];
} }
// Post event // Post event
@ -881,6 +930,21 @@ static void linphone_iphone_popup_password_request(LinphoneCore *lc, LinphoneAut
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:self userInfo:dict]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:self userInfo:dict];
} }
- (void)autoDownload:(LinphoneChatMessage *)message {
LinphoneContent *content = linphone_chat_message_get_file_transfer_information(message);
NSString *name = [NSString stringWithUTF8String:linphone_content_get_name(content)];
NSString *fileType = [NSString stringWithUTF8String:linphone_content_get_type(content)];
NSString *key = [ChatConversationViewSwift getKeyFromFileType:fileType fileName:name];
[LinphoneManager setValueInMessageAppData:name forKey:key inMessage:message];
dispatch_async(dispatch_get_main_queue(), ^{
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneMessageReceived object:VIEW(ChatConversationViewSwift)];
if (![VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] && [ConfigManager.instance lpConfigBoolForKeyWithKey:@"auto_write_to_gallery_preference"]) {
[ChatConversationViewSwift writeMediaToGalleryFromName:name fileType:fileType];
}
});
}
static void linphone_iphone_message_received(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage *message) { static void linphone_iphone_message_received(LinphoneCore *lc, LinphoneChatRoom *room, LinphoneChatMessage *message) {
[(__bridge LinphoneManager *)linphone_core_cbs_get_user_data(linphone_core_get_current_callbacks(lc)) onMessageReceived:lc room:room message:message]; [(__bridge LinphoneManager *)linphone_core_cbs_get_user_data(linphone_core_get_current_callbacks(lc)) onMessageReceived:lc room:room message:message];
} }
@ -1264,7 +1328,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
[LinphoneManager.instance lpConfigSetInt:0 forKey:@"must_link_account_time"]; [LinphoneManager.instance lpConfigSetInt:0 forKey:@"must_link_account_time"];
} else { } else {
LinphoneAccount *account = linphone_core_get_default_account(LC); LinphoneAccount *account = linphone_core_get_default_account(LC);
LinphoneAccountParams const *accountParams = linphone_account_get_params(account); LinphoneAccountParams const *accountParams = account ? linphone_account_get_params(account) : NULL;
if (account && if (account &&
strcmp(linphone_account_params_get_domain(accountParams), strcmp(linphone_account_params_get_domain(accountParams),
[LinphoneManager.instance lpConfigStringForKey:@"domain_name" [LinphoneManager.instance lpConfigStringForKey:@"domain_name"
@ -1285,7 +1349,14 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
handler:^(UIAlertAction * action) { handler:^(UIAlertAction * action) {
[PhoneMainView.instance changeCurrentView:AssistantLinkView.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:AssistantLinkView.compositeViewDescription];
}]; }];
UIAlertAction* otherAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Never ask again", nil)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
[LinphoneManager.instance lpConfigSetBool:false forKey:@"display_link_account_popup"];
}];
defaultAction.accessibilityLabel = @"Later"; defaultAction.accessibilityLabel = @"Later";
[errView addAction:otherAction];
[errView addAction:defaultAction]; [errView addAction:defaultAction];
[errView addAction:continueAction]; [errView addAction:continueAction];
[PhoneMainView.instance presentViewController:errView animated:YES completion:nil]; [PhoneMainView.instance presentViewController:errView animated:YES completion:nil];
@ -1304,7 +1375,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
NSDate *nextTime = NSDate *nextTime =
[NSDate dateWithTimeIntervalSince1970:[self lpConfigIntForKey:@"must_link_account_time" withDefault:1]]; [NSDate dateWithTimeIntervalSince1970:[self lpConfigIntForKey:@"must_link_account_time" withDefault:1]];
NSDate *now = [NSDate date]; NSDate *now = [NSDate date];
if (nextTime.timeIntervalSince1970 > 0 && [now earlierDate:nextTime] == nextTime) { if (nextTime.timeIntervalSince1970 > 0 && [now earlierDate:nextTime] == nextTime && [LinphoneManager.instance lpConfigBoolForKey:@"display_link_account_popup"] && ![LinphoneManager.instance lpConfigIntForKey:@"hide_link_phone_number"]) {
LinphoneAccount *account = linphone_core_get_default_account(LC); LinphoneAccount *account = linphone_core_get_default_account(LC);
if (account) { if (account) {
const char *username = linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account))); const char *username = linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)));
@ -1344,22 +1415,37 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
} }
} }
- (void)activateBasicChatroomCPIMForLinphoneAccounts { - (void)enableLinphoneAccountSpecificSettings {
const MSList *accountsList = linphone_core_get_account_list(theLinphoneCore); const MSList *accountsList = linphone_core_get_account_list(theLinphoneCore);
while (accountsList) { while (accountsList) {
LinphoneAccount * account = accountsList->data; LinphoneAccount * account = accountsList->data;
LinphoneAccountParams const * currentParams = linphone_account_get_params(account); LinphoneAccountParams const * currentParams = linphone_account_get_params(account);
LinphoneAddress const * currentAddress = linphone_account_params_get_identity_address(currentParams); LinphoneAddress const * currentAddress = linphone_account_params_get_identity_address(currentParams);
char * addressIdentity = linphone_address_as_string(currentAddress);
if (strcmp(linphone_address_get_domain(currentAddress), "sip.linphone.org") == 0 && !linphone_account_params_cpim_in_basic_chat_room_enabled(currentParams) ) { if (strcmp(linphone_address_get_domain(currentAddress), "sip.linphone.org") == 0) {
LOGI(@"Enabling CPIM in basic chatroom for user %s", linphone_address_get_username(currentAddress));
LinphoneAccountParams * newParams = linphone_account_params_clone(linphone_account_get_params(account)); LinphoneAccountParams * newParams = linphone_account_params_clone(linphone_account_get_params(account));
linphone_account_params_enable_cpim_in_basic_chat_room(newParams, true); if (!linphone_account_params_cpim_in_basic_chat_room_enabled(currentParams) ) {
LOGI(@"Enabling CPIM in basic chatroom for account [%s]", addressIdentity);
linphone_account_params_enable_cpim_in_basic_chat_room(newParams, true);
}
const char* current_lime_url = linphone_account_params_get_lime_server_url(currentParams);
if (!current_lime_url){
const char* core_lime_url = linphone_core_get_lime_x3dh_server_url(LC);
if (core_lime_url) {
LOGI(@"Copying core's LIME X3DH server URL [%s] to account [%s]", core_lime_url, addressIdentity);
linphone_account_params_set_lime_server_url(newParams, core_lime_url);
} else {
LOGI(@"Account [%s] didn't have a LIME X3DH server URL, setting one: [%s]", addressIdentity, core_lime_url);
linphone_account_params_set_lime_server_url(newParams, "https://lime.linphone.org/lime-server/lime-server.php");
}
}
linphone_account_set_params(account, newParams); linphone_account_set_params(account, newParams);
linphone_account_params_unref(newParams); linphone_account_params_unref(newParams);
} }
ms_free(addressIdentity);
accountsList = accountsList->next; accountsList = accountsList->next;
} }
} }
@ -1370,7 +1456,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
linphone_core_start([LinphoneManager getLc]); linphone_core_start([LinphoneManager getLc]);
[self configurePushProviderForAccounts]; [self configurePushProviderForAccounts];
[self activateBasicChatroomCPIMForLinphoneAccounts]; [self enableLinphoneAccountSpecificSettings];
} }
- (void)createLinphoneCore { - (void)createLinphoneCore {
@ -1439,7 +1525,6 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
MSFactory *f = linphone_core_get_ms_factory(theLinphoneCore); MSFactory *f = linphone_core_get_ms_factory(theLinphoneCore);
libmssilk_init(f); libmssilk_init(f);
libmsamr_init(f); libmsamr_init(f);
libmsx264_init(f);
libmsopenh264_init(f); libmsopenh264_init(f);
libmswebrtc_init(f); libmswebrtc_init(f);
libmscodec2_init(f); libmscodec2_init(f);
@ -1472,6 +1557,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
- (void)destroyLinphoneCore { - (void)destroyLinphoneCore {
// just in case // just in case
[self removeCTCallCenterCb]; [self removeCTCallCenterCb];
[MagicSearchSingleton destroyInstance];
if (theLinphoneCore != nil) { // just in case application terminate before linphone core initialization if (theLinphoneCore != nil) { // just in case application terminate before linphone core initialization
@ -1501,8 +1587,6 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
- (void)resetLinphoneCore { - (void)resetLinphoneCore {
[self destroyLinphoneCore]; [self destroyLinphoneCore];
[self createLinphoneCore]; [self createLinphoneCore];
// reload friends
[self.fastAddressBook fetchContactsInBackGroundThread];
} }
static int comp_call_id(const LinphoneCall *call, const char *callid) { static int comp_call_id(const LinphoneCall *call, const char *callid) {
@ -1817,7 +1901,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
LinphoneChatMessage *msg = linphone_chat_room_create_message(room, replyText.UTF8String); LinphoneChatMessage *msg = linphone_chat_room_create_message(room, replyText.UTF8String);
linphone_chat_message_send(msg); linphone_chat_message_send(msg);
[ChatConversationView markAsRead:room]; [ChatConversationViewSwift markAsRead:room];
} }
/* /*
@ -1900,7 +1984,10 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
} }
[self checkLocalNetworkPermission]; [self checkLocalNetworkPermission];
// For OutgoingCall, show CallOutgoingView // For OutgoingCall, show CallOutgoingView
[CallManager.instance startCallWithAddr:iaddr isSas:FALSE isVideo:false isConference:false]; LinphoneVideoActivationPolicy *policy = linphone_core_get_video_activation_policy(LC);
BOOL initiateVideoCall = linphone_video_activation_policy_get_automatically_initiate(policy);
[CallManager.instance startCallWithAddr:iaddr isSas:FALSE isVideo:initiateVideoCall isConference:false];
linphone_video_activation_policy_unref(policy);
} }
#pragma mark - Misc Functions #pragma mark - Misc Functions
@ -2226,6 +2313,23 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
#pragma mark - #pragma mark -
- (MSList *) createAccountsNotHiddenList {
MSList *list = NULL;
const MSList *accounts = linphone_core_get_account_list(LC);
while (accounts) {
const char *isHidden = linphone_account_get_custom_param(accounts->data, "hidden");
if (isHidden == NULL || strcmp(linphone_account_get_custom_param(accounts->data, "hidden"), "1") != 0) {
if (!list) {
list = bctbx_list_new(accounts->data);
} else {
bctbx_list_append(list, accounts->data);
}
}
accounts = accounts->next;
}
return list;
}
- (void)removeAllAccounts { - (void)removeAllAccounts {
linphone_core_clear_accounts(LC); linphone_core_clear_accounts(LC);
linphone_core_clear_all_auth_info(LC); linphone_core_clear_all_auth_info(LC);
@ -2319,6 +2423,31 @@ void linphone_iphone_conference_state_changed(LinphoneCore *lc, LinphoneConferen
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneConfStateChanged object:nil userInfo:dict]; [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneConfStateChanged object:nil userInfo:dict];
} }
+ (BOOL) getChatroomPushEnabled:(LinphoneChatRoom *)chatroom {
bool currently_enabled = true;
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
NSDictionary *chatroomsPushStatus = [defaults dictionaryForKey:@"chatroomsPushStatus"];
if (chatroomsPushStatus != nil && chatroom) {
char *uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(chatroom));
NSString* pushStatus = [chatroomsPushStatus objectForKey:[NSString stringWithUTF8String:uri]];
currently_enabled = (pushStatus == nil) || [pushStatus isEqualToString:@"enabled"];
ms_free(uri);
}
return currently_enabled;
}
+ (void) setChatroomPushEnabled:(LinphoneChatRoom *)chatroom withPushEnabled:(BOOL)enabled {
if (!chatroom) return;
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:kLinphoneMsgNotificationAppGroupId];
NSMutableDictionary *chatroomsPushStatus = [[NSMutableDictionary alloc] initWithDictionary:[defaults dictionaryForKey:@"chatroomsPushStatus"]];
if (chatroomsPushStatus == nil) chatroomsPushStatus = [[NSMutableDictionary dictionary] init];
char *uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(chatroom));
[chatroomsPushStatus setValue:(enabled ? @"enabled" : @"disabled") forKey:[NSString stringWithUTF8String:uri]];
ms_free(uri);
[defaults setObject:chatroomsPushStatus forKey:@"chatroomsPushStatus"];
}
@end @end

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -81,6 +81,22 @@
<rect key="frame" x="0.0" y="0.0" width="365" height="357"/> <rect key="frame" x="0.0" y="0.0" width="365" height="357"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="LPj-VT-0fH" userLabel="imdmIcon">
<rect key="frame" x="372" y="342" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ephemeral_messages_color_A.png" id="7JB-ZL-0lZ" userLabel="ephemeralIcon">
<rect key="frame" x="352" y="346" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="IRV-qN-sRj" userLabel="ephemeralTime">
<rect key="frame" x="285" y="346" width="65" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
<color key="textColor" red="1" green="0.36862745099999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VAJ-tE-fsa"> <view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VAJ-tE-fsa">
<rect key="frame" x="0.0" y="10" width="382" height="347"/> <rect key="frame" x="0.0" y="10" width="382" height="347"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -185,22 +201,6 @@
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/> <dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView> </textView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="LPj-VT-0fH" userLabel="imdmIcon">
<rect key="frame" x="372" y="337" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<accessibility key="accessibilityConfiguration" label="Delivery failed"/>
</imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ephemeral_messages_color_A.png" id="7JB-ZL-0lZ" userLabel="ephemeralIcon">
<rect key="frame" x="351" y="336" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="IRV-qN-sRj" userLabel="ephemeralTime">
<rect key="frame" x="282" y="336" width="65" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="9"/>
<color key="textColor" red="1" green="0.36862745099999999" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<view tag="28021" contentMode="scaleToFill" id="bhq-9n-zYF" userLabel="voiceRecording"> <view tag="28021" contentMode="scaleToFill" id="bhq-9n-zYF" userLabel="voiceRecording">
<rect key="frame" x="7" y="252" width="351" height="60"/> <rect key="frame" x="7" y="252" width="351" height="60"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES"/>
@ -261,7 +261,7 @@
<image name="color_M.png" width="2" height="2"/> <image name="color_M.png" width="2" height="2"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/> <image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="linphone_logo.png" width="41.599998474121094" height="42.400001525878906"/> <image name="linphone_logo.png" width="41.599998474121094" height="42.400001525878906"/>
<image name="menu_reply_default.png" width="25" height="25"/> <image name="menu_reply_default.png" width="60" height="60"/>
<image name="vr_play.png" width="200" height="200"/> <image name="vr_play.png" width="200" height="200"/>
<image name="vr_wave.png" width="1078" height="90"/> <image name="vr_wave.png" width="1078" height="90"/>
</resources> </resources>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -27,7 +27,7 @@
<rect key="frame" x="0.0" y="0.0" width="189" height="64"/> <rect key="frame" x="0.0" y="0.0" width="189" height="64"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="hBI-Xz-aEV" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="hBI-Xz-aEV" userLabel="avatarImage">
<rect key="frame" x="6" y="20" width="27" height="27"/> <rect key="frame" x="6" y="20" width="27" height="27"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
@ -59,14 +59,14 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<textView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Lore ipsum..." translatesAutoresizingMaskIntoConstraints="NO" id="CYa-If-oB4" userLabel="messageText" customClass="UITextViewNoDefine"> <textView clipsSubviews="YES" contentMode="scaleToFill" fixedFrame="YES" scrollEnabled="NO" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" editable="NO" text="Lore ipsum..." translatesAutoresizingMaskIntoConstraints="NO" id="CYa-If-oB4" userLabel="messageText" customClass="UITextViewNoDefine">
<rect key="frame" x="0.0" y="0.0" width="126" height="40"/> <rect key="frame" x="4" y="0.0" width="118" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" heightSizable="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/> <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
<dataDetectorType key="dataDetectorTypes" link="YES"/> <dataDetectorType key="dataDetectorTypes" link="YES"/>
</textView> </textView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="Nod-GX-0kg" userLabel="imdmIcon"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="chat_read.png" translatesAutoresizingMaskIntoConstraints="NO" id="Nod-GX-0kg" userLabel="imdmIcon">
<rect key="frame" x="133" y="30" width="10" height="10"/> <rect key="frame" x="133" y="28" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/>
</imageView> </imageView>
<label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GDJ-O8-m6J" userLabel="ephemeralTime"> <label hidden="YES" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="00:00:00" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GDJ-O8-m6J" userLabel="ephemeralTime">
@ -93,6 +93,6 @@
<image name="chat_read.png" width="20" height="20"/> <image name="chat_read.png" width="20" height="20"/>
<image name="color_A.png" width="2" height="2"/> <image name="color_A.png" width="2" height="2"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/> <image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="menu_reply_default.png" width="25" height="25"/> <image name="menu_reply_default.png" width="60" height="60"/>
</resources> </resources>
</document> </document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="18122" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21225" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="18093"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21207"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -26,7 +26,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="86"/> <rect key="frame" x="0.0" y="0.0" width="375" height="86"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="19" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="19" userLabel="avatarImage" customClass="UIImageView">
<rect key="frame" x="10" y="11" width="42" height="42"/> <rect key="frame" x="10" y="11" width="42" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
@ -66,15 +66,11 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<view hidden="YES" autoresizesSubviews="NO" userInteractionEnabled="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7DE-KJ-9Q3" userLabel="unreadCountView" customClass="UIBouncingView"> <view hidden="YES" autoresizesSubviews="NO" userInteractionEnabled="NO" tag="7" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7DE-KJ-9Q3" userLabel="unreadCountView" customClass="UIBouncingView">
<rect key="frame" x="338" y="12" width="21" height="21"/> <rect key="frame" x="338" y="12" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" tag="8" contentMode="scaleAspectFit" fixedFrame="YES" image="chat_list_indicator.png" translatesAutoresizingMaskIntoConstraints="NO" id="NXj-A8-YLh" userLabel="unreadCountImage">
<rect key="frame" x="0.0" y="0.0" width="21" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="99" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="7" translatesAutoresizingMaskIntoConstraints="NO" id="ZXq-Do-7Ua" userLabel="unreadCountLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="9" contentMode="left" fixedFrame="YES" text="99" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="7" translatesAutoresizingMaskIntoConstraints="NO" id="ZXq-Do-7Ua" userLabel="unreadCountLabel">
<rect key="frame" x="0.0" y="0.0" width="21" height="21"/> <rect key="frame" x="0.0" y="0.0" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration"> <accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" none="YES"/> <accessibilityTraits key="traits" none="YES"/>
@ -88,11 +84,11 @@
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="forward_message_default.png" id="rbY-QS-6QH" userLabel="transferIcon"> <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="forward_message_default.png" id="rbY-QS-6QH" userLabel="transferIcon">
<rect key="frame" x="338" y="33" width="21" height="21"/> <rect key="frame" x="338" y="33" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="q18-yi-ol3"> <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="ephemeral_messages_color_A.png" translatesAutoresizingMaskIntoConstraints="NO" id="q18-yi-ol3">
<rect key="frame" x="338" y="54" width="21" height="21"/> <rect key="frame" x="338" y="54" width="20" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
</subviews> </subviews>
@ -103,7 +99,6 @@
</objects> </objects>
<resources> <resources>
<image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/> <image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="chat_list_indicator.png" width="28" height="28"/>
<image name="chat_read.png" width="20" height="20"/> <image name="chat_read.png" width="20" height="20"/>
<image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/> <image name="ephemeral_messages_color_A.png" width="136" height="158.39999389648438"/>
<image name="forward_message_default.png" width="187" height="148"/> <image name="forward_message_default.png" width="187" height="148"/>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -23,7 +23,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/> <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z2U-vm-azg" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z2U-vm-azg" userLabel="avatarImage">
<rect key="frame" x="0.0" y="8" width="44" height="28"/> <rect key="frame" x="0.0" y="8" width="44" height="28"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</imageView> </imageView>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina5_5" orientation="landscape" appearance="light"/> <device id="retina5_5" orientation="landscape" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -61,7 +61,7 @@
<action selector="onDelete:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="gSd-t2-eDY"/> <action selector="onDelete:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="gSd-t2-eDY"/>
</connections> </connections>
</button> </button>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="Zsv-H9-9Dv" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="Zsv-H9-9Dv" userLabel="avatarImage">
<rect key="frame" x="8" y="0.0" width="35" height="42"/> <rect key="frame" x="8" y="0.0" width="35" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -47,7 +47,7 @@
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<accessibility key="accessibilityConfiguration" label="Linphone"/> <accessibility key="accessibilityConfiguration" label="Linphone"/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="ktO-jm-Ra6" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="ktO-jm-Ra6" userLabel="avatarImage">
<rect key="frame" x="10" y="10" width="40" height="40"/> <rect key="frame" x="10" y="10" width="40" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
@ -61,7 +61,7 @@
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view> </view>
</subviews> </subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="890.39999999999998" y="192.50374812593705"/> <point key="canvasLocation" x="890.39999999999998" y="192.50374812593705"/>

View file

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="19162" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19144"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="UIChatCreateCollectionViewCell">
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fHV-en-AZD" userLabel="displayNameLabel">
<rect key="frame" x="14" y="18" width="92" height="20"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" image="conference_delete.png" translatesAutoresizingMaskIntoConstraints="NO" id="yfP-hQ-SXb" userLabel="selectedImage">
<rect key="frame" x="1" y="23" width="10" height="10"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView>
</subviews>
</view>
<size key="customSize" width="170" height="45"/>
<connections>
<outlet property="nameLabel" destination="fHV-en-AZD" id="gOU-sp-v0V"/>
</connections>
<point key="canvasLocation" x="2" y="86"/>
</collectionViewCell>
</objects>
<resources>
<image name="conference_delete.png" width="17.600000381469727" height="17.600000381469727"/>
</resources>
</document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="20037" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -11,11 +11,14 @@
<connections> <connections>
<outlet property="authButton" destination="lWW-wB-FMR" id="C7W-JM-WFQ"/> <outlet property="authButton" destination="lWW-wB-FMR" id="C7W-JM-WFQ"/>
<outlet property="authView" destination="CCn-Oz-I0M" id="fSM-6k-paN"/> <outlet property="authView" destination="CCn-Oz-I0M" id="fSM-6k-paN"/>
<outlet property="backgroundColor" destination="cqN-1f-6SE" id="gjg-LB-xLT"/>
<outlet property="cancelButton" destination="B1K-CB-3of" id="KKi-Xc-ldA"/> <outlet property="cancelButton" destination="B1K-CB-3of" id="KKi-Xc-ldA"/>
<outlet property="confirmationButton" destination="SbQ-re-fGQ" id="yiv-a9-o8E"/> <outlet property="confirmationButton" destination="SbQ-re-fGQ" id="yiv-a9-o8E"/>
<outlet property="firstView" destination="ef9-Iu-Bcb" id="hKx-op-r7Z"/>
<outlet property="forwardImage" destination="1Wh-Yi-cUe" id="YQq-bt-pk1"/> <outlet property="forwardImage" destination="1Wh-Yi-cUe" id="YQq-bt-pk1"/>
<outlet property="groupCallImage" destination="SVn-4k-9yc" id="sAP-8V-ttn"/> <outlet property="groupCallImage" destination="SVn-4k-9yc" id="sAP-8V-ttn"/>
<outlet property="securityImage" destination="bbo-g3-bGy" id="qZa-li-yrl"/> <outlet property="securityImage" destination="bbo-g3-bGy" id="qZa-li-yrl"/>
<outlet property="subscribeLabel" destination="Xbl-Qs-GaE" id="Qnf-pA-nL0"/>
<outlet property="titleLabel" destination="jLz-g1-cTe" id="qaj-OB-2r1"/> <outlet property="titleLabel" destination="jLz-g1-cTe" id="qaj-OB-2r1"/>
<outlet property="view" destination="2Vb-Xy-rci" id="nNw-EJ-AY3"/> <outlet property="view" destination="2Vb-Xy-rci" id="nNw-EJ-AY3"/>
</connections> </connections>
@ -25,27 +28,32 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" alpha="0.89999999999999991" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_C.png" translatesAutoresizingMaskIntoConstraints="NO" id="cqN-1f-6SE" userLabel="backgroundColor"> <view contentMode="scaleToFill" id="ef9-Iu-Bcb" userLabel="firstView">
<rect key="frame" x="0.0" y="0.0" width="377" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView> </view>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2BQ-o9-xv2"> <view contentMode="scaleToFill" id="2BQ-o9-xv2">
<rect key="frame" x="28" y="139" width="320" height="365"/> <rect key="frame" x="25" y="114" width="325" height="440"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="color_C.png" translatesAutoresizingMaskIntoConstraints="NO" id="cqN-1f-6SE" userLabel="backgroundColor">
<rect key="frame" x="-17" y="-17" width="360" height="474"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.26666668059999998" green="0.26666668059999998" blue="0.26666668059999998" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" image="security_2_indicator.png" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-g3-bGy" userLabel="securityImage"> <imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" insetsLayoutMarginsFromSafeArea="NO" image="security_2_indicator.png" translatesAutoresizingMaskIntoConstraints="NO" id="bbo-g3-bGy" userLabel="securityImage">
<rect key="frame" x="130" y="0.0" width="56" height="68"/> <rect key="frame" x="130" y="15" width="64" height="68"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Are you sure you want to delete all your selection?" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="jLz-g1-cTe" userLabel="titleLabel"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Are you sure you want to delete all your selection?" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="jLz-g1-cTe" userLabel="titleLabel">
<rect key="frame" x="-10" y="15" width="336" height="279"/> <rect key="frame" x="-8" y="15" width="347" height="350"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="21"/> <fontDescription key="fontDescription" type="system" pointSize="21"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="B1K-CB-3of" userLabel="cancelButton" customClass="UIRoundBorderedButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="B1K-CB-3of" userLabel="cancelButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="8" y="308" width="139" height="42"/> <rect key="frame" x="16" y="383" width="139" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" title="CANCEL" backgroundImage="color_H.png"> <state key="normal" title="CANCEL" backgroundImage="color_H.png">
<color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -57,11 +65,11 @@
</connections> </connections>
</button> </button>
<view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CCn-Oz-I0M" userLabel="authView"> <view hidden="YES" contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="CCn-Oz-I0M" userLabel="authView">
<rect key="frame" x="61" y="273" width="240" height="27"/> <rect key="frame" x="65" y="345" width="243" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lWW-wB-FMR" userLabel="authButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lWW-wB-FMR" userLabel="authButton">
<rect key="frame" x="26" y="2" width="17" height="22"/> <rect key="frame" x="24" y="0.0" width="22" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" image="checkbox_unchecked.png"/> <state key="normal" image="checkbox_unchecked.png"/>
<state key="selected" image="checkbox_checked.png"/> <state key="selected" image="checkbox_checked.png"/>
@ -70,7 +78,7 @@
</connections> </connections>
</button> </button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Do not show again" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQL-Sf-slc"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Do not show again" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="dQL-Sf-slc">
<rect key="frame" x="55" y="2" width="176" height="21"/> <rect key="frame" x="57" y="10" width="173" height="15"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@ -79,7 +87,7 @@
</subviews> </subviews>
</view> </view>
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SbQ-re-fGQ" userLabel="confirmationButton" customClass="UIRoundBorderedButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="SbQ-re-fGQ" userLabel="confirmationButton" customClass="UIRoundBorderedButton">
<rect key="frame" x="169" y="308" width="143" height="42"/> <rect key="frame" x="177" y="383" width="136" height="36"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<state key="normal" title="DELETE" backgroundImage="color_I.png"> <state key="normal" title="DELETE" backgroundImage="color_I.png">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -91,20 +99,34 @@
</connections> </connections>
</button> </button>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="forward_message_default.png" translatesAutoresizingMaskIntoConstraints="NO" id="1Wh-Yi-cUe"> <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="forward_message_default.png" translatesAutoresizingMaskIntoConstraints="NO" id="1Wh-Yi-cUe">
<rect key="frame" x="89" y="50" width="138" height="54"/> <rect key="frame" x="100" y="50" width="136" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="voip_conference_new.png" translatesAutoresizingMaskIntoConstraints="NO" id="SVn-4k-9yc"> <imageView hidden="YES" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="voip_conference_new.png" translatesAutoresizingMaskIntoConstraints="NO" id="SVn-4k-9yc">
<rect key="frame" x="89" y="50" width="138" height="54"/> <rect key="frame" x="100" y="50" width="136" height="54"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxX="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<label hidden="YES" opaque="NO" tag="13" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="https://subscribe.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Xbl-Qs-GaE" userLabel="subscribeLabel">
<rect key="frame" x="-42" y="291" width="414" height="29"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.98766469960000003" green="0.27512490750000002" blue="0.029739789660000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<connections>
<outletCollection property="gestureRecognizers" destination="tfG-O1-yfD" appends="YES" id="Jcg-KH-U5B"/>
</connections>
</label>
</subviews> </subviews>
</view> </view>
</subviews> </subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
<point key="canvasLocation" x="871.20000000000005" y="261.31934032983509"/> <point key="canvasLocation" x="548" y="34"/>
</view> </view>
<tapGestureRecognizer id="tfG-O1-yfD" userLabel="onSubscribeTap">
<connections>
<action selector="onSubscribeTap:" destination="-1" id="Zq2-mP-ccM"/>
</connections>
</tapGestureRecognizer>
</objects> </objects>
<resources> <resources>
<image name="checkbox_checked.png" width="27.200000762939453" height="27.200000762939453"/> <image name="checkbox_checked.png" width="27.200000762939453" height="27.200000762939453"/>

View file

@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait" appearance="light"/>
<adaptation id="fullscreen"/>
</device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -14,37 +12,47 @@
<outlet property="avatarImage" destination="23" id="24"/> <outlet property="avatarImage" destination="23" id="24"/>
<outlet property="linphoneImage" destination="25" id="27"/> <outlet property="linphoneImage" destination="25" id="27"/>
<outlet property="nameLabel" destination="6" id="26"/> <outlet property="nameLabel" destination="6" id="26"/>
<outlet property="organizationLabel" destination="fva-Hf-er8" id="Ib6-rS-ybW"/>
</connections> </connections>
</placeholder> </placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="16"> <view contentMode="scaleToFill" id="16">
<rect key="frame" x="0.0" y="0.0" width="360" height="44"/> <rect key="frame" x="0.0" y="0.0" width="360" height="49"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" image="avatar.png" id="23" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="23" userLabel="avatarImage">
<rect key="frame" x="6" y="6" width="32" height="32"/> <rect key="frame" x="6" y="9" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="2" contentMode="left" text="John Doe" lineBreakMode="tailTruncation" minimumFontSize="10" id="6" userLabel="nameLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="2" contentMode="left" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="6" userLabel="nameLabel">
<rect key="frame" x="46" y="0.0" width="256" height="44"/> <rect key="frame" x="46" y="4" width="256" height="39"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" label="Firstname"/> <accessibility key="accessibilityConfiguration" label="Firstname"/>
<fontDescription key="fontDescription" type="system" pointSize="21"/> <fontDescription key="fontDescription" type="system" pointSize="21"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" misplaced="YES" image="linphone_user.png" id="25" userLabel="linphoneImage"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" fixedFrame="YES" image="linphone_user.png" translatesAutoresizingMaskIntoConstraints="NO" id="25" userLabel="linphoneImage">
<rect key="frame" x="319" y="10" width="25" height="25"/> <rect key="frame" x="319" y="13" width="25" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="2" contentMode="left" fixedFrame="YES" text="Organization" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="fva-Hf-er8" userLabel="organizationLabel">
<rect key="frame" x="46" y="29" width="256" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration" label="Firstname"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<nil key="highlightedColor"/>
</label>
</subviews> </subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="137.59999999999999" y="-10.794602698650676"/>
</view> </view>
</objects> </objects>
<resources> <resources>
<image name="avatar.png" width="259" height="259"/> <image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="linphone_user.png" width="26" height="26"/> <image name="linphone_user.png" width="41.599998474121094" height="42.400001525878906"/>
</resources> </resources>
</document> </document>

View file

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/> <capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -61,7 +61,7 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="88"/> <rect key="frame" x="0.0" y="0.0" width="375" height="88"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES" heightSizable="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="john.doe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="frB-ep-LWi" userLabel="addressLabel"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="john.doe@sip.linphone.org" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="frB-ep-LWi" userLabel="addressLabel" customClass="CopyableLabel" customModule="linphoneapp" customModuleProvider="target">
<rect key="frame" x="26" y="0.0" width="323" height="44"/> <rect key="frame" x="26" y="0.0" width="323" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/> <fontDescription key="fontDescription" type="system" pointSize="17"/>

View file

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9060" systemVersion="15B42" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9051"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UIHistoryCell"> <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="UIHistoryCell">
@ -18,31 +20,27 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/> <rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" image="avatar.png" id="J9B-Wl-Qgm" userLabel="avatarImage" customClass="UIRoundedImageView"> <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" fixedFrame="YES" image="avatar.png" translatesAutoresizingMaskIntoConstraints="NO" id="J9B-Wl-Qgm" userLabel="avatarImage">
<rect key="frame" x="6" y="6" width="32" height="32"/> <rect key="frame" x="6" y="6" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<animations/>
</imageView> </imageView>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="history_missed_default.png" id="Jpe-IK-xK1" userLabel="stateImage"> <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="history_missed_default.png" translatesAutoresizingMaskIntoConstraints="NO" id="Jpe-IK-xK1" userLabel="stateImage">
<rect key="frame" x="46" y="6" width="32" height="32"/> <rect key="frame" x="46" y="6" width="32" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<animations/>
</imageView> </imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="2" contentMode="left" text="John Doe" lineBreakMode="tailTruncation" minimumFontSize="10" id="zG2-Kg-0jD" userLabel="displayNameLabel"> <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" tag="2" contentMode="left" fixedFrame="YES" text="John Doe" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="zG2-Kg-0jD" userLabel="displayNameLabel">
<rect key="frame" x="86" y="0.0" width="237" height="44"/> <rect key="frame" x="86" y="0.0" width="237" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<accessibility key="accessibilityConfiguration" label="Firstname"/> <accessibility key="accessibilityConfiguration" label="Firstname"/>
<fontDescription key="fontDescription" type="system" pointSize="21"/> <fontDescription key="fontDescription" type="system" pointSize="21"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" id="Lfl-dI-bSt" userLabel="detailsButton" customClass="UIIconButton"> <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Lfl-dI-bSt" userLabel="detailsButton" customClass="UIIconButton">
<rect key="frame" x="331" y="0.0" width="44" height="44"/> <rect key="frame" x="331" y="0.0" width="44" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<animations/>
<state key="normal" image="list_details_default.png"> <state key="normal" image="list_details_default.png">
<color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/> <color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state> </state>
<state key="highlighted" image="list_details_over.png"/> <state key="highlighted" image="list_details_over.png"/>
<connections> <connections>
@ -50,17 +48,16 @@
</connections> </connections>
</button> </button>
</subviews> </subviews>
<animations/> <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<nil key="simulatedStatusBarMetrics"/> <nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="366.5" y="248"/> <point key="canvasLocation" x="559.5419847328244" y="174.64788732394368"/>
</view> </view>
</objects> </objects>
<resources> <resources>
<image name="avatar.png" width="255" height="255"/> <image name="avatar.png" width="414.39999389648438" height="414.39999389648438"/>
<image name="history_missed_default.png" width="32" height="32"/> <image name="history_missed_default.png" width="52.799999237060547" height="52.799999237060547"/>
<image name="list_details_default.png" width="33" height="34"/> <image name="list_details_default.png" width="54.400001525878906" height="55.200000762939453"/>
<image name="list_details_over.png" width="33" height="34"/> <image name="list_details_over.png" width="54.400001525878906" height="55.200000762939453"/>
</resources> </resources>
</document> </document>

View file

@ -194,11 +194,13 @@
message = NSLocalizedString(@"Fetching remote configuration", nil); message = NSLocalizedString(@"Fetching remote configuration", nil);
} else if (account == NULL) { } else if (account == NULL) {
state = LinphoneRegistrationNone; state = LinphoneRegistrationNone;
if (linphone_core_get_account_list(LC) != NULL) { MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
if (accounts != NULL) {
message = NSLocalizedString(@"No default account", nil); message = NSLocalizedString(@"No default account", nil);
} else { } else {
message = NSLocalizedString(@"No account configured", nil); message = NSLocalizedString(@"No account configured", nil);
} }
bctbx_free(accounts);
} else { } else {
state = linphone_account_get_state(account); state = linphone_account_get_state(account);
@ -330,14 +332,9 @@
correspondantCode = [code substringToIndex:2]; correspondantCode = [code substringToIndex:2];
myCode = [code substringFromIndex:2]; myCode = [code substringFromIndex:2];
} }
NSString *message = NSString *message = [NSString stringWithFormat:NSLocalizedString(@"\nCommunication security:\n\nTo raise the security level, you can check the following codes with your correspondent.\n\nSay: %1$@\n\nYour correspondent must say: %2$@", nil),
[NSString stringWithFormat:NSLocalizedString(@"\nConfirmation security\n\n" myCode.uppercaseString, correspondantCode.uppercaseString];
@"Say: %@\n"
@"Confirm that your interlocutor\n"
@"says: %@",
nil),
myCode.uppercaseString, correspondantCode.uppercaseString];
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive && if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive &&
floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init]; UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
@ -369,14 +366,14 @@
UIFont *baseFont = [UIFont systemFontOfSize:21.0]; UIFont *baseFont = [UIFont systemFontOfSize:21.0];
[attrString addAttribute:NSFontAttributeName value:baseFont range:NSMakeRange(0, length)]; [attrString addAttribute:NSFontAttributeName value:baseFont range:NSMakeRange(0, length)];
UIFont *boldFont = [UIFont boldSystemFontOfSize:23.0]; UIFont *boldFont = [UIFont boldSystemFontOfSize:23.0];
[attrString addAttribute:NSFontAttributeName value:boldFont range:[message rangeOfString:@"Confirmation security"]]; [attrString addAttribute:NSFontAttributeName value:boldFont range:[message rangeOfString:@"Communication security"]];
UIColor *color = [UIColor colorWithRed:(150 / 255.0) green:(193 / 255.0) blue:(31 / 255.0) alpha:1.0]; UIColor *color = [UIColor colorWithRed:(150 / 255.0) green:(193 / 255.0) blue:(31 / 255.0) alpha:1.0];
[attrString addAttribute:NSForegroundColorAttributeName value:color range:[message rangeOfString:myCode.uppercaseString]]; [attrString addAttribute:NSForegroundColorAttributeName value:color range:[message rangeOfString:myCode.uppercaseString]];
[attrString addAttribute:NSForegroundColorAttributeName value:color range:[message rangeOfString:correspondantCode.uppercaseString]]; [attrString addAttribute:NSForegroundColorAttributeName value:color range:[message rangeOfString:correspondantCode.uppercaseString]];
securityDialog = [UIConfirmationDialog ShowWithAttributedMessage:attrString securityDialog = [UIConfirmationDialog ShowWithAttributedMessage:attrString
cancelMessage:NSLocalizedString(@"DENY", nil) cancelMessage:NSLocalizedString(@"Later", nil)
confirmMessage:NSLocalizedString(@"ACCEPT", nil) confirmMessage:NSLocalizedString(@"Correct", nil)
onCancelClick:^() { onCancelClick:^() {
if (linphone_core_get_current_call(LC) == call) { if (linphone_core_get_current_call(LC) == call) {
linphone_call_set_authentication_token_verified(call, NO); linphone_call_set_authentication_token_verified(call, NO);
@ -394,6 +391,7 @@
securityDialog.securityImage.hidden = FALSE; securityDialog.securityImage.hidden = FALSE;
[securityDialog setSpecialColor]; [securityDialog setSpecialColor];
[securityDialog setWhiteCancel];
} }
} }
} }
@ -405,7 +403,6 @@
[ControlsViewModelBridge toggleStatsVisibility]; [ControlsViewModelBridge toggleStatsVisibility];
} }
- (IBAction)onSideMenuClick:(id)sender { - (IBAction)onSideMenuClick:(id)sender {
UICompositeView *cvc = PhoneMainView.instance.mainViewController; UICompositeView *cvc = PhoneMainView.instance.mainViewController;
[cvc hideSideMenu:(cvc.sideMenuView.frame.origin.x == 0)]; [cvc hideSideMenu:(cvc.sideMenuView.frame.origin.x == 0)];
@ -415,10 +412,15 @@
- (IBAction)onRegistrationStateClick:(id)sender { - (IBAction)onRegistrationStateClick:(id)sender {
if (linphone_core_get_default_account(LC)) { if (linphone_core_get_default_account(LC)) {
linphone_core_refresh_registers(LC); linphone_core_refresh_registers(LC);
} else if (linphone_core_get_account_list(LC)) {
[PhoneMainView.instance changeCurrentView:SettingsView.compositeViewDescription];
} else { } else {
[PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription];
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
if (accounts) {
[PhoneMainView.instance changeCurrentView:SettingsView.compositeViewDescription];
} else {
[PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription];
}
bctbx_free(accounts);
} }
} }

View file

@ -76,16 +76,20 @@
- (void)update:(BOOL)appear { - (void)update:(BOOL)appear {
[self updateSelectedButton:[PhoneMainView.instance currentView]]; [self updateSelectedButton:[PhoneMainView.instance currentView]];
[self updateMissedCall:linphone_core_get_missed_calls_count(LC) appear:appear]; [self updateMissedCall:linphone_core_get_missed_calls_count(LC) appear:appear];
[self updateUnreadMessage:appear]; if (![LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"]) {
[self updateUnreadMessage:appear];
}
} }
- (void)updateUnreadMessage:(BOOL)appear { - (void)updateUnreadMessage:(BOOL)appear {
int unreadMessage = [LinphoneManager unreadMessageCount]; int unreadMessage = [LinphoneManager unreadMessageCount];
if (unreadMessage > 0) { if (![LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"]) {
_chatNotificationLabel.text = [NSString stringWithFormat:@"%i", unreadMessage]; if (unreadMessage > 0) {
[_chatNotificationView startAnimating:appear]; _chatNotificationLabel.text = [NSString stringWithFormat:@"%i", unreadMessage];
} else { [_chatNotificationView startAnimating:appear];
[_chatNotificationView stopAnimating:appear]; } else {
[_chatNotificationView stopAnimating:appear];
}
} }
} }
@ -109,7 +113,25 @@
[view equal:ChatConversationCreateView.compositeViewDescription] || [view equal:ChatConversationCreateView.compositeViewDescription] ||
[view equal:ChatConversationInfoView.compositeViewDescription] || [view equal:ChatConversationInfoView.compositeViewDescription] ||
[view equal:ChatConversationImdnView.compositeViewDescription] || [view equal:ChatConversationImdnView.compositeViewDescription] ||
[view equal:ChatConversationView.compositeViewDescription]; [view equal:ChatConversationViewSwift.compositeViewDescription];
if ([LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"] && [self viewIsCurrentlyPortrait]) {
CGFloat itemWidth = [UIScreen mainScreen].bounds.size.width/3;
[_chatButton setEnabled:false];
[_chatButton setHidden:true];
[_chatNotificationView setHidden:true];
_historyButton.frame = CGRectMake(0, 0, itemWidth, 66);
_contactsButton.frame = CGRectMake(itemWidth, 0, itemWidth, 66);
_dialerButton.frame = CGRectMake(itemWidth*2, 0, itemWidth, 66);
_selectedButtonImage.frame = CGRectMake(_selectedButtonImage.frame.origin.x, _selectedButtonImage.frame.origin.y, itemWidth, 3);
} else if ([LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"] && ![self viewIsCurrentlyPortrait]) {
[_chatButton setEnabled:false];
[_chatButton setHidden:true];
[_chatNotificationView setHidden:true];
_historyButton.frame = CGRectMake(0, 20, 90, 90);
_contactsButton.frame = CGRectMake(0, 120, 90, 90);
_dialerButton.frame = CGRectMake(0, 220, 90, 90);
_selectedButtonImage.frame = CGRectMake(_selectedButtonImage.frame.origin.x, _selectedButtonImage.frame.origin.y, 3, 90);
}
CGRect selectedNewFrame = _selectedButtonImage.frame; CGRect selectedNewFrame = _selectedButtonImage.frame;
if ([self viewIsCurrentlyPortrait]) { if ([self viewIsCurrentlyPortrait]) {
selectedNewFrame.origin.x = selectedNewFrame.origin.x =

View file

@ -48,7 +48,7 @@
} }
- (IBAction)onBackToCallClick:(id)sender { - (IBAction)onBackToCallClick:(id)sender {
[PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription]; [PhoneMainView.instance popToView:[CallsViewModelBridge callViewToDisplay]];
} }
@end @end

View file

@ -192,14 +192,20 @@
_voiceRecordingFile = nil; _voiceRecordingFile = nil;
LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:self.message]; LinphoneContent *voiceContent = [UIChatBubbleTextCell voiceContent:self.message];
if (voiceContent) { if (voiceContent) {
_voiceRecordingFile = [NSString stringWithUTF8String:[VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] ? linphone_content_get_plain_file_path(voiceContent) : linphone_content_get_file_path(voiceContent)]; const char *fileName = ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId] ? linphone_content_get_plain_file_path(voiceContent) : linphone_content_get_file_path(voiceContent));
if ([VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) if (fileName == nil) {
linphone_content_set_file_path(voiceContent, [[LinphoneManager imagesDirectory] stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]].UTF8String);
linphone_chat_message_download_content(self.message, voiceContent);
}
_voiceRecordingFile = fileName ? [NSString stringWithUTF8String:fileName] : nil;
if (fileName && [VFSUtil vfsEnabledWithGroupName:kLinphoneMsgNotificationAppGroupId]) {
[encrptedFilePaths setValue:_voiceRecordingFile forKey:[NSString stringWithUTF8String:linphone_content_get_name(voiceContent)]]; [encrptedFilePaths setValue:_voiceRecordingFile forKey:[NSString stringWithUTF8String:linphone_content_get_name(voiceContent)]];
}
_vrTimerLabel.text = [self formattedDuration:linphone_content_get_file_duration(voiceContent)/1000]; _vrTimerLabel.text = [self formattedDuration:linphone_content_get_file_duration(voiceContent)/1000];
_vrWaveMaskPlayback.frame = CGRectZero; _vrWaveMaskPlayback.frame = CGRectZero;
_vrWaveMaskPlayback.backgroundColor = linphone_chat_message_is_outgoing(self.message) ? UIColor.orangeColor : UIColor.grayColor; _vrWaveMaskPlayback.backgroundColor = linphone_chat_message_is_outgoing(self.message) ? UIColor.orangeColor : UIColor.grayColor;
} }
const bctbx_list_t *contents = linphone_chat_message_get_contents(self.message); const bctbx_list_t *contents = linphone_chat_message_get_contents(self.message);
size_t contentCount = bctbx_list_size(contents); size_t contentCount = bctbx_list_size(contents);

View file

@ -39,7 +39,7 @@
@property(readonly, nonatomic) LinphoneEventLog *event; @property(readonly, nonatomic) LinphoneEventLog *event;
@property(readonly, nonatomic) LinphoneChatMessage *message; @property(readonly, nonatomic) LinphoneChatMessage *message;
@property(nonatomic, weak) IBOutlet UIImageView *backgroundColorImage; @property(nonatomic, weak) IBOutlet UIImageView *backgroundColorImage;
@property(nonatomic, weak) IBOutlet UIRoundedImageView *avatarImage; @property(nonatomic, weak) IBOutlet UIImageView *avatarImage;
@property(nonatomic, weak) IBOutlet UILabel *contactDateLabel; @property(nonatomic, weak) IBOutlet UILabel *contactDateLabel;
//@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *statusInProgressSpinner; //@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *statusInProgressSpinner;
@property(nonatomic, weak) IBOutlet UITextViewNoDefine *messageText; @property(nonatomic, weak) IBOutlet UITextViewNoDefine *messageText;

View file

@ -119,16 +119,21 @@
} }
- (void)setChatMessageForCbs:(LinphoneChatMessage *)amessage { - (void)setChatMessageForCbs:(LinphoneChatMessage *)amessage {
if (!amessage || amessage == _message) { if (amessage == _message) {
return; return;
} }
if (_message){
linphone_chat_message_unref(_message);
}
_message = amessage; _message = amessage;
linphone_chat_message_set_user_data(_message, (void *)CFBridgingRetain(self)); if (amessage){
LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(_message); linphone_chat_message_ref(amessage);
linphone_chat_message_cbs_set_msg_state_changed(cbs, message_status); linphone_chat_message_set_user_data(_message, (void *)CFBridgingRetain(self));
linphone_chat_message_cbs_set_participant_imdn_state_changed(cbs, participant_imdn_status); LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(_message);
linphone_chat_message_cbs_set_user_data(cbs, (void *)_event); linphone_chat_message_cbs_set_msg_state_changed(cbs, message_status);
linphone_chat_message_cbs_set_participant_imdn_state_changed(cbs, participant_imdn_status);
linphone_chat_message_cbs_set_user_data(cbs, (void *)_event);
}
} }
+ (NSString *)TextMessageForChat:(LinphoneChatMessage *)message { + (NSString *)TextMessageForChat:(LinphoneChatMessage *)message {
@ -196,9 +201,7 @@
_avatarImage.hidden = TRUE; _avatarImage.hidden = TRUE;
} else { } else {
[_avatarImage setImage:[FastAddressBook imageForAddress:linphone_chat_message_get_from_address(_message)] [_avatarImage setImage:[FastAddressBook imageForAddress:linphone_chat_message_get_from_address(_message)]];
bordered:NO
withRoundedRadius:YES];
_contactDateLabel.text = [self.class ContactDateForChat:_message]; _contactDateLabel.text = [self.class ContactDateForChat:_message];
_contactDateLabel.textAlignment = NSTextAlignmentLeft; _contactDateLabel.textAlignment = NSTextAlignmentLeft;
_avatarImage.hidden = !_isFirst; _avatarImage.hidden = !_isFirst;
@ -321,11 +324,13 @@
- (void)onDelete { - (void)onDelete {
if (_message != NULL) { if (_message != NULL) {
UITableView *tableView = VIEW(ChatConversationView).tableController.tableView; /*
UITableView *tableView = VIEW(ChatConversationViewSwift).tableController.tableView;
NSIndexPath *indexPath = [tableView indexPathForCell:self]; NSIndexPath *indexPath = [tableView indexPathForCell:self];
[tableView.dataSource tableView:tableView [tableView.dataSource tableView:tableView
commitEditingStyle:UITableViewCellEditingStyleDelete commitEditingStyle:UITableViewCellEditingStyleDelete
forRowAtIndexPath:indexPath]; forRowAtIndexPath:indexPath];
*/
} }
} }
@ -337,15 +342,17 @@ static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState st
if (!linphone_chat_message_is_outgoing(msg) || (state != LinphoneChatMessageStateFileTransferDone && state != LinphoneChatMessageStateFileTransferInProgress)) { if (!linphone_chat_message_is_outgoing(msg) || (state != LinphoneChatMessageStateFileTransferDone && state != LinphoneChatMessageStateFileTransferInProgress)) {
LinphoneEventLog *event = (LinphoneEventLog *)linphone_chat_message_cbs_get_user_data(linphone_chat_message_get_callbacks(msg)); LinphoneEventLog *event = (LinphoneEventLog *)linphone_chat_message_cbs_get_user_data(linphone_chat_message_get_callbacks(msg));
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
[view.tableController updateEventEntry:event]; //[view.tableController updateEventEntry:event];
[view.tableController scrollToBottom:true]; //[view.tableController scrollToBottom:true];
} }
} }
static void participant_imdn_status(LinphoneChatMessage* msg, const LinphoneParticipantImdnState *state) { static void participant_imdn_status(LinphoneChatMessage* msg, const LinphoneParticipantImdnState *state) {
ChatConversationImdnView *imdnView = VIEW(ChatConversationImdnView); dispatch_async(dispatch_get_main_queue(), ^{
[imdnView updateImdnList]; ChatConversationImdnView *imdnView = VIEW(ChatConversationImdnView);
[imdnView updateImdnList];
});
} }
- (void)displayImdmStatus:(LinphoneChatMessageState)state { - (void)displayImdmStatus:(LinphoneChatMessageState)state {
@ -762,7 +769,7 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
- (void)layoutSubviews { - (void)layoutSubviews {
[super layoutSubviews]; [super layoutSubviews];
if (_message != nil) { if (_message != nil) {
UITableView *tableView = VIEW(ChatConversationView).tableController.tableView; //UITableView *tableView = VIEW(ChatConversationViewSwift).tableController.tableView;
BOOL is_outgoing = linphone_chat_message_is_outgoing(_message); BOOL is_outgoing = linphone_chat_message_is_outgoing(_message);
CGRect bubbleFrame = _bubbleView.frame; CGRect bubbleFrame = _bubbleView.frame;
int available_width = self.frame.size.width; int available_width = self.frame.size.width;
@ -773,11 +780,13 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
bubbleFrame.size.width = MAX(bubbleFrame.size.width, 300); bubbleFrame.size.width = MAX(bubbleFrame.size.width, 300);
} }
/*
if (tableView.isEditing) { if (tableView.isEditing) {
origin_x = 0; origin_x = 0;
} else { } else {
origin_x = (is_outgoing ? self.frame.size.width - bubbleFrame.size.width : 0); origin_x = (is_outgoing ? self.frame.size.width - bubbleFrame.size.width : 0);
} }
*/
CGRect r = _messageText.frame; CGRect r = _messageText.frame;
r.origin.y = linphone_chat_message_is_reply(_message) ? _replyView.view.frame.origin.y + _replyView.view.frame.size.height + 5 : 3; r.origin.y = linphone_chat_message_is_reply(_message) ? _replyView.view.frame.origin.y + _replyView.view.frame.size.height + 5 : 3;
@ -848,10 +857,13 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
-(void) buildActions { -(void) buildActions {
LinphoneChatMessage *message = self.message; LinphoneChatMessage *message = self.message;
LinphoneEventLog *event = self.event;
_messageActionsTitles = [[NSMutableArray alloc] init]; _messageActionsTitles = [[NSMutableArray alloc] init];
_messageActionsBlocks = [[NSMutableArray alloc] init]; _messageActionsBlocks = [[NSMutableArray alloc] init];
_messageActionsIcons = [[NSMutableArray alloc] init]; _messageActionsIcons = [[NSMutableArray alloc] init];
[VIEW(ChatConversationView).messageField resignFirstResponder];
UIChatBubbleTextCell *thiz = self; UIChatBubbleTextCell *thiz = self;
LinphoneChatMessageState state = linphone_chat_message_get_state(self.message); LinphoneChatMessageState state = linphone_chat_message_get_state(self.message);
@ -883,7 +895,7 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
[_messageActionsIcons addObject:@"menu_forward_default"]; [_messageActionsIcons addObject:@"menu_forward_default"];
[_messageActionsBlocks addObject:^{ [_messageActionsBlocks addObject:^{
[thiz dismissPopup]; [thiz dismissPopup];
VIEW(ChatConversationView).pendingForwardMessage = linphone_chat_message_ref(message); VIEW(ChatConversationViewSwift).pendingForwardMessage = message;
[PhoneMainView.instance changeCurrentView:VIEW(ChatsListView).compositeViewDescription]; [PhoneMainView.instance changeCurrentView:VIEW(ChatsListView).compositeViewDescription];
}]; }];
@ -893,26 +905,53 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
[_messageActionsIcons addObject:@"menu_reply_default"]; [_messageActionsIcons addObject:@"menu_reply_default"];
[_messageActionsBlocks addObject:^{ [_messageActionsBlocks addObject:^{
[thiz dismissPopup]; [thiz dismissPopup];
[VIEW(ChatConversationView) initiateReplyViewForMessage:message]; [VIEW(ChatConversationViewSwift) initiateReplyViewForMessage:message];
}]; }];
if (linphone_chat_message_is_outgoing(self.message) && linphone_chat_room_get_nb_participants(linphone_chat_message_get_chat_room(self.message)) > 1) { LinphoneChatRoom *chatroom = linphone_chat_message_get_chat_room(self.message);
if (linphone_chat_room_get_nb_participants(chatroom) > 1) {
[_messageActionsTitles addObject:NSLocalizedString(@"Infos", nil)]; [_messageActionsTitles addObject:NSLocalizedString(@"Infos", nil)];
[_messageActionsIcons addObject:@"menu_info"]; [_messageActionsIcons addObject:@"menu_info"];
[_messageActionsBlocks addObject:^{ [_messageActionsBlocks addObject:^{
[thiz dismissPopup]; [thiz dismissPopup];
ChatConversationImdnView *view = VIEW(ChatConversationImdnView); ChatConversationImdnView *view = VIEW(ChatConversationImdnView);
view.msg = message; view.event = event;
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}]; }];
} }
if (!linphone_chat_message_is_outgoing(self.message)
&& [FastAddressBook getContactWithAddress:linphone_chat_message_get_from_address(self.message)] == nil
&& !(linphone_chat_room_get_capabilities(chatroom) & LinphoneChatRoomCapabilitiesOneToOne) ) {
LinphoneAddress *fromAddress = linphone_address_clone(linphone_chat_message_get_from_address(self.message));
[_messageActionsTitles addObject:NSLocalizedString(@"Add to contact", nil)];
[_messageActionsIcons addObject:@"contact_add_default"];
[_messageActionsBlocks addObject:^{
[thiz dismissPopup];
linphone_address_clean(fromAddress);
char *lAddress = linphone_address_as_string_uri_only(fromAddress);
if (lAddress != NULL) {
NSString *normSip = [NSString stringWithUTF8String:lAddress];
normSip = [normSip hasPrefix:@"sip:"] ? [normSip substringFromIndex:4] : normSip;
normSip = [normSip hasPrefix:@"sips:"] ? [normSip substringFromIndex:5] : normSip;
[ContactSelection setAddAddress:normSip];
[ContactSelection setSelectionMode:ContactSelectionModeEdit];
[ContactSelection enableSipFilter:FALSE];
[PhoneMainView.instance changeCurrentView:ContactsListView.compositeViewDescription];
ms_free(lAddress);
}
linphone_address_unref(fromAddress);
}];
}
[_messageActionsTitles addObject:NSLocalizedString(@"Delete", nil)]; [_messageActionsTitles addObject:NSLocalizedString(@"Delete", nil)];
[_messageActionsIcons addObject:@"menu_delete"]; [_messageActionsIcons addObject:@"menu_delete"];
[_messageActionsBlocks addObject:^{ [_messageActionsBlocks addObject:^{
[thiz dismissPopup]; [thiz dismissPopup];
linphone_chat_room_delete_message(linphone_chat_message_get_chat_room(message), message); linphone_chat_room_delete_message(linphone_chat_message_get_chat_room(message), message);
[VIEW(ChatConversationView).tableController reloadData]; //[VIEW(ChatConversationViewSwift).tableController reloadData];
}]; }];
} }
@ -924,7 +963,7 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
return; return;
[VIEW(ChatConversationView).tableController dismissMessagesPopups]; //[VIEW(ChatConversationViewSwift).tableController dismissMessagesPopups];
[self buildActions]; [self buildActions];
int width = 250; int width = 250;
int cellHeight = 45; int cellHeight = 45;
@ -932,17 +971,19 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
CGRect screenRect = UIScreen.mainScreen.bounds; CGRect screenRect = UIScreen.mainScreen.bounds;
int menuHeight = numberOfItems * cellHeight; int menuHeight = numberOfItems * cellHeight;
/*
CGRect frame = CGRectMake( CGRect frame = CGRectMake(
linphone_chat_message_is_outgoing(self.message) ? screenRect.size.width - width - 10 : 10, linphone_chat_message_is_outgoing(self.message) ? screenRect.size.width - width - 10 : 10,
(self.frame.origin.y + self.frame.size.height) - [VIEW(ChatConversationView).tableController .tableView contentOffset].y > screenRect.size.height /2 ? self.frame.origin.y - menuHeight - 10: self.frame.origin.y + self.frame.size.height, (self.frame.origin.y + self.frame.size.height) - [VIEW(ChatConversationViewSwift).tableController .tableView contentOffset].y > screenRect.size.height /2 ? self.frame.origin.y - menuHeight - 10: self.frame.origin.y + self.frame.size.height,
width, width,
menuHeight); menuHeight);*/
_popupMenu = [[UITableView alloc]initWithFrame:frame]; //_popupMenu = [[UITableView alloc]initWithFrame:frame];
_popupMenu.scrollEnabled = false; _popupMenu.scrollEnabled = false;
_popupMenu.dataSource = self; _popupMenu.dataSource = self;
_popupMenu.delegate = self; _popupMenu.delegate = self;
_popupMenu.separatorStyle = UITableViewCellSeparatorStyleNone;
_popupMenu.layer.masksToBounds = false; _popupMenu.layer.masksToBounds = false;
_popupMenu.layer.shadowColor = [UIColor darkGrayColor].CGColor; _popupMenu.layer.shadowColor = [UIColor darkGrayColor].CGColor;
@ -955,11 +996,11 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
_popupMenu.editing = NO; _popupMenu.editing = NO;
_popupMenu.userInteractionEnabled = true; _popupMenu.userInteractionEnabled = true;
[_popupMenu reloadData]; [_popupMenu reloadData];
[VIEW(ChatConversationView).tableController.view addSubview:_popupMenu]; //[VIEW(ChatConversationViewSwift).tableController.view addSubview:_popupMenu];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOutsideMenu:)]; UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapOutsideMenu:)];
tapGestureRecognizer.cancelsTouchesInView = NO; tapGestureRecognizer.cancelsTouchesInView = NO;
tapGestureRecognizer.numberOfTapsRequired = 1; tapGestureRecognizer.numberOfTapsRequired = 1;
[VIEW(ChatConversationView).tableController.view addGestureRecognizer:tapGestureRecognizer]; //[VIEW(ChatConversationViewSwift).tableController.view addGestureRecognizer:tapGestureRecognizer];
} }
-(void) dismissPopup { -(void) dismissPopup {
@ -972,10 +1013,12 @@ static const CGFloat REPLY_OR_FORWARD_TAG_HEIGHT = 18;
-(void) tapOutsideMenu:(UITapGestureRecognizer *) g { -(void) tapOutsideMenu:(UITapGestureRecognizer *) g {
CGPoint p = [g locationInView:VIEW(ChatConversationView).tableController.view]; /*
CGPoint p = [g locationInView:VIEW(ChatConversationViewSwift).tableController.view];
if (!CGRectContainsPoint(_popupMenu.frame,p)) { if (!CGRectContainsPoint(_popupMenu.frame,p)) {
[self dismissPopup]; [self dismissPopup];
} }
*/
} }
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

View file

@ -29,7 +29,8 @@
LinphoneChatRoom *chatRoom; LinphoneChatRoom *chatRoom;
} }
@property(nonatomic, strong) IBOutlet UIRoundedImageView *avatarImage; @property(readonly, nonatomic) LinphoneEventLog *event;
@property(nonatomic, strong) IBOutlet UIImageView *avatarImage;
@property (weak, nonatomic) IBOutlet UIImageView *securityImage; @property (weak, nonatomic) IBOutlet UIImageView *securityImage;
@property(nonatomic, strong) IBOutlet UILabel *addressLabel; @property(nonatomic, strong) IBOutlet UILabel *addressLabel;
@property(nonatomic, strong) IBOutlet UILabel *chatContentLabel; @property(nonatomic, strong) IBOutlet UILabel *chatContentLabel;

View file

@ -21,6 +21,7 @@
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "LinphoneManager.h" #import "LinphoneManager.h"
#import "Utils.h" #import "Utils.h"
#import "linphoneapp-Swift.h"
@implementation UIChatCell @implementation UIChatCell
@ -38,6 +39,10 @@
[self addSubview:sub]; [self addSubview:sub];
} }
[_imdmIcon setHidden:TRUE]; [_imdmIcon setHidden:TRUE];
_unreadCountView.backgroundColor = VoipTheme.primary_color;
_unreadCountView.layer.cornerRadius = 10;
_unreadCountView.clipsToBounds = true;
_unreadCountLabel.textAlignment = NSTextAlignmentCenter;
return self; return self;
} }
@ -73,14 +78,15 @@
const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(chatRoom); const LinphoneAddress *addr = firstParticipant ? linphone_participant_get_address(firstParticipant) : linphone_chat_room_get_peer_address(chatRoom);
if (addr) { if (addr) {
[ContactDisplay setDisplayNameLabel:_addressLabel forAddress:addr]; [ContactDisplay setDisplayNameLabel:_addressLabel forAddress:addr];
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForAddress:addr]];
} else { } else {
_addressLabel.text = [NSString stringWithUTF8String:LINPHONE_DUMMY_SUBJECT]; _addressLabel.text = [NSString stringWithUTF8String:LINPHONE_DUMMY_SUBJECT];
} }
bctbx_list_free(participants);
} else { } else {
const char *subject = linphone_chat_room_get_subject(chatRoom); const char *subject = linphone_chat_room_get_subject(chatRoom);
_addressLabel.text = [NSString stringWithUTF8String:subject ?: LINPHONE_DUMMY_SUBJECT]; _addressLabel.text = [NSString stringWithUTF8String:subject ?: LINPHONE_DUMMY_SUBJECT];
[_avatarImage setImage:[UIImage imageNamed:@"chat_group_avatar.png"] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[UIImage imageNamed:@"chat_group_avatar.png"]];
} }
// TODO update security image when security level changed // TODO update security image when security level changed
[_securityImage setImage:[FastAddressBook imageForSecurityLevel:linphone_chat_room_get_security_level(chatRoom)]]; [_securityImage setImage:[FastAddressBook imageForSecurityLevel:linphone_chat_room_get_security_level(chatRoom)]];
@ -89,11 +95,11 @@
LinphoneChatMessage *last_msg = linphone_chat_room_get_last_message_in_history(chatRoom); LinphoneChatMessage *last_msg = linphone_chat_room_get_last_message_in_history(chatRoom);
if (last_msg) { if (last_msg) {
BOOL imdnInSnap = FALSE; BOOL imdnInSnap = TRUE;
if (imdnInSnap) { if (imdnInSnap) {
BOOL outgoing = linphone_chat_message_is_outgoing(last_msg); BOOL outgoing = linphone_chat_message_is_outgoing(last_msg);
NSString *text = [UIChatBubbleTextCell TextMessageForChat:last_msg]; NSString *text = [UIChatBubbleTextCell TextMessageForChat:last_msg];
if (outgoing) { if (capabilities & LinphoneChatRoomCapabilitiesOneToOne) {
// shorten long messages // shorten long messages
/*if ([text length] > 50) /*if ([text length] > 50)
text = [[text substringToIndex:50] stringByAppendingString:@"[...]"];*/ text = [[text substringToIndex:50] stringByAppendingString:@"[...]"];*/
@ -117,6 +123,13 @@
_chatContentLabel.attributedText = boldText; _chatContentLabel.attributedText = boldText;
} }
if (outgoing){
linphone_chat_message_set_user_data(last_msg, (void *)CFBridgingRetain(self));
LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(last_msg);
linphone_chat_message_cbs_set_msg_state_changed(cbs, message_status);
linphone_chat_message_cbs_set_participant_imdn_state_changed(cbs, participant_imdn_status);
linphone_chat_message_cbs_set_user_data(cbs, (void *)_event);
}
LinphoneChatMessageState state = linphone_chat_message_get_state(last_msg); LinphoneChatMessageState state = linphone_chat_message_get_state(last_msg);
if (outgoing && (state == LinphoneChatMessageStateDeliveredToUser || state == LinphoneChatMessageStateDisplayed || state == LinphoneChatMessageStateNotDelivered || state == LinphoneChatMessageStateFileTransferError)) { if (outgoing && (state == LinphoneChatMessageStateDeliveredToUser || state == LinphoneChatMessageStateDisplayed || state == LinphoneChatMessageStateNotDelivered || state == LinphoneChatMessageStateFileTransferError)) {
@ -132,7 +145,8 @@
_chatContentLabel.frame = newFrame; _chatContentLabel.frame = newFrame;
} }
} else { } else {
NSString *text = [[FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(last_msg)] NSString *conferenceInfo = [ICSBubbleView getConferenceSummaryWithCmessage:last_msg];
NSString *text = conferenceInfo != nil ? conferenceInfo : [[FastAddressBook displayNameForAddress:linphone_chat_message_get_from_address(last_msg)]
stringByAppendingFormat:@" : %@", [UIChatBubbleTextCell TextMessageForChat:last_msg]]; stringByAppendingFormat:@" : %@", [UIChatBubbleTextCell TextMessageForChat:last_msg]];
// shorten long messages // shorten long messages
/*if ([text length] > 50) /*if ([text length] > 50)
@ -214,4 +228,22 @@
} }
} }
static void message_status(LinphoneChatMessage *msg, LinphoneChatMessageState state) {
LOGI(@"State for message [%p] changed to %s", msg, linphone_chat_message_state_to_string(state));
if (state == LinphoneChatMessageStateFileTransferInProgress)
return;
if (!linphone_chat_message_is_outgoing(msg) || (state != LinphoneChatMessageStateFileTransferDone && state != LinphoneChatMessageStateFileTransferInProgress)) {
ChatsListView *view = VIEW(ChatsListView);
[view.tableController updateEventEntry:msg];
}
}
static void participant_imdn_status(LinphoneChatMessage* msg, const LinphoneParticipantImdnState *state) {
dispatch_async(dispatch_get_main_queue(), ^{
ChatConversationImdnView *imdnView = VIEW(ChatConversationImdnView);
[imdnView updateImdnList];
});
}
@end @end

View file

@ -22,7 +22,7 @@
@interface UIChatConversationImdnTableViewCell : UITableViewCell @interface UIChatConversationImdnTableViewCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UIRoundedImageView *avatar; @property (weak, nonatomic) IBOutlet UIImageView *avatar;
@property (weak, nonatomic) IBOutlet UILabel *displayName; @property (weak, nonatomic) IBOutlet UILabel *displayName;
@property (weak, nonatomic) IBOutlet UILabel *dateLabel; @property (weak, nonatomic) IBOutlet UILabel *dateLabel;
- (id)initWithIdentifier:(NSString *)identifier; - (id)initWithIdentifier:(NSString *)identifier;

View file

@ -22,7 +22,7 @@
@interface UIChatConversationInfoTableViewCell : UITableViewCell <UIGestureRecognizerDelegate> @interface UIChatConversationInfoTableViewCell : UITableViewCell <UIGestureRecognizerDelegate>
@property (weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage; @property (weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property (weak, nonatomic) IBOutlet UIIconButton *removeButton; @property (weak, nonatomic) IBOutlet UIIconButton *removeButton;
@property (weak, nonatomic) IBOutlet UIView *adminButton; @property (weak, nonatomic) IBOutlet UIView *adminButton;
@property (weak, nonatomic) IBOutlet UILabel *adminLabel; @property (weak, nonatomic) IBOutlet UILabel *adminLabel;

View file

@ -24,7 +24,7 @@
@property(weak, nonatomic) IBOutlet UILabel *addressLabel; @property(weak, nonatomic) IBOutlet UILabel *addressLabel;
@property (weak, nonatomic) IBOutlet UIImageView *selectedImage; @property (weak, nonatomic) IBOutlet UIImageView *selectedImage;
@property (weak, nonatomic) IBOutlet UIImageView *linphoneImage; @property (weak, nonatomic) IBOutlet UIImageView *linphoneImage;
@property (weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage; @property (weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property (weak, nonatomic) IBOutlet UIImageView *securityImage; @property (weak, nonatomic) IBOutlet UIImageView *securityImage;
@property (weak, nonatomic) IBOutlet UIView *greyView; @property (weak, nonatomic) IBOutlet UIView *greyView;

View file

@ -21,9 +21,8 @@
#import "ChatConversationCreateView.h" #import "ChatConversationCreateView.h"
@interface UIChatCreateCollectionViewCell : UICollectionViewCell @interface UIChatCreateCollectionViewCell : UICollectionViewCell
@property (weak, nonatomic) IBOutlet UILabel *nameLabel; @property UILabel *nameLabel;
@property (strong, nonatomic) ChatConversationCreateView *controller; @property (strong, nonatomic) ChatConversationCreateView *controller;
@property (strong, nonatomic) NSString *uri; @property (strong, nonatomic) NSString *uri;
- (id)initWithName:(NSString *)identifier;
- (void)onDelete; - (void)onDelete;
@end @end

View file

@ -18,27 +18,31 @@
*/ */
#import "UIChatCreateCollectionViewCell.h" #import "UIChatCreateCollectionViewCell.h"
#import "linphoneapp-Swift.h"
@implementation UIChatCreateCollectionViewCell @implementation UIChatCreateCollectionViewCell
- (void)awakeFromNib {
[super awakeFromNib];
}
- (id)initWithName:(NSString *)identifier {
if (self != nil) {
NSArray *arrayOfViews =
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
if ([arrayOfViews count] >= 1) {
UIChatCreateCollectionViewCell *sub = ((UIChatCreateCollectionViewCell *)[arrayOfViews objectAtIndex:0]);
[self addSubview:sub];
_nameLabel = sub.nameLabel;
}
}
[_nameLabel setText:identifier];
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
self.contentView.translatesAutoresizingMaskIntoConstraints = false;
[SnapkitBridge matchParentDimensionsWithView:self.contentView topInset:10];
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.nameLabel.numberOfLines = 1;
[self.contentView addSubview:self.nameLabel];
[SnapkitBridge matchParentDimensionsWithView:self.nameLabel leftInset:20];
[SnapkitBridge heightWithView:self heiht:50];
UIImageView *image = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"conference_delete"]];
image.contentMode = UIViewContentModeScaleAspectFit;
[self.contentView addSubview:image];
[SnapkitBridge squareWithView:image size:15];
[SnapkitBridge alignParentLeftWithView:image];
[SnapkitBridge centerYWithView:image];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDelete)]; UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onDelete)];
tap.numberOfTouchesRequired = 1; tap.numberOfTouchesRequired = 1;
[self addGestureRecognizer:tap]; [image addGestureRecognizer:tap];
image.userInteractionEnabled = true;
return self; return self;
} }
@ -60,4 +64,6 @@
[_controller.tableController.tableView reloadData]; [_controller.tableController.tableView reloadData];
_controller.nextButton.enabled = (_controller.tableController.contactsGroup.count > 0) || _controller.isForEditing; _controller.nextButton.enabled = (_controller.tableController.contactsGroup.count > 0) || _controller.isForEditing;
} }
@end @end

View file

@ -91,8 +91,8 @@
#pragma mark - #pragma mark -
- (void)accessoryForCell:(UITableViewCell *)cell atPath:(NSIndexPath *)indexPath { - (void)accessoryForCell:(UITableViewCell *)cell atPath:(NSIndexPath *)indexPath {
cell.selectionStyle = UITableViewCellSelectionStyleGray;
if ([self isEditing]) { if ([self isEditing]) {
cell.selectionStyle = UITableViewCellSelectionStyleGray;
UIButton *checkBoxButton = [UIButton buttonWithType:UIButtonTypeCustom]; UIButton *checkBoxButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = nil; UIImage *image = nil;
if ([_selectedItems containsObject:indexPath]) { if ([_selectedItems containsObject:indexPath]) {

View file

@ -84,6 +84,7 @@
- (UIViewController *)getCurrentViewController; - (UIViewController *)getCurrentViewController;
- (UIInterfaceOrientation)currentOrientation; - (UIInterfaceOrientation)currentOrientation;
- (void)clearCache:(NSArray *)exclude; - (void)clearCache:(NSArray *)exclude;
- (void)removeEntryFromCache:(NSString *)key;
- (IBAction)onRightSwipe:(id)sender; - (IBAction)onRightSwipe:(id)sender;

View file

@ -305,6 +305,9 @@
return nil; return nil;
} }
- (void)removeEntryFromCache:(NSString *)key {
[viewControllerCache removeObjectForKey:key];
}
- (void)clearCache:(NSArray *)exclude { - (void)clearCache:(NSArray *)exclude {
@ -313,7 +316,7 @@
bool remove = true; bool remove = true;
/*ImagePickerView can be used as popover and we do NOT want to free it*/; /*ImagePickerView can be used as popover and we do NOT want to free it*/;
if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:ActiveCallOrConferenceView.compositeViewDescription.name]) { if ([key isEqualToString:ImagePickerView.compositeViewDescription.name] || [key isEqualToString:SingleCallView.compositeViewDescription.name] || [key isEqualToString:ConferenceCallView.compositeViewDescription.name]) {
remove = false; remove = false;
} else if (exclude != nil) { } else if (exclude != nil) {
for (UICompositeViewDescription *description in exclude) { for (UICompositeViewDescription *description in exclude) {
@ -336,6 +339,9 @@
} }
- (IBAction)onRightSwipe:(id)sender { - (IBAction)onRightSwipe:(id)sender {
if (linphone_core_get_calls_nb(LC) > 0) {
return;
}
[self hideSideMenu:NO]; [self hideSideMenu:NO];
} }
@ -657,6 +663,8 @@
// 4. side menu // 4. side menu
self.sideMenuView.frame = sideMenuFrame; self.sideMenuView.frame = sideMenuFrame;
self.sideMenuViewController.view.frame = self.sideMenuView.bounds; self.sideMenuViewController.view.frame = self.sideMenuView.bounds;
[PhoneMainView.instance.mainViewController.view bringSubviewToFront:_sideMenuView];
[PhoneMainView.instance.mainViewController.view bringSubviewToFront:_sideMenuViewController.view];
// Commit animation // Commit animation
if (tabBar != nil || statusBar != nil || sideMenu != nil || fullscreen != nil) { if (tabBar != nil || statusBar != nil || sideMenu != nil || fullscreen != nil) {

View file

@ -17,11 +17,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "UIRoundBorderedButton.h" #import <UIKit/UIKit.h>
#import "UICompositeView.h"
#import "UIRoundBorderedButton.h"
typedef void (^UIConfirmationBlock)(void); typedef void (^UIConfirmationBlock)(void);
@interface UIConfirmationDialog : UIViewController { @interface UIConfirmationDialog : UIViewController <UICompositeViewDelegate, UIGestureRecognizerDelegate, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>{
UIConfirmationBlock onCancelCb; UIConfirmationBlock onCancelCb;
UIConfirmationBlock onConfirmCb; UIConfirmationBlock onConfirmCb;
} }
@ -49,13 +52,18 @@ typedef void (^UIConfirmationBlock)(void);
@property (weak, nonatomic) IBOutlet UIImageView *groupCallImage; @property (weak, nonatomic) IBOutlet UIImageView *groupCallImage;
@property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton; @property(weak, nonatomic) IBOutlet UIRoundBorderedButton *confirmationButton;
@property (weak, nonatomic) IBOutlet UIView *authView; @property (weak, nonatomic) IBOutlet UIView *authView;
@property (weak, nonatomic) IBOutlet UIImageView *backgroundColor;
@property(weak, nonatomic) IBOutlet UILabel *titleLabel; @property(weak, nonatomic) IBOutlet UILabel *titleLabel;
@property(weak, nonatomic) IBOutlet UIView *firstView;
@property (weak, nonatomic) IBOutlet UIButton *authButton; @property (weak, nonatomic) IBOutlet UIButton *authButton;
@property(weak, nonatomic) IBOutlet UILabel *subscribeLabel;
- (void)setSpecialColor; - (void)setSpecialColor;
-(void) setWhiteCancel; -(void) setWhiteCancel;
- (IBAction)onCancelClick:(id)sender; - (IBAction)onCancelClick:(id)sender;
- (IBAction)onConfirmationClick:(id)sender; - (IBAction)onConfirmationClick:(id)sender;
- (IBAction)onAuthClick:(id)sender; - (IBAction)onAuthClick:(id)sender;
- (IBAction)onSubscribeTap:(id)sender;
- (void)dismiss; - (void)dismiss;
@end @end

View file

@ -19,7 +19,7 @@
#import "UIConfirmationDialog.h" #import "UIConfirmationDialog.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "linphoneapp-Swift.h"" #import "linphoneapp-Swift.h"
@implementation UIConfirmationDialog @implementation UIConfirmationDialog
+ (UIConfirmationDialog *)initDialog:(NSString *)cancel + (UIConfirmationDialog *)initDialog:(NSString *)cancel
@ -33,6 +33,8 @@
dialog.view.frame = PhoneMainView.instance.mainViewController.view.frame; dialog.view.frame = PhoneMainView.instance.mainViewController.view.frame;
[controller.view addSubview:dialog.view]; [controller.view addSubview:dialog.view];
[controller addChildViewController:dialog]; [controller addChildViewController:dialog];
dialog.backgroundColor.layer.cornerRadius = 10;
dialog.backgroundColor.layer.masksToBounds = true;
dialog->onCancelCb = onCancel; dialog->onCancelCb = onCancel;
dialog->onConfirmCb = onConfirm; dialog->onConfirmCb = onConfirm;
@ -48,9 +50,21 @@
[[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_A.png"]] CGColor]; [[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_A.png"]] CGColor];
dialog.cancelButton.layer.borderColor = dialog.cancelButton.layer.borderColor =
[[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_F.png"]] CGColor]; [[UIColor colorWithPatternImage:[UIImage imageNamed:@"color_F.png"]] CGColor];
if (linphone_core_get_post_quantum_available()) {
[dialog.securityImage setImage:[UIImage imageNamed:@"post_quantum_secure.png"]];
}
return dialog; return dialog;
} }
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(onCancelClick:)];
tapGestureRecognizer.delegate = self;
[self.firstView addGestureRecognizer:tapGestureRecognizer];
}
+ (UIConfirmationDialog *)ShowWithMessage:(NSString *)message + (UIConfirmationDialog *)ShowWithMessage:(NSString *)message
cancelMessage:(NSString *)cancel cancelMessage:(NSString *)cancel
confirmMessage:(NSString *)confirm confirmMessage:(NSString *)confirm
@ -131,4 +145,12 @@
- (void)dismiss { - (void)dismiss {
[self onCancelClick:nil]; [self onCancelClick:nil];
} }
- (IBAction)onSubscribeTap:(id)sender {
UIGestureRecognizer *gest = sender;
NSString *url = ((UILabel *)gest.view).text;
if (![UIApplication.sharedApplication openURL:[NSURL URLWithString:url]]) {
LOGE(@"Failed to open %@, invalid URL", url);
}
}
@end @end

View file

@ -24,7 +24,8 @@
@interface UIContactCell : UITableViewCell @interface UIContactCell : UITableViewCell
@property(nonatomic, strong) IBOutlet UILabel *nameLabel; @property(nonatomic, strong) IBOutlet UILabel *nameLabel;
@property(nonatomic, strong) IBOutlet UIRoundedImageView *avatarImage; @property(nonatomic, strong) IBOutlet UILabel *organizationLabel;
@property(nonatomic, strong) IBOutlet UIImageView *avatarImage;
@property(weak, nonatomic) IBOutlet UIImageView *linphoneImage; @property(weak, nonatomic) IBOutlet UIImageView *linphoneImage;
@property(nonatomic, assign) Contact *contact; @property(nonatomic, assign) Contact *contact;

View file

@ -79,6 +79,7 @@
_linphoneImage.hidden = TRUE; _linphoneImage.hidden = TRUE;
if(_contact) { if(_contact) {
[ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact]; [ContactDisplay setDisplayNameLabel:_nameLabel forContact:_contact];
_organizationLabel.text = [FastAddressBook ogrganizationForContact:_contact];
_linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] || _linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] ||
! ((_contact.friend && linphone_presence_model_get_basic_status(linphone_friend_get_presence_model(_contact.friend)) == LinphonePresenceBasicStatusOpen) || [FastAddressBook contactHasValidSipDomain:_contact]); ! ((_contact.friend && linphone_presence_model_get_basic_status(linphone_friend_get_presence_model(_contact.friend)) == LinphonePresenceBasicStatusOpen) || [FastAddressBook contactHasValidSipDomain:_contact]);
} }

View file

@ -50,28 +50,34 @@
normAddr = linphone_account_normalize_phone_number(account, normAddr = linphone_account_normalize_phone_number(account,
_addressLabel.text.UTF8String); _addressLabel.text.UTF8String);
} }
LinphoneAddress *addr = linphone_core_interpret_url(LC, normAddr); LinphoneAddress *addr = linphone_core_interpret_url_2(LC, normAddr, true);
_chatButton.enabled = _callButton.enabled = _encryptedChatButton.enabled = (addr != NULL); _chatButton.enabled = _callButton.enabled = _encryptedChatButton.enabled = (addr != NULL);
_chatButton.accessibilityLabel = _chatButton.accessibilityLabel =
[NSString stringWithFormat:NSLocalizedString(@"Chat with %@", nil), _addressLabel.text]; [NSString stringWithFormat:NSLocalizedString(@"Chat with %@", nil), _addressLabel.text];
_callButton.accessibilityLabel = [NSString stringWithFormat:NSLocalizedString(@"Call %@", nil), _addressLabel.text]; _callButton.accessibilityLabel = [NSString stringWithFormat:NSLocalizedString(@"Call %@", nil), _addressLabel.text];
// Test presence // Test presence
Contact *contact; Contact *contact = addr ? [FastAddressBook getContactWithAddress:(addr)] : NULL;
contact = addr ? [FastAddressBook getContactWithAddress:(addr)] : NULL; LinphoneFriend *contactFriend = NULL;
if (contact && contact.friend) {
contactFriend = contact.friend;
} else if (addr) {
contactFriend = linphone_core_find_friend(LC, addr);
}
ContactDetailsView *contactDetailsView = VIEW(ContactDetailsView);
_linphoneImage.hidden = TRUE; _linphoneImage.hidden = TRUE;
if (contact) { if (contactFriend) {
const LinphonePresenceModel *model = contact.friend ? linphone_friend_get_presence_model_for_uri_or_tel(contact.friend, _addressLabel.text.UTF8String) : NULL; const LinphonePresenceModel *model = contactFriend ? linphone_friend_get_presence_model_for_uri_or_tel(contactFriend, _addressLabel.text.UTF8String) : NULL;
self.linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] || self.linphoneImage.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"hide_linphone_contacts" inSection:@"app"] ||
!((model && linphone_presence_model_get_basic_status(model) == LinphonePresenceBasicStatusOpen) || !((model && linphone_presence_model_get_basic_status(model) == LinphonePresenceBasicStatusOpen) ||
(account && !linphone_account_is_phone_number(account, (account && !linphone_account_is_phone_number(account,
_addressLabel.text.UTF8String) && _addressLabel.text.UTF8String) &&
[FastAddressBook isSipURIValid:_addressLabel.text])); [FastAddressBook isSipURIValid:_addressLabel.text]));
ContactDetailsView *contactDetailsView = VIEW(ContactDetailsView); self.inviteButton.hidden = !ENABLE_SMS_INVITE || [[contactDetailsView.contact sipAddresses] count] > 0 || !self.linphoneImage.hidden;
self.inviteButton.hidden = !ENABLE_SMS_INVITE || [[contactDetailsView.contact sipAddresses] count] > 0 || !self.linphoneImage.hidden;
[self shouldHideEncryptedChatView:account && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(account)) && model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh)]; [self shouldHideEncryptedChatView:account && linphone_account_params_get_conference_factory_uri(linphone_account_get_params(account)) && model && linphone_presence_model_has_capability(model, LinphoneFriendCapabilityLimeX3dh)];
_chatButton.hidden = [LinphoneManager.instance lpConfigBoolForKey:@"force_lime_chat_rooms"] || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"];
} }
if (addr) { if (addr) {
@ -80,7 +86,7 @@
} }
- (void)shouldHideEncryptedChatView:(BOOL)hasLime { - (void)shouldHideEncryptedChatView:(BOOL)hasLime {
_encryptedChatView.hidden = !hasLime; _encryptedChatView.hidden = !hasLime || [LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"];
CGRect newFrame = _optionsView.frame; CGRect newFrame = _optionsView.frame;
if (!hasLime) { if (!hasLime) {
newFrame.origin.x = _addressLabel.frame.origin.x + _callButton.frame.size.width * 2/3; newFrame.origin.x = _addressLabel.frame.origin.x + _callButton.frame.size.width * 2/3;
@ -103,7 +109,7 @@
normAddr = linphone_account_normalize_phone_number(account, normAddr = linphone_account_normalize_phone_number(account,
_addressLabel.text.UTF8String); _addressLabel.text.UTF8String);
} }
LinphoneAddress *addr = linphone_core_interpret_url(LC, normAddr); LinphoneAddress *addr = linphone_core_interpret_url_2(LC, normAddr, true);
// Test presence // Test presence
Contact *contact = [FastAddressBook getContactWithAddress:(addr)]; Contact *contact = [FastAddressBook getContactWithAddress:(addr)];

View file

@ -67,7 +67,6 @@
if (addressField && (!dtmf || !linphone_core_in_call(LC))) { if (addressField && (!dtmf || !linphone_core_in_call(LC))) {
NSString *newAddress = [NSString stringWithFormat:@"%@%c", addressField.text, digit]; NSString *newAddress = [NSString stringWithFormat:@"%@%c", addressField.text, digit];
[addressField setText:newAddress]; [addressField setText:newAddress];
linphone_core_play_dtmf(LC, digit, -1);
} else { } else {
linphone_call_send_dtmf(linphone_core_get_current_call(LC), digit); linphone_call_send_dtmf(linphone_core_get_current_call(LC), digit);
linphone_core_play_dtmf(LC, digit, 100); linphone_core_play_dtmf(LC, digit, 100);

View file

@ -28,7 +28,7 @@
@property (nonatomic, assign) LinphoneCallLog *callLog; @property (nonatomic, assign) LinphoneCallLog *callLog;
@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage; @property(weak, nonatomic) IBOutlet UIImageView *avatarImage;
@property(nonatomic, strong) IBOutlet UILabel *displayNameLabel; @property(nonatomic, strong) IBOutlet UILabel *displayNameLabel;
@property(weak, nonatomic) IBOutlet UIImageView *stateImage; @property(weak, nonatomic) IBOutlet UIImageView *stateImage;
@property(weak, nonatomic) IBOutlet UIIconButton *detailsButton; @property(weak, nonatomic) IBOutlet UIIconButton *detailsButton;

View file

@ -91,7 +91,7 @@
// Set up the cell... // Set up the cell...
if (linphone_call_log_was_conference(callLog)) { if (linphone_call_log_was_conference(callLog)) {
const char *subject = linphone_conference_info_get_subject(linphone_call_log_get_conference_info(callLog)); const char *subject = linphone_conference_info_get_subject(linphone_call_log_get_conference_info(callLog));
displayNameLabel.text = [NSString stringWithFormat:@"%s",subject]; displayNameLabel.text = [NSString stringWithUTF8String:subject];
[_avatarImage setImage:[UIImage imageNamed:@"voip_multiple_contacts_avatar"]]; [_avatarImage setImage:[UIImage imageNamed:@"voip_multiple_contacts_avatar"]];
_stateImage.hidden = true; _stateImage.hidden = true;
} else { } else {
@ -118,7 +118,7 @@
[displayNameLabel.text stringByAppendingString:[NSString stringWithFormat:@" (%lu)", count]]; [displayNameLabel.text stringByAppendingString:[NSString stringWithFormat:@" (%lu)", count]];
} }
[_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES]; [_avatarImage setImage:[FastAddressBook imageForAddress:addr]];
} }
} }

View file

@ -92,12 +92,6 @@
[_playButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemPlay:[UIColor blackColor]] forState:UIControlStateNormal]; [_playButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemPlay:[UIColor blackColor]] forState:UIControlStateNormal];
[_stopButton setTitle:@"" forState:UIControlStateNormal]; [_stopButton setTitle:@"" forState:UIControlStateNormal];
[_stopButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemRefresh:[UIColor blackColor]] forState:UIControlStateNormal]; [_stopButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemRefresh:[UIColor blackColor]] forState:UIControlStateNormal];
if (linphone_player_get_is_video_available(player)) {
linphone_player_set_window_id(player, (__bridge void *)VIEW(RecordingsListView).videoView);
VIEW(RecordingsListView).videoView.hidden = NO;
} else {
VIEW(RecordingsListView).videoView.hidden = YES;
}
} }
- (BOOL)isOpened { - (BOOL)isOpened {
@ -195,6 +189,12 @@ void on_eof_reached(LinphonePlayer *pl) {
[_playButton setTitle:@"" forState:UIControlStateNormal]; [_playButton setTitle:@"" forState:UIControlStateNormal];
[_playButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemPause:[UIColor blackColor]] forState:UIControlStateNormal]; [_playButton setImage:[UIImage imageFromSystemBarButton:UIBarButtonSystemItemPause:[UIColor blackColor]] forState:UIControlStateNormal];
linphone_player_start(player); linphone_player_start(player);
if (linphone_player_get_is_video_available(player)) {
linphone_player_set_window_id(player, (__bridge void *)VIEW(RecordingsListView).videoView);
VIEW(RecordingsListView).videoView.hidden = NO;
} else {
VIEW(RecordingsListView).videoView.hidden = YES;
}
break; break;
case LinphonePlayerPlaying: case LinphonePlayerPlaying:
NSLog(@"Pause"); NSLog(@"Pause");

View file

@ -25,8 +25,6 @@
@property (weak, nonatomic) IBOutlet UILabel *nameLabel; @property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (strong, nonatomic) IBOutlet UIToolbar *toolbar; @property (strong, nonatomic) IBOutlet UIToolbar *toolbar;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *shareButton; @property (weak, nonatomic) IBOutlet UIBarButtonItem *shareButton;
@property(nonatomic, assign) __block NSString *recording; @property(nonatomic, assign) __block NSString *recording;
- (id)initWithIdentifier:(NSString*)identifier; - (id)initWithIdentifier:(NSString*)identifier;

View file

@ -90,7 +90,8 @@ static UILinphoneAudioPlayer *player;
} }
- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated { - (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated {
self.selectionStyle = UITableViewCellSelectionStyleNone; if (!VIEW(RecordingsListView).tableController.isEditing)
self.selectionStyle = UITableViewCellSelectionStyleNone;
} }
- (void)updateFrame { - (void)updateFrame {
@ -105,10 +106,11 @@ static UILinphoneAudioPlayer *player;
-(void)setSelected:(BOOL)selected animated:(BOOL)animated{ -(void)setSelected:(BOOL)selected animated:(BOOL)animated{
[super setSelected:selected animated:animated]; [super setSelected:selected animated:animated];
_toolbar.hidden = !selected;
if (!selected) { if (!selected || (selected && VIEW(RecordingsListView).tableController.isEditing)) {
return; _toolbar.hidden = true;
} return;
}
if (player && [player isCreated]) { if (player && [player isCreated]) {
[player close]; [player close];
} }
@ -122,6 +124,7 @@ static UILinphoneAudioPlayer *player;
player.view.frame = _playerView.frame; player.view.frame = _playerView.frame;
player.view.bounds = _playerView.bounds; player.view.bounds = _playerView.bounds;
[player open]; [player open];
_toolbar.hidden = false;
} }
- (void)onShareButtonPressed { - (void)onShareButtonPressed {

View file

@ -20,6 +20,7 @@ import linphonesw
var previousFilter : String? var previousFilter : String?
var magicSearch : MagicSearch var magicSearch : MagicSearch
var magicSearchDelegate : MagicSearchDelegate? var magicSearchDelegate : MagicSearchDelegate?
var lastSearch : [SearchResult]?
override init() { override init() {
@ -30,10 +31,12 @@ import linphonesw
magicSearchDelegate = MagicSearchDelegateStub(onSearchResultsReceived: { (magicSearch: MagicSearch) in magicSearchDelegate = MagicSearchDelegateStub(onSearchResultsReceived: { (magicSearch: MagicSearch) in
self.needUpdateLastSearchContacts = true self.needUpdateLastSearchContacts = true
self.ongoingSearch = false self.ongoingSearch = false
self.lastSearch = magicSearch.lastSearch
Log.directLog(BCTBX_LOG_MESSAGE, text: "Contact magic search -- filter = \(String(describing: self.previousFilter)) -- \(magicSearch.lastSearch.count) contact founds") Log.directLog(BCTBX_LOG_MESSAGE, text: "Contact magic search -- filter = \(String(describing: self.previousFilter)) -- \(magicSearch.lastSearch.count) contact founds")
NotificationCenter.default.post(name: Notification.Name(kLinphoneMagicSearchFinished), object: self) NotificationCenter.default.post(name: Notification.Name(kLinphoneMagicSearchFinished), object: self)
}, onLdapHaveMoreResults: { (magicSearch: MagicSearch, ldap: Ldap) in }, onLdapHaveMoreResults: { (magicSearch: MagicSearch, ldap: Ldap) in
Log.directLog(BCTBX_LOG_MESSAGE, text: "Ldap have more result") Log.directLog(BCTBX_LOG_MESSAGE, text: "Ldap have more result")
NotificationCenter.default.post(name: Notification.Name(kLinphoneMagicSearchMoreAvailable), object: self)
}) })
magicSearch.addDelegate(delegate: magicSearchDelegate!) magicSearch.addDelegate(delegate: magicSearchDelegate!)
@ -47,17 +50,26 @@ import linphonesw
return theMagicSearchSingleton! return theMagicSearchSingleton!
} }
@objc static func destroyInstance() {
theMagicSearchSingleton = nil
}
func getContactFromAddr(addr: Address) -> Contact? { func getContactFromAddr(addr: Address) -> Contact? {
return LinphoneManager.instance().fastAddressBook.addressBookMap.object(forKey: addr.asStringUriOnly() as Any) as? Contact return LinphoneManager.instance().fastAddressBook.addressBookMap.object(forKey: addr.asStringUriOnly() as Any) as? Contact
} }
func getContactFromPhoneNb(phoneNb: String) -> Contact? { func getContactFromPhoneNb(phoneNb: String) -> Contact? {
let contactKey = FastAddressBook.localizedLabel(FastAddressBook.normalizeSipURI( lc?.defaultAccount?.normalizePhoneNumber(username: phoneNb) ?? phoneNb)) let contactKey = FastAddressBook.localizedLabel(FastAddressBook.normalizeSipURI(lc?.defaultAccount?.normalizePhoneNumber(username: phoneNb) ?? phoneNb, use_prefix: true))
return LinphoneManager.instance().fastAddressBook.addressBookMap.object(forKey: contactKey as Any) as? Contact return LinphoneManager.instance().fastAddressBook.addressBookMap.object(forKey: contactKey as Any) as? Contact
} }
func searchAndAddMatchingContact(searchResult: SearchResult) -> Contact? { func searchAndAddMatchingContact(searchResult: SearchResult) -> Contact? {
if let friend = searchResult.friend { if let friend = searchResult.friend {
if (searchResult.sourceFlags == MagicSearchSource.LdapServers.rawValue), let newContact = Contact(friend: friend.getCobject) {
// Contact comes from LDAP, creating a new one
newContact.createdFromLdapOrProvisioning = true
return newContact
}
if let addr = friend.address, let foundContact = getContactFromAddr(addr: addr) { if let addr = friend.address, let foundContact = getContactFromAddr(addr: addr) {
return foundContact return foundContact
} }
@ -66,11 +78,6 @@ import linphonesw
return foundContact return foundContact
} }
} }
// No contacts found (searchResult likely comes from LDAP), creating a new one
if let newContact = Contact(friend: friend.getCobject) {
newContact.createdFromLdap = true
return newContact
}
} }
if let addr = searchResult.address, let foundContact = getContactFromAddr(addr: addr) { if let addr = searchResult.address, let foundContact = getContactFromAddr(addr: addr) {
@ -81,6 +88,11 @@ import linphonesw
return foundContact return foundContact
} }
// Friend comes from provisioning
if let addr = searchResult.address, let friend = searchResult.friend, let newContact = Contact(friend: friend.getCobject) {
newContact.createdFromLdapOrProvisioning = true
return newContact
}
return nil return nil
} }
@ -91,8 +103,10 @@ import linphonesw
@objc func getLastSearchResults() -> UnsafeMutablePointer<bctbx_list_t>? { @objc func getLastSearchResults() -> UnsafeMutablePointer<bctbx_list_t>? {
var cList: UnsafeMutablePointer<bctbx_list_t>? = nil var cList: UnsafeMutablePointer<bctbx_list_t>? = nil
for data in magicSearch.lastSearch { if let search = lastSearch {
cList = bctbx_list_append(cList, UnsafeMutableRawPointer(data.getCobject)) for data in search {
cList = bctbx_list_append(cList, UnsafeMutableRawPointer(data.getCobject))
}
} }
return cList return cList
} }
@ -100,13 +114,9 @@ import linphonesw
@objc func getLastSearchContacts() -> [Contact] { @objc func getLastSearchContacts() -> [Contact] {
if (needUpdateLastSearchContacts) { if (needUpdateLastSearchContacts) {
lastSearchContacts = [] lastSearchContacts = []
var addedContactNames : [String] = []
for res in magicSearch.lastSearch { for res in magicSearch.lastSearch {
if let contact = searchAndAddMatchingContact(searchResult: res) { if let contact = searchAndAddMatchingContact(searchResult: res) {
if (!addedContactNames.contains(contact.displayName)) { lastSearchContacts.append(contact)
addedContactNames.append(contact.displayName)
lastSearchContacts.append(contact)
}
} }
} }
needUpdateLastSearchContacts = false needUpdateLastSearchContacts = false

View file

@ -114,6 +114,8 @@
- (void)getOrCreateOneToOneChatRoom:(const LinphoneAddress *)remoteAddress waitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted; - (void)getOrCreateOneToOneChatRoom:(const LinphoneAddress *)remoteAddress waitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted;
- (LinphoneChatRoom *)createChatRoom:(const char *)subject addresses:(bctbx_list_t *)addresses andWaitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted isGroup:(BOOL)isGroup; - (LinphoneChatRoom *)createChatRoom:(const char *)subject addresses:(bctbx_list_t *)addresses andWaitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted isGroup:(BOOL)isGroup;
- (void)goToChatRoom:(LinphoneChatRoom *)cr; - (void)goToChatRoom:(LinphoneChatRoom *)cr;
- (void)goToChatRoomSwift:(LinphoneChatRoom *)cr;
- (void)resetBeforeGoToChatRoomSwift;
+ (PhoneMainView*) instance; + (PhoneMainView*) instance;
- (BOOL)isIphoneXDevice; - (BOOL)isIphoneXDevice;
@ -123,3 +125,4 @@
@end @end
void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState); void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState);
void main_view_chat_room_conference_joined(LinphoneChatRoom *cr, const LinphoneEventLog *event_log);

View file

@ -307,9 +307,9 @@ static RootViewManager *rootViewManagerInstance = nil;
if (linphone_chat_message_is_outgoing(msg)) if (linphone_chat_message_is_outgoing(msg))
return; return;
ChatConversationView *view = VIEW(ChatConversationView); ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
// if we already are in the conversation, we should not ring/vibrate // if we already are in the conversation, we should not ring/vibrate
if (view.chatRoom && _currentRoom == view.chatRoom) if (view.linphoneChatRoom && _currentRoom == view.linphoneChatRoom)
return; return;
if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive) if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive)
@ -375,7 +375,6 @@ static RootViewManager *rootViewManagerInstance = nil;
} }
break; break;
} }
case LinphoneCallOutgoingInit:
case LinphoneCallOutgoingEarlyMedia: case LinphoneCallOutgoingEarlyMedia:
case LinphoneCallOutgoingProgress: case LinphoneCallOutgoingProgress:
case LinphoneCallOutgoingRinging: { case LinphoneCallOutgoingRinging: {
@ -387,7 +386,7 @@ static RootViewManager *rootViewManagerInstance = nil;
} }
break; break;
} }
case LinphoneCallPausedByRemote: case LinphoneCallPausedByRemote:break;
case LinphoneCallConnected: { case LinphoneCallConnected: {
if (![LinphoneManager.instance isCTCallCenterExist]) { if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/ /*only register CT call center CB for connected call*/
@ -419,6 +418,7 @@ static RootViewManager *rootViewManagerInstance = nil;
} }
case LinphoneCallUpdating: case LinphoneCallUpdating:
break; break;
} }
if (state == LinphoneCallEnd || state == LinphoneCallError || floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) if (state == LinphoneCallEnd || state == LinphoneCallError || floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)
[self updateApplicationBadgeNumber]; [self updateApplicationBadgeNumber];
@ -454,9 +454,19 @@ static RootViewManager *rootViewManagerInstance = nil;
LinphoneManager *lm = LinphoneManager.instance; LinphoneManager *lm = LinphoneManager.instance;
LOGI(@"%s", linphone_global_state_to_string(linphone_core_get_global_state(LC))); LOGI(@"%s", linphone_global_state_to_string(linphone_core_get_global_state(LC)));
NSString* groupName = [NSString stringWithFormat:@"group.%@.linphoneExtension",[[NSBundle mainBundle] bundleIdentifier]];
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:groupName];
NSDictionary *dict = [defaults valueForKey:@"photoData"];
NSDictionary *dictFile = [defaults valueForKey:@"icloudData"];
NSDictionary *dictUrl = [defaults valueForKey:@"url"];
// If we've been started by a remote push notification, // If we've been started by a remote push notification,
// we'll already be on the corresponding chat conversation view, no need to go anywhere else // we'll already be on the corresponding chat conversation view, no need to go anywhere else
if (![[self currentView].name isEqualToString:@"ChatConversationView"]) { if (dict||dictFile||dictUrl){
[self changeCurrentView:ChatsListView.compositeViewDescription];
}else if (![[self currentView].name isEqualToString:@"ChatConversationViewSwift"]) {
if (linphone_core_get_global_state(LC) != LinphoneGlobalOn) { if (linphone_core_get_global_state(LC) != LinphoneGlobalOn) {
[self changeCurrentView:DialerView.compositeViewDescription]; [self changeCurrentView:DialerView.compositeViewDescription];
@ -608,7 +618,8 @@ static RootViewManager *rootViewManagerInstance = nil;
} }
[self _changeCurrentView:viewStack.lastObject ?: DialerView.compositeViewDescription [self _changeCurrentView:viewStack.lastObject ?: DialerView.compositeViewDescription
transition:[PhoneMainView getBackwardTransition] transition:[PhoneMainView getBackwardTransition]
animated:ANIMATED]; animated:ANIMATED
addViewToStack:FALSE];
return [mainViewController getCurrentViewController]; return [mainViewController getCurrentViewController];
} }
@ -622,18 +633,19 @@ static RootViewManager *rootViewManagerInstance = nil;
- (void)changeCurrentView:(UICompositeViewDescription *)view { - (void)changeCurrentView:(UICompositeViewDescription *)view {
[self _changeCurrentView:view transition:nil animated:ANIMATED]; [self _changeCurrentView:view transition:nil animated:ANIMATED addViewToStack:TRUE];
} }
- (UIViewController *)_changeCurrentView:(UICompositeViewDescription *)view - (UIViewController *)_changeCurrentView:(UICompositeViewDescription *)view
transition:(CATransition *)transition transition:(CATransition *)transition
animated:(BOOL)animated { animated:(BOOL)animated
addViewToStack:(BOOL)addViewToStack {
PhoneMainView *vc = [[RootViewManager instance] setViewControllerForDescription:view]; PhoneMainView *vc = [[RootViewManager instance] setViewControllerForDescription:view];
if (![view equal:vc.currentView] || vc != self) { if (![view equal:vc.currentView] || vc != self) {
LOGI(@"Change current view to %@", view.name); LOGI(@"Change current view to %@", view.name);
[self setPreviousViewName:vc.currentView.name]; [self setPreviousViewName:vc.currentView.name];
NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack; NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
[viewStack addObject:view]; if (addViewToStack) [viewStack addObject:view];
if (animated && transition == nil) if (animated && transition == nil)
transition = [PhoneMainView getTransition:vc.currentView new:view]; transition = [PhoneMainView getTransition:vc.currentView new:view];
[vc.mainViewController setViewTransition:(animated ? transition : nil)]; [vc.mainViewController setViewTransition:(animated ? transition : nil)];
@ -654,7 +666,8 @@ static RootViewManager *rootViewManagerInstance = nil;
while (viewStack.count > 0 && ![[viewStack lastObject] equal:view]) { while (viewStack.count > 0 && ![[viewStack lastObject] equal:view]) {
[viewStack removeLastObject]; [viewStack removeLastObject];
} }
return [self _changeCurrentView:view transition:[PhoneMainView getBackwardTransition] animated:ANIMATED]; BOOL addView = (viewStack.count == 0); // if we couldn't find the view in the stack, we need to add it
return [self _changeCurrentView:view transition:[PhoneMainView getBackwardTransition] animated:ANIMATED addViewToStack:addView];
} }
- (void) setPreviousViewName:(NSString*)previous{ - (void) setPreviousViewName:(NSString*)previous{
@ -842,7 +855,7 @@ static RootViewManager *rootViewManagerInstance = nil;
return; return;
} }
[self goToChatRoom:room]; [self goToChatRoomSwift:room];
} }
- (LinphoneChatRoom *)createChatRoom:(const char *)subject addresses:(bctbx_list_t *)addresses andWaitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted isGroup:(BOOL)isGroup{ - (LinphoneChatRoom *)createChatRoom:(const char *)subject addresses:(bctbx_list_t *)addresses andWaitView:(UIView *)waitView isEncrypted:(BOOL)isEncrypted isGroup:(BOOL)isGroup{
@ -865,7 +878,7 @@ static RootViewManager *rootViewManagerInstance = nil;
return nil; return nil;
} }
LinphoneChatRoom *basicRoom = linphone_core_get_chat_room(LC, addresses->data); LinphoneChatRoom *basicRoom = linphone_core_get_chat_room(LC, addresses->data);
[self goToChatRoom:basicRoom]; [self goToChatRoomSwift:basicRoom];
return nil; return nil;
} }
@ -888,6 +901,7 @@ static RootViewManager *rootViewManagerInstance = nil;
LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get()); LinphoneChatRoomCbs *cbs = linphone_factory_create_chat_room_cbs(linphone_factory_get());
linphone_chat_room_cbs_set_state_changed(cbs, main_view_chat_room_state_changed); linphone_chat_room_cbs_set_state_changed(cbs, main_view_chat_room_state_changed);
linphone_chat_room_cbs_set_conference_joined(cbs, main_view_chat_room_conference_joined);
linphone_chat_room_add_callbacks(room, cbs); linphone_chat_room_add_callbacks(room, cbs);
return room; return room;
@ -905,31 +919,50 @@ static RootViewManager *rootViewManagerInstance = nil;
[view clearMessageView]; [view clearMessageView];
view.chatRoom = cr; view.chatRoom = cr;
view.peerAddress = linphone_address_as_string(linphone_chat_room_get_peer_address(cr)); view.peerAddress = linphone_address_as_string(linphone_chat_room_get_peer_address(cr));
view.localAddress = linphone_address_as_string(linphone_chat_room_get_local_address(cr));
self.currentRoom = view.chatRoom; self.currentRoom = view.chatRoom;
if (PhoneMainView.instance.currentView == view.compositeViewDescription) if (PhoneMainView.instance.currentView == view.compositeViewDescription)
[view configureForRoom:FALSE]; [view configureForRoom:FALSE];
else else
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
} }
- (void)goToChatRoomSwift:(LinphoneChatRoom *)cr {
_waitView.hidden = YES;
_waitView = NULL;
ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
self.currentRoom = view.linphoneChatRoom;
[view initChatRoomWithCChatRoom:cr];
[PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
- (void)resetBeforeGoToChatRoomSwift{
ChatConversationViewSwift *view = VIEW(ChatConversationViewSwift);
[view resetView];
}
void main_view_chat_room_conference_joined(LinphoneChatRoom *cr, const LinphoneEventLog *event_log) {
PhoneMainView *view = PhoneMainView.instance;
LOGI(@"Chat room [%p] conference joined.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
[view goToChatRoomSwift:cr];
if (!IPAD)
return;
if (PhoneMainView.instance.currentView != ChatsListView.compositeViewDescription && PhoneMainView.instance.currentView != ChatConversationViewSwift.compositeViewDescription)
return;
ChatsListView *mainView = VIEW(ChatsListView);
[mainView.tableController loadData];
[mainView.tableController selectFirstRow];
}
void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) { void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomState newState) {
PhoneMainView *view = PhoneMainView.instance; PhoneMainView *view = PhoneMainView.instance;
switch (newState) { switch (newState) {
case LinphoneChatRoomStateCreated: {
LOGI(@"Chat room [%p] created on server.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
[view goToChatRoom:cr];
if (!IPAD)
break;
if (PhoneMainView.instance.currentView != ChatsListView.compositeViewDescription && PhoneMainView.instance.currentView != ChatConversationView.compositeViewDescription)
break;
ChatsListView *mainView = VIEW(ChatsListView);
[mainView.tableController loadData];
[mainView.tableController selectFirstRow];
break;
}
case LinphoneChatRoomStateCreationFailed: case LinphoneChatRoomStateCreationFailed:
LOGE(@"Chat room [%p] could not be created on server.", cr); LOGE(@"Chat room [%p] could not be created on server.", cr);
linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr)); linphone_chat_room_remove_callbacks(cr, linphone_chat_room_get_current_callbacks(cr));
@ -938,7 +971,7 @@ void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomSta
break; break;
case LinphoneChatRoomStateTerminated: case LinphoneChatRoomStateTerminated:
LOGI(@"Chat room [%p] has been terminated.", cr); LOGI(@"Chat room [%p] has been terminated.", cr);
[view goToChatRoom:cr]; [view goToChatRoomSwift:cr];
break; break;
default: default:
break; break;
@ -962,4 +995,8 @@ void main_view_chat_room_state_changed(LinphoneChatRoom *cr, LinphoneChatRoomSta
} }
} }
-(void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[UIDeviceBridge notifyDisplayModeSwitch];
}
@end @end

View file

@ -147,8 +147,6 @@
NSString *recordingPath = subAr[indexPath.row]; NSString *recordingPath = subAr[indexPath.row];
[cell setRecording:recordingPath]; [cell setRecording:recordingPath];
[super accessoryForCell:cell atPath:indexPath]; [super accessoryForCell:cell atPath:indexPath];
//accessoryForCell set it to gray but we don't want it
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell updateFrame]; [cell updateFrame];
cell.contentView.userInteractionEnabled = false; cell.contentView.userInteractionEnabled = false;
return cell; return cell;

View file

@ -22,12 +22,12 @@
#import "LinphoneAppDelegate.h" #import "LinphoneAppDelegate.h"
#import "PhoneMainView.h" #import "PhoneMainView.h"
#import "Utils.h" #import "Utils.h"
#import "linphoneapp-Swift.h"
#import "DCRoundSwitch.h" #import "DCRoundSwitch.h"
#import "IASKSpecifierValuesViewController.h" #import "IASKSpecifierValuesViewController.h"
#import "IASKPSTextFieldSpecifierViewCell.h" #import "IASKPSTextFieldSpecifierViewCell.h"
#import "IASKPSTitleValueSpecifierViewCell.h"
#import "IASKSpecifier.h" #import "IASKSpecifier.h"
#import "IASKTextField.h" #import "IASKTextField.h"
#include "linphone/lpconfig.h" #include "linphone/lpconfig.h"
@ -247,7 +247,7 @@
[field setTextColor:LINPHONE_MAIN_COLOR]; [field setTextColor:LINPHONE_MAIN_COLOR];
} }
if ([cell isKindOfClass:[IASKPSTitleValueSpecifierViewCell class]]) { if ([cell isKindOfClass:[UITableViewCell class]]) {
cell.detailTextLabel.textColor = [UIColor grayColor]; cell.detailTextLabel.textColor = [UIColor grayColor];
} else { } else {
cell.detailTextLabel.textColor = LINPHONE_MAIN_COLOR; cell.detailTextLabel.textColor = LINPHONE_MAIN_COLOR;
@ -445,8 +445,11 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
linphone_account_creator_set_password(creator, _tmpPwd.UTF8String); linphone_account_creator_set_password(creator, _tmpPwd.UTF8String);
[settingsStore setObject:_tmpPwd forKey:@"account_mandatory_password_preference"]; [settingsStore setObject:_tmpPwd forKey:@"account_mandatory_password_preference"];
MSList *accountList = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC), LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
[settingsStore integerForKey:@"current_proxy_config_preference"]); [settingsStore integerForKey:@"current_proxy_config_preference"]);
bctbx_free(accountList);
if (account != NULL) { if (account != NULL) {
const LinphoneAuthInfo *auth = linphone_account_find_auth_info(account); const LinphoneAuthInfo *auth = linphone_account_find_auth_info(account);
if (auth) { if (auth) {
@ -521,11 +524,26 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
[keys addObject:@"download_bandwidth_preference"]; [keys addObject:@"download_bandwidth_preference"];
} else if ([@"auto_download_mode" compare:notif.object] == NSOrderedSame) { } else if ([@"auto_download_mode" compare:notif.object] == NSOrderedSame) {
NSString *download_mode = [notif.userInfo objectForKey:@"auto_download_mode"]; NSString *download_mode = [notif.userInfo objectForKey:@"auto_download_mode"];
removeFromHiddenKeys = [download_mode isEqualToString:@"Customize"]; if([download_mode isEqualToString:@"Customize"]){
if (removeFromHiddenKeys) [LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"auto_download_mode_is_never"];
[LinphoneManager.instance lpConfigSetInt:10000000 forKey:@"auto_download_incoming_files_max_size"]; removeFromHiddenKeys = [download_mode isEqualToString:@"Customize"];
[keys addObject:@"auto_download_incoming_files_max_size"]; [LinphoneManager.instance lpConfigSetInt:10000000 forKey:@"auto_download_incoming_files_max_size"];
} [keys addObject:@"auto_download_incoming_files_max_size"];
[hiddenKeys addObject:@"auto_write_to_gallery_mode"];
} else {
[LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"auto_download_mode_is_never"];
[hiddenKeys addObject:@"auto_download_incoming_files_max_size"];
}
}else if ([@"vfs_enabled_mode" compare:notif.object] == NSOrderedSame) {
removeFromHiddenKeys = [[notif.userInfo objectForKey:@"vfs_enabled_mode"] boolValue];
if(removeFromHiddenKeys){
[LinphoneManager.instance lpConfigSetBool:TRUE forKey:@"vfs_enabled_mode"];
[LinphoneManager.instance lpConfigSetBool:FALSE forKey:@"auto_write_to_gallery_preference"];
[hiddenKeys addObject:@"auto_write_to_gallery_mode"];
[hiddenKeys addObject:@"vfs_enabled_mode"];
[keys addObject:@"vfs_enabled"];
}
}
for (NSString *key in keys) { for (NSString *key in keys) {
if (removeFromHiddenKeys) if (removeFromHiddenKeys)
@ -552,11 +570,12 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
return [[IASKSpecifier alloc] initWithSpecifier:dict]; return [[IASKSpecifier alloc] initWithSpecifier:dict];
} }
} else { } else {
if ([[specifier key] isEqualToString:@"media_encryption_preference"]) { BOOL pq_available = linphone_core_get_post_quantum_available();
if ([[specifier key] isEqualToString:pq_available ? @"media_encryption_preference_pq_enabled" : @"media_encryption_preference"]) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[specifier specifierDict]]; NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[specifier specifierDict]];
if (!linphone_core_media_encryption_supported(LC, LinphoneMediaEncryptionZRTP)) { if (!linphone_core_media_encryption_supported(LC, LinphoneMediaEncryptionZRTP)) {
NSMutableArray *titles = [NSMutableArray arrayWithArray:[dict objectForKey:@"Titles"]]; NSMutableArray *titles = [NSMutableArray arrayWithArray:[dict objectForKey:@"Titles"]];
[titles removeObject:@"ZRTP"]; [titles removeObject:pq_available ? @"ZRTP" : @"ZRTP Post Quantum"];
[dict setObject:titles forKey:@"Titles"]; [dict setObject:titles forKey:@"Titles"];
NSMutableArray *values = [NSMutableArray arrayWithArray:[dict objectForKey:@"Values"]]; NSMutableArray *values = [NSMutableArray arrayWithArray:[dict objectForKey:@"Values"]];
[values removeObject:@"ZRTP"]; [values removeObject:@"ZRTP"];
@ -583,7 +602,7 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
} }
if ([specifier.key hasPrefix:@"menu_account_"]) { if ([specifier.key hasPrefix:@"menu_account_"]) {
const bctbx_list_t *accounts = linphone_core_get_account_list(LC); MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
int index = [specifier.key substringFromIndex:@"menu_account_".length].intValue - 1; int index = [specifier.key substringFromIndex:@"menu_account_".length].intValue - 1;
if (index < bctbx_list_size(accounts)) { if (index < bctbx_list_size(accounts)) {
LinphoneAccount *account = (LinphoneAccount *)bctbx_list_nth_data(accounts, index); LinphoneAccount *account = (LinphoneAccount *)bctbx_list_nth_data(accounts, index);
@ -591,6 +610,7 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
stringWithUTF8String:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)))]; stringWithUTF8String:linphone_address_get_username(linphone_account_params_get_identity_address(linphone_account_get_params(account)))];
[specifier.specifierDict setValue:name forKey:kIASKTitle]; [specifier.specifierDict setValue:name forKey:kIASKTitle];
} }
bctbx_free(accounts);
} }
if ([specifier.key hasPrefix:@"ldap_"]) { if ([specifier.key hasPrefix:@"ldap_"]) {
@ -625,23 +645,40 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
- (NSSet *)findHiddenKeys { - (NSSet *)findHiddenKeys {
LinphoneManager *lm = LinphoneManager.instance; LinphoneManager *lm = LinphoneManager.instance;
NSMutableSet *hiddenKeys = [NSMutableSet set]; NSMutableSet *hiddenKeys = [NSMutableSet set];
const MSList *accounts = linphone_core_get_account_list(LC); MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
for (size_t i = bctbx_list_size(accounts) + 1; i <= 5; i++) { for (size_t i = bctbx_list_size(accounts) + 1; i <= 5; i++) {
[hiddenKeys addObject:[NSString stringWithFormat:@"menu_account_%lu", i]]; [hiddenKeys addObject:[NSString stringWithFormat:@"menu_account_%lu", i]];
} }
bctbx_free(accounts);
const MSList *ldaps = linphone_core_get_ldap_list(LC); const MSList *ldaps = linphone_core_get_ldap_list(LC);
for (size_t i = bctbx_list_size(ldaps) + 1; i <= 5; i++) { for (size_t i = bctbx_list_size(ldaps) + 1; i <= 5; i++) {
[hiddenKeys addObject:[NSString stringWithFormat:@"ldap_%lu", i]]; [hiddenKeys addObject:[NSString stringWithFormat:@"ldap_%lu", i]];
} }
if (!linphone_core_sip_transport_supported(LC, LinphoneTransportTls)) { if (!linphone_core_sip_transport_supported(LC, LinphoneTransportTls)) {
[hiddenKeys addObject:@"media_encryption_preference"]; [hiddenKeys addObject:@"media_encryption_preference"];
[hiddenKeys addObject:@"media_encryption_preference_pq_enabled"];
} else {
if (linphone_core_get_post_quantum_available()) {
[hiddenKeys addObject:@"media_encryption_preference"];
} else {
[hiddenKeys addObject:@"media_encryption_preference_pq_enabled"];
}
} }
if (!linphone_core_ldap_available(LC)) { if (!linphone_core_ldap_available(LC)) {
[hiddenKeys addObject:@"contacts_menu"]; [hiddenKeys addObject:@"contacts_menu"];
} }
if ([LinphoneManager.instance lpConfigBoolForKey:@"disable_chat_feature"]){
[hiddenKeys addObject:@"message_menu"];
}
if ([LinphoneManager.instance lpConfigBoolForKey:@"disable_video_feature"]) {
[hiddenKeys addObject:@"enable_video_preference"];
[hiddenKeys addObject:@"video_menu"];
}
#ifndef DEBUG #ifndef DEBUG
[hiddenKeys addObject:@"debug_actions_group"]; [hiddenKeys addObject:@"debug_actions_group"];
[hiddenKeys addObject:@"release_button"]; [hiddenKeys addObject:@"release_button"];
@ -761,6 +798,13 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
if (![[lm lpConfigStringForKey:@"auto_download_mode"] isEqualToString:@"Customize"]) { if (![[lm lpConfigStringForKey:@"auto_download_mode"] isEqualToString:@"Customize"]) {
[hiddenKeys addObject:@"auto_download_incoming_files_max_size"]; [hiddenKeys addObject:@"auto_download_incoming_files_max_size"];
} }
if ([lm lpConfigBoolForKey:@"vfs_enabled_mode"]) {
[hiddenKeys addObject:@"auto_write_to_gallery_mode"];
[hiddenKeys addObject:@"vfs_enabled_mode"];
}else{
[hiddenKeys addObject:@"vfs_enabled"];
}
return hiddenKeys; return hiddenKeys;
} }
@ -851,25 +895,34 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
[PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription]; [PhoneMainView.instance changeCurrentView:AssistantView.compositeViewDescription];
return; return;
} else if ([key isEqual:@"account_mandatory_remove_button"]) { } else if ([key isEqual:@"account_mandatory_remove_button"]) {
UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Warning", nil) NSString *popUpText;
message:NSLocalizedString(@"Are you sure to want to remove your proxy setup?", nil) NSString *appDomain = [LinphoneManager.instance lpConfigStringForKey:@"domain_name"
preferredStyle:UIAlertControllerStyleAlert]; inSection:@"app"
withDefault:@"sip.linphone.org"];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", nil) MSList *accountList = [LinphoneManager.instance createAccountsNotHiddenList];
style:UIAlertActionStyleDefault LinphoneAccount *account = bctbx_list_nth_data(accountList,
handler:^(UIAlertAction * action) {}]; [settingsStore integerForKey:@"current_proxy_config_preference"]);
UIAlertAction* continueAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", nil) bool isLinphoneAccount = strcmp(appDomain.UTF8String, linphone_account_params_get_domain(linphone_account_get_params(account))) == 0;
style:UIAlertActionStyleDefault if (isLinphoneAccount) {
handler:^(UIAlertAction * action) { popUpText = NSLocalizedString(@"Your account will only be deleted locally.\nTo delete it permanently, go to our account management platform:", nil);
[settingsStore removeAccount]; } else {
[self recomputeAccountLabelsAndSync]; popUpText = NSLocalizedString(@"Your account will only be deleted locally.\nTo delete it permanently, go on your SIP provider website.", nil);
[_settingsController.navigationController popViewControllerAnimated:NO]; }
}]; bctbx_free(accountList);
[errView addAction:defaultAction]; UIConfirmationDialog *dialog = [UIConfirmationDialog ShowWithMessage:popUpText
[errView addAction:continueAction]; cancelMessage:nil
[self presentViewController:errView animated:YES completion:nil]; confirmMessage:nil
onCancelClick:nil
onConfirmationClick:^() {
[settingsStore removeAccount];
[self recomputeAccountLabelsAndSync];
[_settingsController.navigationController popViewControllerAnimated:NO];
}];
dialog.subscribeLabel.hidden = !isLinphoneAccount; // Only display link to https://subscribe.linphone.org for linphone accounts
} else if ([key isEqual:@"account_mandatory_change_password"]) { } else if ([key isEqual:@"account_mandatory_change_password"]) {
UIAlertController *alertView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Change your password", nil) UIAlertController *alertView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Change your password", nil)
message:NSLocalizedString(@"Please enter and confirm your new password", nil) message:NSLocalizedString(@"Please enter and confirm your new password", nil)
@ -901,8 +954,11 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
if (pwd && ![pwd isEqualToString:@""]) { if (pwd && ![pwd isEqualToString:@""]) {
if ([pwd isEqualToString:conf_pwd]) { if ([pwd isEqualToString:conf_pwd]) {
_tmpPwd = pwd; _tmpPwd = pwd;
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC),
[settingsStore integerForKey:@"current_proxy_config_preference"]); MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
LinphoneAccount *account = bctbx_list_nth_data(accounts,
[settingsStore integerForKey:@"current_proxy_config_preference"]);
bctbx_free(accounts);
const LinphoneAuthInfo *ai = linphone_account_find_auth_info(account); const LinphoneAuthInfo *ai = linphone_account_find_auth_info(account);
LinphoneAccountCreator *account_creator = linphone_account_creator_new( LinphoneAccountCreator *account_creator = linphone_account_creator_new(
@ -1072,6 +1128,14 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
// retrieve linphone logs if available // retrieve linphone logs if available
char *filepath = linphone_core_compress_log_collection(); char *filepath = linphone_core_compress_log_collection();
LinphoneCoreCbs *coreCbs = linphone_factory_create_core_cbs(linphone_factory_get());
linphone_core_cbs_set_log_collection_upload_state_changed(coreCbs, core_log_collection_upload_state_changed);
linphone_core_add_callbacks(LC, coreCbs);
linphone_core_upload_log_collection(LC);
if (filepath != NULL) { if (filepath != NULL) {
NSString *filename = [[NSString stringWithUTF8String:filepath] componentsSeparatedByString:@"/"].lastObject; NSString *filename = [[NSString stringWithUTF8String:filepath] componentsSeparatedByString:@"/"].lastObject;
NSString *mimeType = nil; NSString *mimeType = nil;
@ -1119,6 +1183,17 @@ void update_hash_cbs(LinphoneAccountCreator *creator, LinphoneAccountCreatorStat
[self emailAttachments:attachments]; [self emailAttachments:attachments];
} }
void core_log_collection_upload_state_changed(LinphoneCore *core, LinphoneCoreLogCollectionUploadState state , const char* info) {
LOGD(@"LinphoneCoreLogCollectionUploadStateDelivered core_log_collection_upload_state_changed %s", info);
if (state == LinphoneCoreLogCollectionUploadStateDelivered) {
LOGD(@"LinphoneCoreLogCollectionUploadStateDelivered %s", info);
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
pasteboard.string = [NSString stringWithUTF8String: info];
}
}
- (void)sendEmailWithPrivacyAttachments { - (void)sendEmailWithPrivacyAttachments {
NSMutableArray *attachments = [[NSMutableArray alloc] initWithCapacity:4]; NSMutableArray *attachments = [[NSMutableArray alloc] initWithCapacity:4];

View file

@ -66,8 +66,11 @@
changeCurrentView:AssistantView.compositeViewDescription]; changeCurrentView:AssistantView.compositeViewDescription];
}]]; }]];
BOOL mustLink = ([LinphoneManager.instance lpConfigIntForKey:@"must_link_account_time"] > 0); BOOL mustLink = ([LinphoneManager.instance lpConfigIntForKey:@"must_link_account_time"] > 0);
BOOL hasAccount = linphone_core_get_account_list(LC) != NULL;
if (mustLink && hasAccount) { MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
BOOL hasAccount = accounts != NULL;
bctbx_free(accounts);
if (mustLink && hasAccount && ![LinphoneManager.instance lpConfigIntForKey:@"hide_link_phone_number"]) {
[_sideMenuEntries [_sideMenuEntries
addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"Link my account", nil) addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"Link my account", nil)
image:[UIImage imageNamed:@"menu_link_account.png"] image:[UIImage imageNamed:@"menu_link_account.png"]
@ -103,13 +106,17 @@
}]]; }]];
} }
[_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:VoipTexts.conference_scheduled LinphoneAccount *defaultAccount = linphone_core_get_default_account(LC);
image:[UIImage imageNamed:@"menu_voip_meeting_schedule"] if (defaultAccount && linphone_account_params_get_audio_video_conference_factory_address(linphone_account_get_params(defaultAccount))){
tapBlock:^() {
[PhoneMainView.instance [_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:VoipTexts.conference_scheduled
changeCurrentView:ScheduledConferencesView.compositeViewDescription]; image:[UIImage imageNamed:@"side_menu_voip_meeting_schedule"]
tapBlock:^() {
[PhoneMainView.instance
changeCurrentView:ScheduledConferencesView.compositeViewDescription];
}]]; }]];
}
[_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"About", nil) [_sideMenuEntries addObject:[[SideMenuEntry alloc] initWithTitle:NSLocalizedString(@"About", nil)
image:[UIImage imageNamed:@"menu_about.png"] image:[UIImage imageNamed:@"menu_about.png"]
@ -127,9 +134,12 @@
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) { if (section == 0) {
BOOL hasDefault = (linphone_core_get_default_account(LC) != NULL); BOOL hasDefault = (linphone_core_get_default_account(LC) != NULL);
// default account is shown in the header already // default account is shown in the header already
size_t count = bctbx_list_size(linphone_core_get_account_list(LC)); MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
size_t count = bctbx_list_size(accounts);
bctbx_free(accounts);
return MAX(0, (int)count - (hasDefault ? 1 : 0)); return MAX(0, (int)count - (hasDefault ? 1 : 0));
} else { } else {
return [_sideMenuEntries count]; return [_sideMenuEntries count];
@ -142,12 +152,14 @@
// isLcInitialized called here because this is called when going in bg after LC destroy // isLcInitialized called here because this is called when going in bg after LC destroy
if (indexPath.section == 0 && [LinphoneManager isLcInitialized]) { if (indexPath.section == 0 && [LinphoneManager isLcInitialized]) {
// do not display default account here, it is already in header view // do not display default account here, it is already in header view
MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
int idx = int idx =
linphone_core_get_default_account(LC) linphone_core_get_default_account(LC)
? bctbx_list_index(linphone_core_get_account_list(LC), linphone_core_get_default_account(LC)) ? bctbx_list_index(accounts, linphone_core_get_default_account(LC))
: HUGE_VAL; : HUGE_VAL;
LinphoneAccount *account = bctbx_list_nth_data(linphone_core_get_account_list(LC), LinphoneAccount *account = bctbx_list_nth_data(accounts,
(int)indexPath.row + (idx <= indexPath.row ? 1 : 0)); (int)indexPath.row + (idx <= indexPath.row ? 1 : 0));
bctbx_free(accounts);
if (account) { if (account) {
cell.textLabel.text = [NSString stringWithUTF8String:linphone_account_params_get_identity(linphone_account_get_params(account))]; cell.textLabel.text = [NSString stringWithUTF8String:linphone_account_params_get_identity(linphone_account_get_params(account))];
cell.imageView.image = [StatusBarView imageForState:linphone_account_get_state(account)]; cell.imageView.image = [StatusBarView imageForState:linphone_account_get_state(account)];

View file

@ -70,7 +70,9 @@
_addressLabel.text = str ? [NSString stringWithUTF8String:str] : NSLocalizedString(@"No address", nil); _addressLabel.text = str ? [NSString stringWithUTF8String:str] : NSLocalizedString(@"No address", nil);
if (str) ms_free(str); if (str) ms_free(str);
} else { } else {
_nameLabel.text = linphone_core_get_account_list(LC) ? NSLocalizedString(@"No default account", nil) : NSLocalizedString(@"No account", nil); MSList *accounts = [LinphoneManager.instance createAccountsNotHiddenList];
_nameLabel.text = accounts ? NSLocalizedString(@"No default account", nil) : NSLocalizedString(@"No account", nil);
bctbx_free(accounts);
// display direct IP:port address so that we can be reached // display direct IP:port address so that we can be reached
LinphoneAddress *addr = linphone_core_get_primary_contact_parsed(LC); LinphoneAddress *addr = linphone_core_get_primary_contact_parsed(LC);
if (addr) { if (addr) {

View file

@ -72,7 +72,7 @@ enum NetworkType: Int {
Log.directLog(BCTBX_LOG_MESSAGE, text: "File :\(file) removed") Log.directLog(BCTBX_LOG_MESSAGE, text: "File :\(file) removed")
} catch { } catch {
print("Could not remove file : \(file) \(error)") Log.e("Could not remove file : \(file) \(error)")
} }
} }

View file

@ -1,21 +1,21 @@
/* /*
* Copyright (c) 2010-2020 Belledonne Communications SARL. * Copyright (c) 2010-2020 Belledonne Communications SARL.
* *
* This file is part of linphone-iphone * This file is part of linphone-iphone
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import Foundation import Foundation
import linphonesw import linphonesw
@ -27,14 +27,14 @@ import AVFoundation
@objc class CallAppData: NSObject { @objc class CallAppData: NSObject {
@objc var batteryWarningShown = false @objc var batteryWarningShown = false
@objc var videoRequested = false /*set when user has requested for video*/ @objc var videoRequested = false /*set when user has requested for video*/
@objc var isConference = true @objc var isConference = false
} }
/* /*
* CallManager is a class that manages application calls and supports callkit. * CallManager is a class that manages application calls and supports callkit.
* There is only one CallManager by calling CallManager.instance(). * There is only one CallManager by calling CallManager.instance().
*/ */
@objc class CallManager: NSObject, CoreDelegate { @objc class CallManager: NSObject, CoreDelegate {
static var theCallManager: CallManager? static var theCallManager: CallManager?
let providerDelegate: ProviderDelegate! // to support callkit let providerDelegate: ProviderDelegate! // to support callkit
@ -49,28 +49,31 @@ import AVFoundation
var actionsToPerformOnceWhenCoreIsOn : [(()->Void)] = [] var actionsToPerformOnceWhenCoreIsOn : [(()->Void)] = []
var conference: Conference? var conference: Conference?
var callkitAudioSessionActivated : Bool? = nil // if "nil", ignore. var callkitAudioSessionActivated : Bool? = nil // if "nil", ignore.
var actionToFulFill : CXCallAction? = nil
var endCallKitReplacedCall: Bool = true
static var uuidReplacedCall: String?
var backgroundContextCall : Call? var backgroundContextCall : Call?
@objc var backgroundContextCameraIsEnabled : Bool = false @objc var backgroundContextCameraIsEnabled : Bool = false
fileprivate override init() { fileprivate override init() {
providerDelegate = ProviderDelegate.shared providerDelegate = ProviderDelegate.shared
callController = CXCallController() callController = CXCallController()
} }
@objc static func instance() -> CallManager { @objc static func instance() -> CallManager {
if (theCallManager == nil) { if (theCallManager == nil) {
theCallManager = CallManager() theCallManager = CallManager()
} }
return theCallManager! return theCallManager!
} }
@objc func setCore(core: OpaquePointer) { @objc func setCore(core: OpaquePointer) {
lc = Core.getSwiftObject(cObject: core) lc = Core.getSwiftObject(cObject: core)
lc?.addDelegate(delegate: self) lc?.addDelegate(delegate: self)
} }
@objc static func getAppData(call: OpaquePointer) -> CallAppData? { @objc static func getAppData(call: OpaquePointer) -> CallAppData? {
let sCall = Call.getSwiftObject(cObject: call) let sCall = Call.getSwiftObject(cObject: call)
return getAppData(sCall: sCall) return getAppData(sCall: sCall)
@ -82,7 +85,7 @@ import AVFoundation
} }
return Unmanaged<CallAppData>.fromOpaque(sCall.userData!).takeUnretainedValue() return Unmanaged<CallAppData>.fromOpaque(sCall.userData!).takeUnretainedValue()
} }
@objc static func setAppData(call:OpaquePointer, appData: CallAppData) { @objc static func setAppData(call:OpaquePointer, appData: CallAppData) {
let sCall = Call.getSwiftObject(cObject: call) let sCall = Call.getSwiftObject(cObject: call)
setAppData(sCall: sCall, appData: appData) setAppData(sCall: sCall, appData: appData)
@ -98,12 +101,12 @@ import AVFoundation
sCall.userData = UnsafeMutableRawPointer(Unmanaged.passRetained(appData!).toOpaque()) sCall.userData = UnsafeMutableRawPointer(Unmanaged.passRetained(appData!).toOpaque())
} }
} }
@objc func findCall(callId: String?) -> OpaquePointer? { @objc func findCall(callId: String?) -> OpaquePointer? {
let call = callByCallId(callId: callId) let call = callByCallId(callId: callId)
return call?.getCobject return call?.getCobject
} }
func callByCallId(callId: String?) -> Call? { func callByCallId(callId: String?) -> Call? {
if (callId == nil) { if (callId == nil) {
return nil return nil
@ -114,7 +117,7 @@ import AVFoundation
} }
return nil return nil
} }
@objc func stopLinphoneCore() { @objc func stopLinphoneCore() {
if (lc?.callsNb == 0) { if (lc?.callsNb == 0) {
lc?.stopAsync() lc?.stopAsync()
@ -133,11 +136,11 @@ import AVFoundation
} }
@objc static func callKitEnabled() -> Bool { @objc static func callKitEnabled() -> Bool {
#if !targetEnvironment(simulator) #if !targetEnvironment(simulator)
if ConfigManager.instance().lpConfigBoolForKey(key: "use_callkit", section: "app") { if ConfigManager.instance().lpConfigBoolForKey(key: "use_callkit", section: "app") {
return true return true
} }
#endif #endif
return false return false
} }
@ -208,17 +211,17 @@ import AVFoundation
} }
} }
} }
func displayIncomingCall(call:Call?, handle: String, hasVideo: Bool, callId: String, displayName:String) { func displayIncomingCall(call:Call?, handle: String, hasVideo: Bool, callId: String, displayName:String) {
let uuid = UUID() let uuid = UUID()
let callInfo = CallInfo.newIncomingCallInfo(callId: callId) let callInfo = CallInfo.newIncomingCallInfo(callId: callId)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid) providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: callId) providerDelegate.uuids.updateValue(uuid, forKey: callId)
providerDelegate.reportIncomingCall(call:call, uuid: uuid, handle: handle, hasVideo: hasVideo, displayName: displayName) providerDelegate.reportIncomingCall(call:call, uuid: uuid, handle: handle, hasVideo: hasVideo, displayName: displayName)
} }
@objc func acceptCall(call: OpaquePointer?, hasVideo:Bool) { @objc func acceptCall(call: OpaquePointer?, hasVideo:Bool) {
if (call == nil) { if (call == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not accept null call!") Log.directLog(BCTBX_LOG_ERROR, text: "Can not accept null call!")
@ -227,7 +230,7 @@ import AVFoundation
let call = Call.getSwiftObject(cObject: call!) let call = Call.getSwiftObject(cObject: call!)
acceptCall(call: call, hasVideo: hasVideo) acceptCall(call: call, hasVideo: hasVideo)
} }
func acceptCall(call: Call, hasVideo:Bool) { func acceptCall(call: Call, hasVideo:Bool) {
do { do {
let callParams = try lc!.createCallParams(call: call) let callParams = try lc!.createCallParams(call: call)
@ -239,7 +242,7 @@ import AVFoundation
} }
callParams.lowBandwidthEnabled = low_bandwidth callParams.lowBandwidthEnabled = low_bandwidth
} }
//We set the record file name here because we can't do it after the call is started. //We set the record file name here because we can't do it after the call is started.
let address = call.callLog?.fromAddress let address = call.callLog?.fromAddress
let writablePath = AppManager.recordingFilePathFromCall(address: address?.username ?? "") let writablePath = AppManager.recordingFilePathFromCall(address: address?.username ?? "")
@ -265,14 +268,14 @@ import AVFoundation
Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)") Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)")
} }
} }
// for outgoing call. There is not yet callId // for outgoing call. There is not yet callId
@objc func startCall(addr: OpaquePointer?, isSas: Bool, isVideo: Bool, isConference: Bool = false) { @objc func startCall(addr: OpaquePointer?, isSas: Bool, isVideo: Bool, isConference: Bool = false) {
if (addr == nil) { if (addr == nil) {
print("Can not start a call with null address!") Log.i("Can not start a call with null address!")
return return
} }
let sAddr = Address.getSwiftObject(cObject: addr!) let sAddr = Address.getSwiftObject(cObject: addr!)
if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && lc?.conference?.isIn != true) { if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && lc?.conference?.isIn != true) {
let uuid = UUID() let uuid = UUID()
@ -280,11 +283,11 @@ import AVFoundation
let handle = CXHandle(type: .generic, value: sAddr.asStringUriOnly()) let handle = CXHandle(type: .generic, value: sAddr.asStringUriOnly())
let startCallAction = CXStartCallAction(call: uuid, handle: handle) let startCallAction = CXStartCallAction(call: uuid, handle: handle)
let transaction = CXTransaction(action: startCallAction) let transaction = CXTransaction(action: startCallAction)
let callInfo = CallInfo.newOutgoingCallInfo(addr: sAddr, isSas: isSas, displayName: name, isVideo: isVideo, isConference:isConference) let callInfo = CallInfo.newOutgoingCallInfo(addr: sAddr, isSas: isSas, displayName: name, isVideo: isVideo, isConference:isConference)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid) providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "") providerDelegate.uuids.updateValue(uuid, forKey: "")
setHeldOtherCalls(exceptCallid: "") setHeldOtherCalls(exceptCallid: "")
requestTransaction(transaction, action: "startCall") requestTransaction(transaction, action: "startCall")
}else { }else {
@ -294,30 +297,30 @@ import AVFoundation
func startCall(addr:String, isSas: Bool = false, isVideo: Bool, isConference: Bool = false) { func startCall(addr:String, isSas: Bool = false, isVideo: Bool, isConference: Bool = false) {
do { do {
let address = try Factory.Instance.createAddress(addr: addr) let address = try Factory.Instance.createAddress(addr: addr)
startCall(addr: address.getCobject,isSas: isSas, isVideo: isVideo, isConference:isConference) startCall(addr: address.getCobject,isSas: isSas, isVideo: isVideo, isConference:isConference)
} catch { } catch {
Log.e("[CallManager] unable to create address for a new outgoing call : \(addr) \(error) ") Log.e("[CallManager] unable to create address for a new outgoing call : \(addr) \(error) ")
} }
} }
func doCall(addr: Address, isSas: Bool, isVideo: Bool, isConference:Bool = false) throws { func doCall(addr: Address, isSas: Bool, isVideo: Bool, isConference:Bool = false) throws {
let displayName = FastAddressBook.displayName(for: addr.getCobject) let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil) let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil)
if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g { if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode") Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
lcallParams.lowBandwidthEnabled = true lcallParams.lowBandwidthEnabled = true
} }
if (displayName != nil) { if (displayName != nil) {
try addr.setDisplayname(newValue: displayName!) try addr.setDisplayname(newValue: displayName!)
} }
if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) { if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant")) try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
} }
if (CallManager.instance().nextCallIsTransfer) { if (CallManager.instance().nextCallIsTransfer) {
let call = CallManager.instance().lc!.currentCall let call = CallManager.instance().lc!.currentCall
try call?.transferTo(referTo: addr) try call?.transferTo(referTo: addr)
@ -359,7 +362,7 @@ import AVFoundation
} }
} }
} }
@objc func groupCall() { @objc func groupCall() {
if (CallManager.callKitEnabled()) { if (CallManager.callKitEnabled()) {
let calls = lc?.calls let calls = lc?.calls
@ -368,29 +371,29 @@ import AVFoundation
} }
let firstCall = calls!.first?.callLog?.callId ?? "" let firstCall = calls!.first?.callLog?.callId ?? ""
let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : "" let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"] let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
if (currentUuid == nil) { if (currentUuid == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.") Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
return return
} }
let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"] let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid) let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
let transcation = CXTransaction(action: groupAction) let transcation = CXTransaction(action: groupAction)
requestTransaction(transcation, action: "groupCall") requestTransaction(transcation, action: "groupCall")
setResumeCalls() setResumeCalls()
} else { } else {
try? lc?.addAllToConference() try? lc?.addAllToConference()
} }
} }
@objc func removeAllCallInfos() { @objc func removeAllCallInfos() {
providerDelegate.callInfos.removeAll() providerDelegate.callInfos.removeAll()
providerDelegate.uuids.removeAll() providerDelegate.uuids.removeAll()
} }
@objc func terminateCall(call: OpaquePointer?) { @objc func terminateCall(call: OpaquePointer?) {
if (call == nil) { if (call == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not terminate null call!") Log.directLog(BCTBX_LOG_ERROR, text: "Can not terminate null call!")
@ -404,12 +407,12 @@ import AVFoundation
Log.directLog(BCTBX_LOG_ERROR, text: "Failed to terminate call failed because \(error)") Log.directLog(BCTBX_LOG_ERROR, text: "Failed to terminate call failed because \(error)")
} }
} }
@objc func markCallAsDeclined(callId: String) { @objc func markCallAsDeclined(callId: String) {
if !CallManager.callKitEnabled() { if !CallManager.callKitEnabled() {
return return
} }
let uuid = providerDelegate.uuids["\(callId)"] let uuid = providerDelegate.uuids["\(callId)"]
if (uuid == nil) { if (uuid == nil) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Mark call \(callId) as declined.") Log.directLog(BCTBX_LOG_MESSAGE, text: "Mark call \(callId) as declined.")
@ -423,7 +426,7 @@ import AVFoundation
providerDelegate.endCall(uuid: uuid!) providerDelegate.endCall(uuid: uuid!)
} }
} }
@objc func setHeld(call: OpaquePointer, hold: Bool) { @objc func setHeld(call: OpaquePointer, hold: Bool) {
let sCall = Call.getSwiftObject(cObject: call) let sCall = Call.getSwiftObject(cObject: call)
if (!hold) { if (!hold) {
@ -434,13 +437,13 @@ import AVFoundation
func setHeld(call: Call, hold: Bool) { func setHeld(call: Call, hold: Bool) {
#if targetEnvironment(simulator) #if targetEnvironment(simulator)
if (hold) { if (hold) {
try?call.pause() try?call.pause()
} else { } else {
try?call.resume() try?call.resume()
} }
#else #else
let callid = call.callLog?.callId ?? "" let callid = call.callLog?.callId ?? ""
let uuid = providerDelegate.uuids["\(callid)"] let uuid = providerDelegate.uuids["\(callid)"]
if (uuid == nil) { if (uuid == nil) {
@ -450,9 +453,9 @@ import AVFoundation
let setHeldAction = CXSetHeldCallAction(call: uuid!, onHold: hold) let setHeldAction = CXSetHeldCallAction(call: uuid!, onHold: hold)
let transaction = CXTransaction(action: setHeldAction) let transaction = CXTransaction(action: setHeldAction)
requestTransaction(transaction, action: "setHeld") requestTransaction(transaction, action: "setHeld")
#endif #endif
} }
@objc func setHeldOtherCalls(exceptCallid: String) { @objc func setHeldOtherCalls(exceptCallid: String) {
for call in CallManager.instance().lc!.calls { for call in CallManager.instance().lc!.calls {
if (call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote) { if (call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote) {
@ -460,7 +463,7 @@ import AVFoundation
} }
} }
} }
func setResumeCalls() { func setResumeCalls() {
for call in CallManager.instance().lc!.calls { for call in CallManager.instance().lc!.calls {
if (call.state == .Paused || call.state == .Pausing || call.state == .PausedByRemote) { if (call.state == .Paused || call.state == .Pausing || call.state == .PausedByRemote) {
@ -468,7 +471,7 @@ import AVFoundation
} }
} }
} }
@objc func performActionWhenCoreIsOn(action: @escaping ()->Void ) { @objc func performActionWhenCoreIsOn(action: @escaping ()->Void ) {
if (globalState == .On) { if (globalState == .On) {
action() action()
@ -476,14 +479,14 @@ import AVFoundation
actionsToPerformOnceWhenCoreIsOn.append(action) actionsToPerformOnceWhenCoreIsOn.append(action)
} }
} }
@objc func acceptVideo(call: OpaquePointer, confirm: Bool) { @objc func acceptVideo(call: OpaquePointer, confirm: Bool) {
let sCall = Call.getSwiftObject(cObject: call) let sCall = Call.getSwiftObject(cObject: call)
let params = try? lc?.createCallParams(call: sCall) let params = try? lc?.createCallParams(call: sCall)
params?.videoEnabled = confirm params?.videoEnabled = confirm
try? sCall.acceptUpdate(params: params) try? sCall.acceptUpdate(params: params)
} }
func onGlobalStateChanged(core: Core, state: GlobalState, message: String) { func onGlobalStateChanged(core: Core, state: GlobalState, message: String) {
if (state == .On) { if (state == .On) {
actionsToPerformOnceWhenCoreIsOn.forEach { actionsToPerformOnceWhenCoreIsOn.forEach {
@ -493,7 +496,7 @@ import AVFoundation
} }
globalState = state globalState = state
} }
func onRegistrationStateChanged(core: Core, proxyConfig: ProxyConfig, state: RegistrationState, message: String) { func onRegistrationStateChanged(core: Core, proxyConfig: ProxyConfig, state: RegistrationState, message: String) {
if core.proxyConfigList.count == 1 && (state == .Failed || state == .Cleared){ if core.proxyConfigList.count == 1 && (state == .Failed || state == .Cleared){
// terminate callkit immediately when registration failed or cleared, supporting single proxy configuration // terminate callkit immediately when registration failed or cleared, supporting single proxy configuration
@ -506,7 +509,7 @@ import AVFoundation
continue continue
} }
} }
CallManager.instance().providerDelegate.endCall(uuid: call.value) CallManager.instance().providerDelegate.endCall(uuid: call.value)
} }
CallManager.instance().endCallkit = true CallManager.instance().endCallkit = true
@ -533,7 +536,7 @@ import AVFoundation
return FastAddressBook.displayName(for: call.remoteAddress?.getCobject) ?? "Unknown" return FastAddressBook.displayName(for: call.remoteAddress?.getCobject) ?? "Unknown"
} }
} }
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) { func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
let callLog = call.callLog let callLog = call.callLog
let callId = callLog?.callId let callId = callLog?.callId
@ -541,7 +544,7 @@ import AVFoundation
displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId!, displayName: "Calling") displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId!, displayName: "Calling")
} else { } else {
let video = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false) let video = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
if (call.userData == nil) { if (call.userData == nil) {
let appData = CallAppData() let appData = CallAppData()
CallManager.setAppData(sCall: call, appData: appData) CallManager.setAppData(sCall: call, appData: appData)
@ -552,161 +555,194 @@ import AVFoundation
ConferenceViewModel.shared.initConference(conference) ConferenceViewModel.shared.initConference(conference)
ConferenceViewModel.shared.configureConference(conference) ConferenceViewModel.shared.configureConference(conference)
} }
switch cstate { switch cstate {
case .IncomingReceived: case .IncomingReceived:
let addr = call.remoteAddress let addr = call.remoteAddress
var displayName = incomingDisplayName(call: call) var displayName = incomingDisplayName(call: call)
if (CallManager.callKitEnabled()) { if call.replacedCall != nil {
let isConference = isConferenceCall(call: call) endCallKitReplacedCall = false
let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
if (isEarlyConference) { let uuid = CallManager.instance().providerDelegate.uuids["\(CallManager.uuidReplacedCall)"]
CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!]
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"] callInfo!.callId = CallManager.instance().referedToCall ?? ""
if (uuid != nil) { CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))" CallManager.instance().providerDelegate.uuids.removeValue(forKey: callId!)
CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName) CallManager.instance().providerDelegate.uuids.updateValue(uuid!, forKey: callInfo!.callId)
} CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
} } else if (CallManager.callKitEnabled()) {
} let isConference = isConferenceCall(call: call)
let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"] if (isEarlyConference) {
if (uuid != nil) { CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
// Tha app is now registered, updated the call already existed. let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName) if (uuid != nil) {
} else { displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))"
CallManager.instance().displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: video, callId: callId!, displayName: displayName) CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
}
} else if (UIApplication.shared.applicationState != .active) {
// not support callkit , use notif
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Incoming call", comment: "")
content.body = displayName
content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
content.categoryIdentifier = "call_cat"
content.userInfo = ["CallId" : callId!]
let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
}
break
case .StreamsRunning:
if (CallManager.callKitEnabled()) {
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
if (uuid != nil) {
let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!]
if (callInfo != nil && callInfo!.isOutgoing && !callInfo!.connected) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call connected with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallConnected(uuid: uuid!)
callInfo!.connected = true
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
} }
} }
} }
if (CallManager.instance().speakerBeforePause) { let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
CallManager.instance().speakerBeforePause = false if call.replacedCall == nil {
CallManager.instance().changeRouteToSpeaker() CallManager.uuidReplacedCall = callId
} }
break
case .OutgoingInit, if (uuid != nil) {
.OutgoingProgress, // Tha app is now registered, updated the call already existed.
.OutgoingRinging, CallManager.instance().providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
.OutgoingEarlyMedia: } else {
if (CallManager.callKitEnabled()) { CallManager.instance().displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: video, callId: callId!, displayName: displayName)
let uuid = CallManager.instance().providerDelegate.uuids[""] }
if (uuid != nil) { } else if (UIApplication.shared.applicationState != .active) {
let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!] // not support callkit , use notif
callInfo!.callId = callId! let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Incoming call", comment: "")
content.body = displayName
content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
content.categoryIdentifier = "call_cat"
content.userInfo = ["CallId" : callId!]
let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
}
break
case .StreamsRunning:
if (CallManager.callKitEnabled()) {
let uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
if (uuid != nil) {
let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!]
if (callInfo != nil && callInfo!.isOutgoing && !callInfo!.connected) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call connected with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallConnected(uuid: uuid!)
callInfo!.connected = true
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!) CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
CallManager.instance().providerDelegate.uuids.removeValue(forKey: "") }
CallManager.instance().providerDelegate.uuids.updateValue(uuid!, forKey: callId!) }
}
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call started connecting with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!) if (CallManager.instance().speakerBeforePause) {
CallManager.instance().speakerBeforePause = false
CallManager.instance().changeRouteToSpeaker()
}
actionToFulFill?.fulfill()
actionToFulFill = nil
break
case .Paused:
actionToFulFill?.fulfill()
actionToFulFill = nil
break
case .OutgoingInit,
.OutgoingProgress,
.OutgoingRinging,
.OutgoingEarlyMedia:
if (CallManager.callKitEnabled()) {
let uuid = CallManager.instance().providerDelegate.uuids[""]
if (uuid != nil) {
let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!]
callInfo!.callId = callId!
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
CallManager.instance().providerDelegate.uuids.removeValue(forKey: "")
CallManager.instance().providerDelegate.uuids.updateValue(uuid!, forKey: callId!)
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: outgoing call started connecting with uuid \(uuid!) and callId \(callId!)")
CallManager.instance().providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
} else {
if CallManager.instance().isConferenceCall(call: call) {
let uuid = UUID()
let callInfo = CallInfo.newOutgoingCallInfo(addr: call.remoteAddress!, isSas: call.params?.mediaEncryption == .ZRTP, displayName: VoipTexts.conference_default_title, isVideo: call.params?.videoEnabled == true, isConference:true)
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
CallManager.instance().providerDelegate.uuids.updateValue(uuid, forKey: "")
CallManager.instance().providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
Core.get().activateAudioSession(actived: true)
} else { } else {
CallManager.instance().referedToCall = callId CallManager.instance().referedToCall = callId
} }
} }
break }
case .End, break
.Error: case .End,
var displayName = "Unknown" .Error:
if (call.dir == .Incoming) { var displayName = "Unknown"
displayName = incomingDisplayName(call: call) if (call.dir == .Incoming) {
} else if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) { displayName = incomingDisplayName(call: call)
displayName = contactName } else if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
} displayName = contactName
}
UIDevice.current.isProximityMonitoringEnabled = false
if (CallManager.instance().lc!.callsNb == 0) {
CallManager.instance().changeRouteToDefault()
// disable this because I don't find anygood reason for it: _bluetoothAvailable = FALSE;
// furthermore it introduces a bug when calling multiple times since route may not be
// reconfigured between cause leading to bluetooth being disabled while it should not
//CallManager.instance().bluetoothEnabled = false
}
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil)
UIDevice.current.isProximityMonitoringEnabled = false // Deliver the notification.
if (CallManager.instance().lc!.callsNb == 0) { let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
CallManager.instance().changeRouteToDefault() let center = UNUserNotificationCenter.current()
// disable this because I don't find anygood reason for it: _bluetoothAvailable = FALSE; center.add(request) { (error : Error?) in
// furthermore it introduces a bug when calling multiple times since route may not be if error != nil {
// reconfigured between cause leading to bluetooth being disabled while it should not
//CallManager.instance().bluetoothEnabled = false
}
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil)
// Deliver the notification.
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request) { (error : Error?) in
if error != nil {
Log.directLog(BCTBX_LOG_ERROR, text: "Error while adding notification request : \(error!.localizedDescription)") Log.directLog(BCTBX_LOG_ERROR, text: "Error while adding notification request : \(error!.localizedDescription)")
}
} }
} }
}
if (CallManager.callKitEnabled()) {
var uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"] if (CallManager.callKitEnabled()) {
if (callId == CallManager.instance().referedToCall) { var uuid = CallManager.instance().providerDelegate.uuids["\(callId!)"]
// refered call ended before connecting if (callId == CallManager.instance().referedToCall) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Callkit: end refered to call : \(String(describing: CallManager.instance().referedToCall))") // refered call ended before connecting
Log.directLog(BCTBX_LOG_MESSAGE, text: "Callkit: end refered to call : \(String(describing: CallManager.instance().referedToCall))")
CallManager.instance().referedFromCall = nil
CallManager.instance().referedToCall = nil
}
if uuid == nil {
// the call not yet connected
uuid = CallManager.instance().providerDelegate.uuids[""]
}
if (uuid != nil) {
if (callId == CallManager.instance().referedFromCall) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Callkit: end refered from call : \(String(describing: CallManager.instance().referedFromCall))")
CallManager.instance().referedFromCall = nil CallManager.instance().referedFromCall = nil
let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!]
callInfo!.callId = CallManager.instance().referedToCall ?? ""
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
CallManager.instance().providerDelegate.uuids.removeValue(forKey: callId!)
CallManager.instance().providerDelegate.uuids.updateValue(uuid!, forKey: callInfo!.callId)
CallManager.instance().referedToCall = nil CallManager.instance().referedToCall = nil
break
} }
if uuid == nil { if (endCallKitReplacedCall){
// the call not yet connected
uuid = CallManager.instance().providerDelegate.uuids[""]
}
if (uuid != nil) {
if (callId == CallManager.instance().referedFromCall) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Callkit: end refered from call : \(String(describing: CallManager.instance().referedFromCall))")
CallManager.instance().referedFromCall = nil
let callInfo = CallManager.instance().providerDelegate.callInfos[uuid!]
callInfo!.callId = CallManager.instance().referedToCall ?? ""
CallManager.instance().providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
CallManager.instance().providerDelegate.uuids.removeValue(forKey: callId!)
CallManager.instance().providerDelegate.uuids.updateValue(uuid!, forKey: callInfo!.callId)
CallManager.instance().referedToCall = nil
break
}
let transaction = CXTransaction(action: let transaction = CXTransaction(action:
CXEndCallAction(call: uuid!)) CXEndCallAction(call: uuid!))
CallManager.instance().requestTransaction(transaction, action: "endCall") CallManager.instance().requestTransaction(transaction, action: "endCall")
}else{
endCallKitReplacedCall = true
} }
} }
break }
case .Released: break
call.userData = nil case .Released:
break CallManager.setAppData(sCall : call, appData : nil);
case .Referred: break
CallManager.instance().referedFromCall = call.callLog?.callId case .Referred:
break CallManager.instance().referedFromCall = call.callLog?.callId
default: break
break default:
break
} }
let readyForRoutechange = CallManager.instance().callkitAudioSessionActivated == nil || (CallManager.instance().callkitAudioSessionActivated == true) let readyForRoutechange = CallManager.instance().callkitAudioSessionActivated == nil || (CallManager.instance().callkitAudioSessionActivated == true)
if (readyForRoutechange && (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning)) { if (readyForRoutechange && (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning)) {
if ((call.currentParams?.videoEnabled ?? false) && CallManager.instance().isReceiverEnabled()) { if ((call.currentParams?.videoEnabled ?? false) && CallManager.instance().isReceiverEnabled() && call.conference == nil) {
CallManager.instance().changeRouteToSpeaker() CallManager.instance().changeRouteToSpeaker()
} else if (isBluetoothAvailable()) { } else if (isBluetoothAvailable()) {
// Use bluetooth device by default if one is available // Use bluetooth device by default if one is available
@ -721,7 +757,7 @@ import AVFoundation
AnyHashable("message"): message AnyHashable("message"): message
]) ])
} }
// Audio messages // Audio messages
@objc func activateAudioSession() { @objc func activateAudioSession() {
@ -742,7 +778,7 @@ import AVFoundation
} }
return speakerCard != nil ? speakerCard : earpieceCard return speakerCard != nil ? speakerCard : earpieceCard
} }
// Local Conference // Local Conference
@objc func startLocalConference() { @objc func startLocalConference() {
@ -753,18 +789,18 @@ import AVFoundation
} }
let firstCall = calls!.first?.callLog?.callId ?? "" let firstCall = calls!.first?.callLog?.callId ?? ""
let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : "" let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"] let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
if (currentUuid == nil) { if (currentUuid == nil) {
Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.") Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
return return
} }
let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"] let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid) let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
let transcation = CXTransaction(action: groupAction) let transcation = CXTransaction(action: groupAction)
requestTransaction(transcation, action: "groupCall") requestTransaction(transcation, action: "groupCall")
setResumeCalls() setResumeCalls()
} else { } else {
addAllToLocalConference() addAllToLocalConference()
@ -775,6 +811,7 @@ import AVFoundation
do { do {
if let core = lc, let params = try? core.createConferenceParams(conference: nil) { if let core = lc, let params = try? core.createConferenceParams(conference: nil) {
params.videoEnabled = false // We disable video for local conferencing (cf Android) params.videoEnabled = false // We disable video for local conferencing (cf Android)
params.subject = VoipTexts.conference_local_title
let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params) let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params)
try conference?.addParticipants(calls: core.calls) try conference?.addParticipants(calls: core.calls)
} }
@ -783,8 +820,12 @@ import AVFoundation
} }
} }
@objc func applyInternationalPrefix() -> Bool {
if let account = lc?.defaultAccount, let params = account.params {
return params.useInternationalPrefixForCallsAndChats
}
return true; // Legacy behavior
}
} }

View file

@ -0,0 +1,106 @@
/*
* 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
enum FileType : String {
case pdf = "pdf";
case png = "png";
case jpg = "jpg";
case jpeg = "jpeg";
case bmp = "bmp";
case heic = "heic";
case mkv = "mkv";
case avi = "avi";
case mov = "mov";
case mp4 = "mp4";
case wav = "wav";
case au = "au";
case m4a = "m4a";
case other = "other";
case file_pdf_default = "file_pdf_default";
case file_picture_default = "file_picture_default";
case file_video_default = "file_video_default";
case file_audio_default = "file_audio_default";
case file_default = "file_default";
func getGroupTypeFromFile() -> String? {
switch self {
case .pdf, .file_pdf_default:
return "file_pdf_default"
case .png, .jpg, .jpeg, .bmp, .heic, .file_picture_default:
return "file_picture_default"
case .mkv, .avi, .mov, .mp4, .file_video_default:
return "file_video_default"
case .wav, .au, .m4a, .file_audio_default:
return "file_audio_default"
default:
return "file_default"
}
}
func getImageFromFile() -> UIImage? {
switch self {
case .pdf, .file_pdf_default:
return UIImage(named:"file_pdf_default")
case .png, .jpg, .jpeg, .bmp, .heic, .file_picture_default:
return UIImage(named:"file_picture_default")
case .mkv, .avi, .mov, .mp4, .file_video_default:
return UIImage(named:"file_video_default")
case .wav, .au, .m4a, .file_audio_default:
return UIImage(named:"file_audio_default")
default:
return UIImage(named:"file_default")
}
}
}
extension FileType {
init() {
self = .file_default
}
init?(_ value: String) {
switch value.lowercased() {
case "pdf", "file_pdf_default":
self = .file_pdf_default
case "png", "jpg", "jpeg", "bmp", "heic", "file_picture_default":
self = .file_picture_default
case "mkv", "avi", "mov", "mp4", "file_video_default":
self = .file_video_default
case "wav", "au", "m4a", "file_audio_default":
self = .file_audio_default
default:
self = .file_default
}
}
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
class ChatConversationTableViewModel {
static let sharedModel = ChatConversationTableViewModel()
var chatRoom: ChatRoom? = nil
var refreshIndexPath = MutableLiveData<Int>(0)
var onClickIndexPath = MutableLiveData<Int>(0)
var onClickMessageIndexPath = 0
var editModeOn = MutableLiveData<Bool>(false)
var messageSelected = MutableLiveData<Int>(0)
var messageListSelected = MutableLiveData<[Bool]>([])
var messageListToDelete : [EventLog] = []
func getMessage(index: Int) -> EventLog? {
if (chatRoom != nil) {
let chatRoomEvents = chatRoom?.getHistoryRangeEvents(begin: index, end: index+1)
return chatRoomEvents?.first
}else{
return nil
}
}
func getIndexMessage(message: ChatMessage) -> Int {
var index = -1
if (chatRoom == nil) {
return index
}
var indexRange = 0
let msgId = message.messageId
while index == -1 {
let chatRoomEvents = chatRoom?.getHistoryRangeEvents(begin: indexRange, end: indexRange+20)
if chatRoomEvents?.count == 0 {
index = -2
}
chatRoomEvents?.reversed().forEach({ event in
let chat = event.chatMessage
if (chat != nil && msgId == chat?.messageId) {
index = indexRange
}
indexRange += 1
})
}
return index
}
func getNBMessages() -> Int {
if (chatRoom == nil) {
return 0
}
return chatRoom!.historyEventsSize
}
func eventTypeIsOfInterestForOne(toOneRoom type: EventLogType) -> Bool {
return type.rawValue == LinphoneEventLogTypeConferenceChatMessage.rawValue || type.rawValue == LinphoneEventLogTypeConferenceEphemeralMessageEnabled.rawValue || type.rawValue == LinphoneEventLogTypeConferenceEphemeralMessageDisabled.rawValue || type.rawValue == LinphoneEventLogTypeConferenceEphemeralMessageLifetimeChanged.rawValue
}
func reloadCollectionViewCell(){
refreshIndexPath.value! += 1
}
func onGridClick(indexMessage: Int, index :Int){
onClickMessageIndexPath = indexMessage
onClickIndexPath.value! = index
}
func changeEditMode(editMode :Bool){
editModeOn.value = editMode
}
func selectAllMessages(){
for i in 0...messageListSelected.value!.count - 1 {
messageListSelected.value![i] = true
messageSelected.value! += 1
}
refreshIndexPath.value! += 1
}
func unSelectAllMessages(){
for i in 0...messageListSelected.value!.count - 1 {
messageListSelected.value![i] = false
}
messageSelected.value! = 0
refreshIndexPath.value! += 1
}
func deleteMessagesSelected(){
for i in 0...(messageListSelected.value!.count - 1) {
if messageListSelected.value![i] == true {
let messageEvent = getMessage(index: i)
//if messageEvent
messageListToDelete.append((messageEvent)!)
}
}
messageListToDelete.forEach { chatMessage in
chatMessage.deleteFromDatabase()
}
messageSelected.value! = 0
refreshIndexPath.value! += 1
}
}

View file

@ -0,0 +1,584 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
class ChatConversationViewModel {
static let sharedModel = ChatConversationViewModel()
let APP_GROUP_ID = "group.belledonne-communications.linphone.widget"
var chatRoom: ChatRoom? = nil
var chatRoomDelegate: ChatRoomDelegate? = nil
var mediaCount : Int = 0
var newMediaCount : Int = 0
var address: String? = nil
var participants: String? = nil
var subject: String? = nil
var shareFileMessage: String? = nil
var debugEnabled = false
var isVoiceRecording = false
var showVoiceRecorderView = false
var isPendingVoiceRecord = false
var isPlayingVoiceRecording = false
var isOneToOneChat = false
var urlFile : [URL?] = []
var mediaURLCollection : [URL] = []
var replyURLCollection : [URL] = []
var data : [Data?] = []
var fileContext : [Data] = []
var progress : [Progress] = []
var workItem : DispatchWorkItem? = nil
var replyMessage : OpaquePointer? = nil
var vrRecordTimer = Timer()
var voiceRecorder : Recorder? = nil
var secureLevel : UIImage?
var imageT : [UIImage?] = []
var mediaCollectionView : [UIImage] = []
var replyCollectionView : [UIImage] = []
var isComposing = MutableLiveData<Bool>(false)
var messageReceived = MutableLiveData<EventLog>()
var stateChanged = MutableLiveData<ChatRoom>()
var secureLevelChanged = MutableLiveData<EventLog>()
var subjectChanged = MutableLiveData<EventLog>()
var eventLog = MutableLiveData<EventLog>()
var indexPathVM = MutableLiveData<Int>()
var shareFileURL = MutableLiveData<String>()
var shareFileName = MutableLiveData<String>()
func resetViewModel(){
chatRoom?.removeDelegate(delegate: chatRoomDelegate!)
mediaURLCollection = []
replyURLCollection.removeAll()
fileContext = []
urlFile = []
data = []
workItem?.cancel()
for progressItem in progress{
progressItem.cancel()
}
progress.removeAll()
}
func createChatConversation(){
chatRoomDelegate = ChatRoomDelegateStub(
onIsComposingReceived: { (room: ChatRoom, remoteAddress: Address, isComposing: Bool) -> Void in
self.on_chat_room_is_composing_received(room, remoteAddress, isComposing)
}, onChatMessageReceived: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_chat_message_received(room, event)
}, onChatMessageSending: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_event_log(room, event)
}, onParticipantAdded: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_secure_level(room, event)
}, onParticipantRemoved: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_secure_level(room, event)
}, onParticipantAdminStatusChanged: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_event_log(room, event)
}, onStateChanged: { (room: ChatRoom, state: ChatRoom.State) -> Void in
self.on_chat_room_state_changed(room)
}, onSecurityEvent: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_secure_level(room, event)
}, onSubjectChanged: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_subject_changed(room, event)
}, onConferenceJoined: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_event_log(room, event)
}, onConferenceLeft: { (room: ChatRoom, event: EventLog) -> Void in
self.on_chat_room_event_log(room, event)
}
)
chatRoom?.addDelegate(delegate: chatRoomDelegate!)
workItem = DispatchWorkItem {
let indexPath = IndexPath(row: self.mediaCollectionView.count, section: 0)
self.mediaURLCollection.append(self.urlFile[indexPath.row]!)
self.mediaCollectionView.append(self.imageT[indexPath.row]!)
self.fileContext.append(self.data[indexPath.row]!)
if(self.mediaCount + self.newMediaCount <= indexPath.row+1){
self.indexPathVM.value = indexPath.row
}
}
}
func on_chat_room_is_composing_received(_ cr: ChatRoom?, _ remoteAddr: Address?, _ isComposingBool: Bool) {
isComposing.value = (linphone_chat_room_is_remote_composing(cr?.getCobject) != 0) || bctbx_list_size(linphone_chat_room_get_composing_addresses(cr?.getCobject)) > 0
}
func on_chat_room_chat_message_received(_ cr: ChatRoom?, _ event_log: EventLog?) {
let chat = event_log?.chatMessage
if chat == nil {
return
}
var hasFile = false
// if auto_download is available and file is downloaded
if (linphone_core_get_max_size_for_auto_download_incoming_files(LinphoneManager.getLc()) > -1) && (chat?.fileTransferInformation != nil) {
hasFile = true
}
var returnValue = false;
chat?.contents.forEach({ content in
if !content.isFileTransfer && !content.isText && !content.isVoiceRecording && !hasFile {
returnValue = true
}
})
if returnValue {
return
}
let from = chat?.fromAddress
if from == nil {
return
}
messageReceived.value = event_log
}
func on_chat_room_state_changed(_ cr: ChatRoom?) {
isOneToOneChat = chatRoom!.hasCapability(mask: Int(LinphoneChatRoomCapabilitiesOneToOne.rawValue))
secureLevel = FastAddressBook.image(for: linphone_chat_room_get_security_level(cr?.getCobject))
stateChanged.value = cr
}
func on_chat_room_subject_changed(_ cr: ChatRoom?, _ event_log: EventLog?) {
subject = event_log?.subject != nil ? event_log?.subject : cr?.subject
subjectChanged.value = event_log
}
func on_chat_room_secure_level(_ cr: ChatRoom?, _ event_log: EventLog?) {
secureLevel = FastAddressBook.image(for: linphone_chat_room_get_security_level(cr?.getCobject))
secureLevelChanged.value = event_log
}
func on_chat_room_event_log(_ cr: ChatRoom?, _ event_log: EventLog?) {
eventLog.value = event_log
}
func nsDataRead() -> Data? {
let groupName = "group.\(Bundle.main.bundleIdentifier ?? "").linphoneExtension"
let path = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupName)?.path
let fullCacheFilePathPath = "\(path ?? "")/\("nsData")"
return NSData(contentsOfFile: fullCacheFilePathPath) as Data?
}
func sendMessage(message: String?, withExterlBodyUrl externalUrl: URL?, rootMessage: ChatMessage?) -> Bool {
if chatRoom == nil {
return false
}
let msg = rootMessage
let basic = isBasicChatRoom(chatRoom?.getCobject)
let params = linphone_account_get_params(linphone_core_get_default_account(LinphoneManager.getLc()))
let cpimEnabled = linphone_account_params_cpim_in_basic_chat_room_enabled(params)
if (!basic || (cpimEnabled != 0)) && (message != nil) && message!.count > 0 {
linphone_chat_message_add_utf8_text_content(msg?.getCobject, message)
}
if (externalUrl != nil) {
linphone_chat_message_set_external_body_url(msg?.getCobject, externalUrl!.absoluteString)
}
let contentList = linphone_chat_message_get_contents(msg?.getCobject)
if bctbx_list_size(contentList) > 0 {
linphone_chat_message_send(msg?.getCobject)
}
if basic && (cpimEnabled == 0) && (message != nil) && message!.count > 0 {
linphone_chat_message_send(linphone_chat_room_create_message_from_utf8(chatRoom?.getCobject, message))
}
return true
}
func isBasicChatRoom(_ room: OpaquePointer?) -> Bool {
if room == nil {
return true
}
let charRoomBasic = ChatRoom.getSwiftObject(cObject: room!)
let isBasic = charRoomBasic.hasCapability(mask: Int(LinphoneChatRoomCapabilitiesBasic.rawValue))
return isBasic
}
func startUploadData(_ data: Data?, withType type: String?, withName name: String?, andMessage message: String?, rootMessage: ChatMessage?){
let fileTransfer = FileTransferDelegate.init()
if let msg = message {
fileTransfer.text = msg
}
var resultType = "file"
var key = "localfile"
if type == "file_video_default" {
resultType = "video"
key = "localvideo"
} else if type == "file_picture_default" {
resultType = "image"
key = "localimage"
}
fileTransfer.uploadData(data, for: chatRoom?.getCobject, type: resultType, subtype: resultType, name: name, key: key, rootMessage: rootMessage?.getCobject)
}
func startFileUpload(_ data: Data?, withName name: String?, rootMessage: ChatMessage?){
let fileTransfer = FileTransferDelegate()
fileTransfer.uploadFile(data, for: ChatConversationViewModel.sharedModel.chatRoom?.getCobject, withName: name, rootMessage: rootMessage?.getCobject)
}
func shareFile() {
let groupName = "group.\(Bundle.main.bundleIdentifier ?? "").linphoneExtension"
let defaults = UserDefaults(suiteName: groupName)
let dict = defaults?.value(forKey: "photoData") as? [AnyHashable : Any]
if let dict_notnil = dict {
//file shared from photo lib
shareFileMessage = dict_notnil["message"] as? String
shareFileName.value = dict_notnil["url"] as? String
defaults?.removeObject(forKey: "photoData")
} else if let dictFile = defaults?.value(forKey: "icloudData") as? [AnyHashable : Any] {
shareFileMessage = dict?["message"] as? String
shareFileName.value = dictFile["url"] as? String
defaults?.removeObject(forKey: "icloudData")
} else if let dictUrl = defaults?.value(forKey: "url") as? [AnyHashable : Any] {
shareFileMessage = dict?["message"] as? String
shareFileURL.value = dictUrl["url"] as? String
defaults?.removeObject(forKey: "url")
}
}
func getImageFrom(_ content: OpaquePointer?, filePath: String?, forReplyBubble: Bool) -> UIImage? {
var filePath = filePath
let type = String(utf8String: linphone_content_get_type(content))
let name = String(utf8String: linphone_content_get_name(content))
if filePath == nil {
filePath = LinphoneManager.validFilePath(name)
}
var image: UIImage? = nil
if type == "video" {
image = UIChatBubbleTextCell.getImageFromVideoUrl(URL(fileURLWithPath: filePath ?? ""))
} else if type == "image" {
image = UIImage(named: filePath ?? "")
}
if let img = image {
return img
} else {
return getImageFromFileName(name, forReplyBubble: forReplyBubble)
}
}
func getImageFromFileName(_ fileName: String?, forReplyBubble forReplyBubbble: Bool) -> UIImage? {
let extensionFile = fileName?.lowercased().components(separatedBy: ".").last
var image: UIImage?
var text = fileName
if fileName?.contains("voice-recording") ?? false {
image = UIImage(named: "file_voice_default")
text = recordingDuration(LinphoneManager.validFilePath(fileName))
} else {
if extensionFile == "pdf" {
image = UIImage(named: "file_pdf_default")
} else if ["png", "jpg", "jpeg", "bmp", "heic"].contains(extensionFile ?? "") {
image = UIImage(named: "file_picture_default")
} else if ["mkv", "avi", "mov", "mp4"].contains(extensionFile ?? "") {
image = UIImage(named: "file_video_default")
} else if ["wav", "au", "m4a"].contains(extensionFile ?? "") {
image = UIImage(named: "file_audio_default")
} else {
image = UIImage(named: "file_default")
}
}
return SwiftUtil.textToImage(drawText: text!, inImage: image!, forReplyBubble: forReplyBubbble)
}
func recordingDuration(_ _voiceRecordingFile: String?) -> String? {
let core = Core.getSwiftObject(cObject: LinphoneManager.getLc())
var result = ""
do{
let linphonePlayer = try core.createLocalPlayer(soundCardName: nil, videoDisplayName: nil, windowId: nil)
try linphonePlayer.open(filename: _voiceRecordingFile!)
result = formattedDuration(linphonePlayer.duration)!
linphonePlayer.close()
}catch{
Log.e(error.localizedDescription)
}
return result
}
func formattedDuration(_ valueMs: Int) -> String? {
return String(format: "%02ld:%02ld", valueMs / 60000, (valueMs % 60000) / 1000)
}
func writeMediaToGalleryFromName(_ name: String?, fileType: String?) {
let filePath = LinphoneManager.validFilePath(name)
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath!) {
let data = NSData(contentsOfFile: filePath!) as Data?
let block: (() -> Void)? = {
if fileType == "image" {
// we're finished, save the image and update the message
let image = UIImage(data: data!)
if image == nil {
ChatConversationViewSwift.showFileDownloadError()
return
}
var placeHolder: PHObjectPlaceholder? = nil
PHPhotoLibrary.shared().performChanges({
let request = PHAssetCreationRequest.creationRequestForAsset(from: image!)
placeHolder = request.placeholderForCreatedAsset
}) { success, error in
DispatchQueue.main.async(execute: {
if error != nil {
Log.e("Cannot save image data downloaded \(error!.localizedDescription)")
let errView = UIAlertController(
title: NSLocalizedString("Transfer error", comment: ""),
message: NSLocalizedString("Cannot write image to photo library", comment: ""),
preferredStyle: .alert)
let defaultAction = UIAlertAction(
title: "OK",
style: .default,
handler: { action in
})
errView.addAction(defaultAction)
PhoneMainView.instance()!.present(errView, animated: true)
} else {
Log.i("Image saved to \(placeHolder!.localIdentifier)")
}
})
}
} else if fileType == "video" {
var placeHolder: PHObjectPlaceholder?
PHPhotoLibrary.shared().performChanges({
let request = PHAssetCreationRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath!))
placeHolder = request?.placeholderForCreatedAsset
}) { success, error in
DispatchQueue.main.async(execute: {
if error != nil {
Log.e("Cannot save video data downloaded \(error!.localizedDescription)")
let errView = UIAlertController(
title: NSLocalizedString("Transfer error", comment: ""),
message: NSLocalizedString("Cannot write video to photo library", comment: ""),
preferredStyle: .alert)
let defaultAction = UIAlertAction(
title: "OK",
style: .default,
handler: { action in
})
errView.addAction(defaultAction)
PhoneMainView.instance()!.present(errView, animated: true)
} else {
Log.i("video saved to \(placeHolder!.localIdentifier)")
}
})
}
}
}
if PHPhotoLibrary.authorizationStatus() == .authorized {
block!()
} else {
PHPhotoLibrary.requestAuthorization({ status in
DispatchQueue.main.async(execute: {
if PHPhotoLibrary.authorizationStatus() == .authorized {
block!()
} else {
let alert = UIAlertController(title: NSLocalizedString("Photo's permission", comment: ""), message: NSLocalizedString("Photo not authorized", comment: ""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default))
PhoneMainView.instance()!.present(alert, animated: true)
}
})
})
}
}
}
func createCollectionViewItem(urlFile: URL?, type: String) {
if let url = urlFile {
do {
if(type == "public.image"){
let dataResult = try Data(contentsOf: url)
ChatConversationViewModel.sharedModel.data.append(dataResult)
if let image = UIImage(data: dataResult) {
ChatConversationViewModel.sharedModel.imageT.append(image)
}else{
ChatConversationViewModel.sharedModel.imageT.append(UIImage(named: "chat_error"))
}
}else if(type == "public.movie"){
ChatConversationViewModel.sharedModel.data.append(try Data(contentsOf: url))
var tmpImage = ChatConversationViewModel.sharedModel.createThumbnailOfVideoFromFileURL(videoURL: url.relativeString)
if tmpImage == nil { tmpImage = UIImage(named: "chat_error")}
ChatConversationViewModel.sharedModel.imageT.append(tmpImage)
}else{
ChatConversationViewModel.sharedModel.data.append(try Data(contentsOf: url))
let otherFile = FileType.init(url.pathExtension)
let otherFileImage = otherFile!.getImageFromFile()
ChatConversationViewModel.sharedModel.imageT.append(otherFileImage)
}
ChatConversationViewModel.sharedModel.urlFile.append(url)
DispatchQueue.main.async(execute: ChatConversationViewModel.sharedModel.workItem!)
}catch let error{
Log.e(error.localizedDescription)
}
}
}
func createCollectionViewItemForReply(urlFile: URL?, type: String) -> UIImage {
if urlFile != nil {
do {
if(type == "public.image"){
let dataResult = try Data(contentsOf: urlFile!)
if let image = UIImage(data: dataResult) {
return image
}else{
return UIImage(named: "chat_error")!
}
}else if(type == "public.movie"){
var tmpImage = ChatConversationViewModel.sharedModel.createThumbnailOfVideoFromFileURL(videoURL: urlFile!.relativeString)
if tmpImage == nil { tmpImage = UIImage(named: "chat_error")}
return tmpImage!
}else{
let otherFile = FileType.init(urlFile!.pathExtension)
let otherFileImage = otherFile!.getImageFromFile()
return otherFileImage!
}
}catch let error{
Log.e(error.localizedDescription)
}
}
return UIImage(named: "chat_error")!
}
func createThumbnailOfVideoFromFileURL(videoURL: String) -> UIImage? {
if let urlVideo = URL(string: videoURL){
let asset = AVAsset(url: urlVideo)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
do {
let img = try assetImgGenerate.copyCGImage(at: CMTimeMake(value: 1, timescale: 10), actualTime: nil)
let thumbnail = UIImage(cgImage: img)
return thumbnail
} catch _{
return nil
}
} else {
return nil
}
}
//Voice recoder and player
func createVoiceRecorder() {
let core = Core.getSwiftObject(cObject: LinphoneManager.getLc())
do{
let p = try core.createRecorderParams()
p.fileFormat = RecorderFileFormat.Mkv
ChatConversationViewModel.sharedModel.voiceRecorder = try core.createRecorder(params: p)
}catch{
Log.e(error.localizedDescription)
}
}
func startVoiceRecording() {
if (voiceRecorder == nil) {
createVoiceRecorder()
}
CallManager.instance().activateAudioSession()
showVoiceRecorderView = true
isVoiceRecording = true
switch linphone_recorder_get_state(voiceRecorder?.getCobject) {
case LinphoneRecorderClosed:
let filename = "\(String(describing: LinphoneManager.imagesDirectory()))/voice-recording-\(UUID().uuidString).mkv"
linphone_recorder_open(voiceRecorder?.getCobject, filename)
linphone_recorder_start(voiceRecorder?.getCobject)
Log.i("[Chat Message Sending] Recorder is closed opening it with \(filename)")
case LinphoneRecorderRunning:
Log.i("[Chat Message Sending] Recorder is already recording")
case LinphoneRecorderPaused:
Log.i("[Chat Message Sending] Recorder isn't closed, resuming recording")
linphone_recorder_start(voiceRecorder?.getCobject)
default:
break
}
}
func stopVoiceRecording() {
if (ChatConversationViewModel.sharedModel.voiceRecorder != nil) && linphone_recorder_get_state(ChatConversationViewModel.sharedModel.voiceRecorder?.getCobject) == LinphoneRecorderRunning {
Log.i("[Chat Message Sending] Pausing / closing voice recorder")
linphone_recorder_pause(ChatConversationViewModel.sharedModel.voiceRecorder?.getCobject)
linphone_recorder_close(ChatConversationViewModel.sharedModel.voiceRecorder?.getCobject)
}
isVoiceRecording = false
vrRecordTimer.invalidate()
isPendingVoiceRecord = linphone_recorder_get_duration(ChatConversationViewModel.sharedModel.voiceRecorder?.getCobject) > 0
}
func initSharedPlayer() {
AudioPlayer.initSharedPlayer()
}
func startSharedPlayer(_ path: String?) {
AudioPlayer.startSharedPlayer(path)
AudioPlayer.sharedModel.fileChanged.value = path
}
func cancelVoiceRecordingVM() {
showVoiceRecorderView = false
isPendingVoiceRecord = false
isVoiceRecording = false
if (voiceRecorder != nil) && linphone_recorder_get_state(voiceRecorder?.getCobject) != LinphoneRecorderClosed {
AudioPlayer.cancelVoiceRecordingVM(voiceRecorder)
}
}
func stopSharedPlayer() {
AudioPlayer.stopSharedPlayer()
}
func removeTmpFile(filePath: String?){
if (filePath != nil) {
if (filePath != "") {
do {
Log.i("[vfs] remove item at \(filePath)")
try FileManager.default.removeItem(atPath: filePath!)
}catch{
Log.e("[vfs] remove item error")
}
}
}
}
}

View file

@ -0,0 +1,725 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import UIKit
import Foundation
import linphonesw
import DropDown
import QuickLook
import SwipeCellKit
class ChatConversationTableViewSwift: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, QLPreviewControllerDelegate, QLPreviewControllerDataSource, SwipeCollectionViewCellDelegate {
static let compositeDescription = UICompositeViewDescription(ChatConversationTableViewSwift.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 }
lazy var collectionView: UICollectionView = {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
return collectionView
}()
var menu: DropDown? = nil
var basic :Bool = false
var floatingScrollButton : UIButton?
var scrollBadge : UILabel?
var floatingScrollBackground : UIButton?
var previewItems : [QLPreviewItem?] = []
var afterPreviewIndex = -1
override func viewDidLoad() {
super.viewDidLoad()
self.initView()
UIDeviceBridge.displayModeSwitched.readCurrentAndObserve { _ in
self.collectionView.backgroundColor = VoipTheme.backgroundWhiteBlack.get()
self.collectionView.reloadData()
}
ChatConversationTableViewModel.sharedModel.refreshIndexPath.observe { index in
self.collectionView.reloadData()
}
ChatConversationTableViewModel.sharedModel.onClickIndexPath.observe { index in
self.onGridClick(indexMessage: ChatConversationTableViewModel.sharedModel.onClickMessageIndexPath, index: index!)
}
ChatConversationTableViewModel.sharedModel.editModeOn.observe { mode in
self.collectionView.reloadData()
}
collectionView.isUserInteractionEnabled = true
collectionView.keyboardDismissMode = .interactive
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func initView(){
basic = isBasicChatRoom(ChatConversationTableViewModel.sharedModel.chatRoom?.getCobject)
view.addSubview(collectionView)
collectionView.contentInsetAdjustmentBehavior = .always
collectionView.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MultilineMessageCell.self, forCellWithReuseIdentifier: MultilineMessageCell.reuseId)
(collectionView.collectionViewLayout as! UICollectionViewFlowLayout).estimatedItemSize = UICollectionViewFlowLayout.automaticSize
(collectionView.collectionViewLayout as! UICollectionViewFlowLayout).minimumLineSpacing = 2
collectionView.transform = CGAffineTransform(scaleX: 1, y: -1)
}
override func viewDidAppear(_ animated: Bool) {
createFloatingButton()
if ChatConversationTableViewModel.sharedModel.getNBMessages() > 0 {
scrollToBottom(animated: false)
}
NotificationCenter.default.addObserver(self, selector: #selector(self.receivePresenceNotification(notification:)), name: Notification.Name("LinphoneFriendPresenceUpdate"), object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.removeObserver(self, name: Notification.Name("LinphoneFriendPresenceUpdate"), object: nil)
NotificationCenter.default.removeObserver(self)
}
@objc func receivePresenceNotification(notification: NSNotification) {
if (notification.name.rawValue == "LinphoneFriendPresenceUpdate"){
let userInfo = notification.userInfo
let friend = userInfo!["friend"]
let indexPathsVisible = self.collectionView.indexPathsForVisibleItems
if indexPathsVisible.count > 0 {
for i in 0...indexPathsVisible.count-1 {
let cell = self.collectionView.cellForItem(at: indexPathsVisible[i])
if cell != nil {
let multilineCell = cell as! MultilineMessageCell
if multilineCell.imageUser.isHidden == false {
let contact = ChatConversationTableViewModel.sharedModel.getMessage(index: indexPathsVisible[i].row)?.chatMessage?.fromAddress
if (contact != nil){
let uri = "sip:" + contact!.username + "@" + contact!.domain
if(uri == friend as! String){
let indexPath = indexPathsVisible[i]
collectionView.reloadItems(at: [indexPath])
}
}
}
}
}
}
}
}
func scrollToMessage(message: ChatMessage){
let messageIndex = ChatConversationTableViewModel.sharedModel.getIndexMessage(message: message)
self.collectionView.scrollToItem(at: IndexPath(row: messageIndex, section: 0), at: .bottom, animated: false)
}
func scrollToBottom(animated: Bool){
DispatchQueue.main.async{
self.collectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .top, animated: animated)
}
ChatConversationViewSwift.markAsRead(ChatConversationViewModel.sharedModel.chatRoom?.getCobject)
self.floatingScrollButton?.isHidden = true
self.floatingScrollBackground?.isHidden = true
scrollBadge!.text = "0"
}
func refreshDataAfterForeground(){
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
func refreshData(isOutgoing: Bool){
if (ChatConversationTableViewModel.sharedModel.getNBMessages() > 1){
let isDisplayingBottomOfTable = collectionView.contentOffset.y <= 20
if ChatConversationTableViewModel.sharedModel.getNBMessages() < 4 {
collectionView.reloadData()
ChatConversationViewSwift.markAsRead(ChatConversationViewModel.sharedModel.chatRoom?.getCobject)
} else if isDisplayingBottomOfTable {
if self.collectionView.numberOfItems(inSection: 0) > 2 {
self.collectionView.scrollToItem(at: IndexPath(item: 1, section: 0), at: .top, animated: false)
}
collectionView.reloadData()
self.scrollToBottom(animated: true)
} else if !isOutgoing {
if !collectionView.indexPathsForVisibleItems.isEmpty {
let selectedCellIndex = collectionView.indexPathsForVisibleItems.sorted().first!
let selectedCell = collectionView.cellForItem(at: selectedCellIndex)
let visibleRect = collectionView.convert(collectionView.bounds, to: selectedCell)
UIView.performWithoutAnimation {
collectionView.reloadData()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2){
let newSelectedCell = self.collectionView.cellForItem(at: IndexPath(row: selectedCellIndex.row + 1, section: 0))
let updatedVisibleRect = self.collectionView.convert(self.collectionView.bounds, to: newSelectedCell)
var contentOffset = self.collectionView.contentOffset
contentOffset.y = contentOffset.y + (visibleRect.origin.y - updatedVisibleRect.origin.y)
self.collectionView.contentOffset = contentOffset
}
}
scrollBadge!.isHidden = false
scrollBadge!.text = "\(ChatConversationViewModel.sharedModel.chatRoom?.unreadMessagesCount ?? 0)"
}
} else {
collectionView.reloadData()
self.scrollToBottom(animated: false)
}
if ChatConversationTableViewModel.sharedModel.editModeOn.value! {
ChatConversationTableViewModel.sharedModel.messageListSelected.value!.insert(false, at: 0)
}
}else{
collectionView.reloadData()
if(ChatConversationViewModel.sharedModel.chatRoom != nil){
ChatConversationViewSwift.markAsRead(ChatConversationViewModel.sharedModel.chatRoom?.getCobject)
}
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let contentOffsetY = scrollView.contentOffset.y
if contentOffsetY <= 20{
if floatingScrollButton != nil && floatingScrollBackground != nil {
floatingScrollButton?.isHidden = true
floatingScrollBackground?.isHidden = true
scrollBadge?.text = "0"
ChatConversationViewSwift.markAsRead(ChatConversationViewModel.sharedModel.chatRoom?.getCobject)
}
} else {
if floatingScrollButton != nil && floatingScrollBackground != nil {
floatingScrollButton?.isHidden = false
floatingScrollBackground?.isHidden = false;
if(scrollBadge?.text == "0"){
scrollBadge?.isHidden = true
}
}
}
}
// MARK: - UICollectionViewDataSource -
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MultilineMessageCell.reuseId, for: indexPath) as! MultilineMessageCell
cell.delegate = self
if let event = ChatConversationTableViewModel.sharedModel.getMessage(index: indexPath.row){
if(ChatConversationTableViewModel.sharedModel.editModeOn.value! && indexPath.row >= ChatConversationTableViewModel.sharedModel.messageListSelected.value!.count){
for _ in ChatConversationTableViewModel.sharedModel.messageListSelected.value!.count...indexPath.row {
ChatConversationTableViewModel.sharedModel.messageListSelected.value!.append(false)
}
}
cell.configure(event: event, selfIndexPathConfigure: indexPath, editMode: ChatConversationTableViewModel.sharedModel.editModeOn.value!, selected: ChatConversationTableViewModel.sharedModel.editModeOn.value! ? ChatConversationTableViewModel.sharedModel.messageListSelected.value![indexPath.row] : false)
if (event.chatMessage != nil && ChatConversationViewModel.sharedModel.chatRoom != nil){
cell.onLongClickOneClick {
if(cell.chatMessage != nil && ChatConversationViewModel.sharedModel.chatRoom != nil){
self.initDataSource(message: cell.chatMessage!)
self.tapChooseMenuItemMessage(contentViewBubble: cell.contentViewBubble, event: cell.eventMessage!, preContentSize: cell.preContentViewBubble.frame.size.height)
}
}
}
if (!cell.replyContent.isHidden && event.chatMessage?.replyMessage != nil){
cell.replyContent.onClick {
self.scrollToMessage(message: (cell.chatMessage?.replyMessage)!)
}
}
if (!cell.imageViewBubble.isHidden || !cell.imageVideoViewBubble.isHidden){
cell.imageViewBubble.onClick {
self.onImageClick(chatMessage: cell.chatMessage!, index: indexPath.row)
}
cell.imageVideoViewBubble.onClick {
self.onImageClick(chatMessage: cell.chatMessage!, index: indexPath.row)
}
}
}
cell.contentView.transform = CGAffineTransform(scaleX: 1, y: -1)
return cell
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
let customCell = cell as! MultilineMessageCell
if customCell.isPlayingVoiceRecording {
AudioPlayer.stopSharedPlayer()
}
if customCell.ephemeralTimer != nil {
customCell.ephemeralTimer?.invalidate()
}
if customCell.chatMessageDelegate != nil {
customCell.chatMessage?.removeDelegate(delegate: customCell.chatMessageDelegate!)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return ChatConversationTableViewModel.sharedModel.getNBMessages()
}
func collectionView(_ collectionView: UICollectionView, editActionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
let message = ChatConversationTableViewModel.sharedModel.getMessage(index: indexPath.row)
if orientation == .left {
if message?.chatMessage != nil {
let replyAction = SwipeAction(style: .default, title: "Reply") { action, indexPath in
self.replyMessage(message: (message?.chatMessage)!)
}
return [replyAction]
} else {
return nil
}
} else {
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.deleteMessage(message: message!)
}
return [deleteAction]
}
}
func collectionView(_ collectionView: UICollectionView, editActionsOptionsForItemAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
var options = SwipeOptions()
if orientation == .left {
options.expansionStyle = .selection
}
return options
}
func isBasicChatRoom(_ room: OpaquePointer?) -> Bool {
if room == nil {
return true
}
let charRoomBasic = ChatRoom.getSwiftObject(cObject: room!)
let isBasic = charRoomBasic.hasCapability(mask: Int(LinphoneChatRoomCapabilitiesBasic.rawValue))
return isBasic
}
func tapChooseMenuItemMessage(contentViewBubble: UIView, event: EventLog, preContentSize: CGFloat) {
menu!.anchorView = view
menu!.width = 200
let coordinateMin = contentViewBubble.convert(contentViewBubble.frame.origin, to: view)
let coordinateMax = contentViewBubble.convert(CGPoint(x: contentViewBubble.frame.maxX, y: contentViewBubble.frame.maxY), to: view)
if (coordinateMax.y + CGFloat(menu!.dataSource.count * 44) - preContentSize < view.frame.maxY) {
menu!.bottomOffset = CGPoint(x: event.chatMessage!.isOutgoing ? coordinateMax.x - 200 : coordinateMin.x, y: coordinateMax.y - preContentSize)
} else if ((coordinateMax.y + CGFloat(menu!.dataSource.count * 44) > view.frame.maxY) && coordinateMin.y > CGFloat(menu!.dataSource.count * 44) + (preContentSize * 2)) {
menu!.bottomOffset = CGPoint(x: event.chatMessage!.isOutgoing ? coordinateMax.x - 200 : coordinateMin.x, y: coordinateMin.y - (preContentSize * 2) - CGFloat(menu!.dataSource.count * 44))
} else {
menu!.bottomOffset = CGPoint(x: event.chatMessage!.isOutgoing ? coordinateMax.x - 200 : coordinateMin.x, y: 0)
}
let view: ChatConversationViewSwift = self.VIEW(ChatConversationViewSwift.compositeViewDescription())
view.messageView.endEditing(true)
menu!.show()
menu!.selectionAction = { [weak self] (index: Int, item: String) in
guard let _ = self else { return }
switch item {
case VoipTexts.bubble_chat_dropDown_resend:
self!.resendMessage(message: event.chatMessage!)
case VoipTexts.bubble_chat_dropDown_copy_text:
self!.copyMessage(message: event.chatMessage!)
case VoipTexts.bubble_chat_dropDown_forward:
self!.forwardMessage(message: event.chatMessage!)
case VoipTexts.bubble_chat_dropDown_reply:
self!.replyMessage(message: event.chatMessage!)
case VoipTexts.bubble_chat_dropDown_infos:
self!.infoMessage(event: event)
case VoipTexts.bubble_chat_dropDown_add_to_contact:
self!.addToContacts(message: event.chatMessage!)
case VoipTexts.bubble_chat_dropDown_delete:
self!.deleteMessage(message: event)
default:
Log.e("Error Default tapChooseMenuItemMessage ChatConversationTableViewSwift")
}
self!.menu!.clearSelection()
}
}
func initDataSource(message: ChatMessage) {
menu = {
let menu = DropDown()
menu.dataSource = [""]
let images = [
"menu_resend_default",
"menu_copy_text_default",
"menu_forward_default",
"menu_reply_default",
"menu_info",
"contact_add_default",
"menu_delete",
"menu_info"
]
menu.cellNib = UINib(nibName: "DropDownCell", bundle: nil)
menu.customCellConfiguration = { index, title, cell in
guard let cell = cell as? MyCell else {
return
}
if(index < images.count){
switch menu.dataSource[index] {
case VoipTexts.bubble_chat_dropDown_resend:
if #available(iOS 13.0, *) {
cell.myImageView.image = UIImage(named: images[0])!.withTintColor(.darkGray)
} else {
cell.myImageView.image = UIImage(named: images[0])
}
case VoipTexts.bubble_chat_dropDown_copy_text:
cell.myImageView.image = UIImage(named: images[1])
case VoipTexts.bubble_chat_dropDown_forward:
cell.myImageView.image = UIImage(named: images[2])
case VoipTexts.bubble_chat_dropDown_reply:
cell.myImageView.image = UIImage(named: images[3])
case VoipTexts.bubble_chat_dropDown_infos:
cell.myImageView.image = UIImage(named: images[4])
case VoipTexts.bubble_chat_dropDown_add_to_contact:
cell.myImageView.image = UIImage(named: images[5])
case VoipTexts.bubble_chat_dropDown_delete:
cell.myImageView.image = UIImage(named: images[6])
default:
cell.myImageView.image = UIImage(named: images[7])
}
}
}
return menu
}()
menu!.dataSource.removeAll()
let state = message.state
if (state.rawValue == LinphoneChatMessageStateNotDelivered.rawValue || state.rawValue == LinphoneChatMessageStateFileTransferError.rawValue) {
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_resend)
}
if (message.utf8Text != "" && !ICSBubbleView.isConferenceInvitationMessage(cmessage: message.getCobject!)) {
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_copy_text)
}
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_forward)
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_reply)
let chatroom = ChatConversationViewModel.sharedModel.chatRoom
if chatroom != nil {
if (chatroom!.nbParticipants > 1) {
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_infos)
}
let isOneToOneChat = ChatConversationViewModel.sharedModel.chatRoom!.hasCapability(mask: Int(LinphoneChatRoomCapabilitiesOneToOne.rawValue))
if (!message.isOutgoing && FastAddressBook.getContactWith(message.fromAddress?.getCobject) == nil
&& !isOneToOneChat && !ConfigManager.instance().lpConfigBoolForKey(key: "read_only_native_address_book")) {
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_add_to_contact)
}
menu!.dataSource.append(VoipTexts.bubble_chat_dropDown_delete)
}
}
func resendMessage(message: ChatMessage){
if ((linphone_core_is_network_reachable(LinphoneManager.getLc()) == 0)) {
PhoneMainView.instance().present(LinphoneUtils.networkErrorView("send a message"), animated: true)
return;
}else{
message.send()
}
}
func copyMessage(message: ChatMessage){
UIPasteboard.general.string = message.utf8Text
}
func forwardMessage(message: ChatMessage){
let view: ChatConversationViewSwift = self.VIEW(ChatConversationViewSwift.compositeViewDescription())
view.pendingForwardMessage = message.getCobject
let viewtoGo: ChatsListView = self.VIEW(ChatsListView.compositeViewDescription())
PhoneMainView.instance().changeCurrentView(viewtoGo.compositeViewDescription())
}
func replyMessage(message: ChatMessage){
let view: ChatConversationViewSwift = self.VIEW(ChatConversationViewSwift.compositeViewDescription())
if (view.messageView.messageText.text == "" && view.stackView.arrangedSubviews[3].isHidden && view.stackView.arrangedSubviews[4].isHidden){
view.messageView.messageText.becomeFirstResponder()
}
view.initiateReplyView(forMessage: message.getCobject)
}
func infoMessage(event: EventLog){
let view: ChatConversationImdnView = self.VIEW(ChatConversationImdnView.compositeViewDescription())
view.event = event.getCobject
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
}
func addToContacts(message: ChatMessage) {
let addr = message.fromAddress
addr?.clean()
if let lAddress = addr?.asStringUriOnly() {
var normSip = String(utf8String: lAddress)
normSip = normSip?.hasPrefix("sip:") ?? false ? (normSip as NSString?)?.substring(from: 4) : normSip
normSip = normSip?.hasPrefix("sips:") ?? false ? (normSip as NSString?)?.substring(from: 5) : normSip
ContactSelection.setAddAddress(normSip)
ContactSelection.setSelectionMode(ContactSelectionModeEdit)
ContactSelection.enableSipFilter(false)
PhoneMainView.instance().changeCurrentView(ContactsListView.compositeViewDescription())
}
}
func deleteMessage(message: EventLog){
let messageChat = message.chatMessage
if messageChat != nil {
if ChatConversationTableViewModel.sharedModel.editModeOn.value! {
let indexDeletedMessage = ChatConversationTableViewModel.sharedModel.getIndexMessage(message: messageChat!)
ChatConversationTableViewModel.sharedModel.messageListSelected.value!.remove(at: indexDeletedMessage)
ChatConversationTableViewModel.sharedModel.messageSelected.value! -= 1
}
let chatRoom = ChatConversationViewModel.sharedModel.chatRoom
if chatRoom != nil {
chatRoom!.deleteMessage(message: messageChat!)
}
} else {
message.deleteFromDatabase()
}
collectionView.reloadData()
}
public func reloadCollectionViewCell(indexPath: IndexPath){
collectionView.reloadItems(at: [indexPath])
}
func getPreviewItem(filePath: String) -> NSURL{
let url = NSURL(fileURLWithPath: filePath)
return url
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return previewItems.count
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return (previewItems[index] as QLPreviewItem?)!
}
func previewControllerDidDismiss(_ controller: QLPreviewController) {
collectionView.scrollToItem(at: IndexPath(item: afterPreviewIndex, section: 0), at: .centeredVertically, animated: false)
afterPreviewIndex = -1
}
func onImageClick(chatMessage: ChatMessage, index: Int) {
let state = chatMessage.state
if (state.rawValue == LinphoneChatMessageStateNotDelivered.rawValue) {
Log.i("Messsage not delivered")
} else {
if (VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) || ConfigManager.instance().lpConfigBoolForKey(key: "use_in_app_file_viewer_for_non_encrypted_files", section: "app")){
var viewer: MediaViewer = VIEW(MediaViewer.compositeViewDescription())
var image = UIImage()
if chatMessage.contents.first!.type == "image" {
if VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) {
var plainFile = chatMessage.contents.first!.exportPlainFile()
image = UIImage(contentsOfFile: plainFile)!
ChatConversationViewModel.sharedModel.removeTmpFile(filePath: plainFile)
plainFile = ""
}else {
image = UIImage(contentsOfFile: chatMessage.contents.first!.filePath)!
}
}
viewer.imageViewer = image
viewer.imageNameViewer = chatMessage.contents.first!.name.isEmpty ? "" : chatMessage.contents.first!.name
viewer.imagePathViewer = chatMessage.contents.first!.exportPlainFile()
viewer.contentType = chatMessage.contents.first!.type
PhoneMainView.instance().changeCurrentView(viewer.compositeViewDescription())
} else {
let previewController = QLPreviewController()
self.previewItems = []
if VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) {
var plainFile = chatMessage.contents.first?.exportPlainFile()
self.previewItems.append(self.getPreviewItem(filePath: plainFile!))
ChatConversationViewModel.sharedModel.removeTmpFile(filePath: plainFile)
plainFile = ""
}else {
self.previewItems.append(self.getPreviewItem(filePath: (chatMessage.contents.first?.filePath)!))
}
afterPreviewIndex = index
previewController.currentPreviewItemIndex = 0
previewController.dataSource = self
previewController.delegate = self
previewController.reloadData()
PhoneMainView.instance().mainViewController.present(previewController, animated: true, completion: nil)
}
}
}
func onGridClick(indexMessage: Int, index: Int) {
let chatMessage = ChatConversationTableViewModel.sharedModel.getMessage(index: indexMessage)?.chatMessage
let state = chatMessage!.state
if (state.rawValue == LinphoneChatMessageStateNotDelivered.rawValue) {
Log.i("Messsage not delivered")
} else {
if (VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) || ConfigManager.instance().lpConfigBoolForKey(key: "use_in_app_file_viewer_for_non_encrypted_files", section: "app")){
var text = ""
var filePathString = VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) ? chatMessage!.contents[index].exportPlainFile() : chatMessage!.contents[index].filePath
if let urlEncoded = filePathString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed){
if !urlEncoded.isEmpty {
if let urlFile = URL(string: "file://" + urlEncoded){
do {
text = try String(contentsOf: urlFile, encoding: .utf8)
let viewer: TextViewer = VIEW(TextViewer.compositeViewDescription())
if chatMessage != nil {
viewer.textViewer = text
viewer.textNameViewer = chatMessage!.contents[index].name.isEmpty ? "" : chatMessage!.contents[index].name
PhoneMainView.instance().changeCurrentView(viewer.compositeViewDescription())
}
} catch {
if text == "" && (chatMessage!.contents[index].type == "image" || chatMessage!.contents[index].type == "video" || chatMessage!.contents[index].name.lowercased().components(separatedBy: ".").last == "pdf"){
let viewer: MediaViewer = VIEW(MediaViewer.compositeViewDescription())
var image = UIImage()
if chatMessage != nil {
if chatMessage!.contents[index].type == "image" {
if VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) {
var plainFile = chatMessage!.contents[index].exportPlainFile()
image = UIImage(contentsOfFile: plainFile)!
ChatConversationViewModel.sharedModel.removeTmpFile(filePath: plainFile)
plainFile = ""
}else {
image = UIImage(contentsOfFile: chatMessage!.contents[index].filePath)!
}
}
viewer.imageViewer = image
viewer.imageNameViewer = chatMessage!.contents[index].name.isEmpty ? "" : chatMessage!.contents[index].name
viewer.imagePathViewer = chatMessage!.contents[index].exportPlainFile()
viewer.contentType = chatMessage!.contents[index].type
PhoneMainView.instance().changeCurrentView(viewer.compositeViewDescription())
}
} else {
let exportView = UIAlertController(
title: VoipTexts.chat_message_cant_open_file_in_app_dialog_title,
message: VoipTexts.chat_message_cant_open_file_in_app_dialog_message,
preferredStyle: .alert)
let cancelAction = UIAlertAction(
title: VoipTexts.cancel,
style: .default,
handler: { action in
})
let exportAction = UIAlertAction(
title: VoipTexts.chat_message_cant_open_file_in_app_dialog_export_button,
style: .destructive,
handler: { action in
let previewController = QLPreviewController()
self.previewItems = []
self.previewItems.append(self.getPreviewItem(filePath: filePathString))
self.afterPreviewIndex = indexMessage
previewController.dataSource = self
previewController.currentPreviewItemIndex = index
previewController.delegate = self
PhoneMainView.instance().mainViewController.present(previewController, animated: true, completion: nil)
})
exportView.addAction(cancelAction)
exportView.addAction(exportAction)
PhoneMainView.instance()!.present(exportView, animated: true)
}
}
}
}
}
/*
if VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) {
ChatConversationViewModel.sharedModel.removeTmpFile(filePath: filePathString)
filePathString = ""
}
*/
} else {
let previewController = QLPreviewController()
self.previewItems = []
chatMessage?.contents.forEach({ content in
if(content.isFile){
if VFSUtil.vfsEnabled(groupName: kLinphoneMsgNotificationAppGroupId) {
var plainFile = content.exportPlainFile()
self.previewItems.append(self.getPreviewItem(filePath: plainFile))
ChatConversationViewModel.sharedModel.removeTmpFile(filePath: plainFile)
plainFile = ""
}else {
self.previewItems.append(self.getPreviewItem(filePath: (content.filePath)))
}
}
})
afterPreviewIndex = indexMessage
previewController.dataSource = self
previewController.currentPreviewItemIndex = index
previewController.delegate = self
PhoneMainView.instance().mainViewController.present(previewController, animated: true, completion: nil)
}
}
}
}

File diff suppressed because it is too large Load diff

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