diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2d857c68b..e7cec4238 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,26 +1,55 @@
variables:
- archive_scheme: linphone
- archive_path: linphone.xcarchive
- export_path: linphone-adhoc-ipa
- export_options_plist: linphone-adhoc.plist
+ workspace: linphone.xcworkspace
+ scheme: linphone
+ destination: name=iPhone 13 Pro
+ testResult_path: derivedData/Logs/Test
+
+stages:
+ - Build
+ - UITests
-job-ios:
+before_script:
+ - pod install
+ - 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
- stage: build
- tags: [ "macmini-m1-xcode13" ]
-
- script:
+Compile & Build:
+ stage: Build
+ tags: ["macmini-m1-xcode13"]
+ 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
- - 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'
-
+ - xcrun simctl shutdown "$destination" && xcrun simctl erase "$destination"
+ script:
+ - xcodebuild -workspace $workspace -scheme $scheme -UseModernBuildSystem=YES -destination "$destination" -derivedDataPath derivedData
+ after_script: []
artifacts:
paths:
- - $archive_path
- - $export_path
+ - derivedData/Build
when: always
- expire_in: 1 week
+ 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
diff --git a/CallUITests-Info.plist b/CallUITests-Info.plist
new file mode 100644
index 000000000..0c67376eb
--- /dev/null
+++ b/CallUITests-Info.plist
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Classes/Base.lproj/AboutView.xib b/Classes/Base.lproj/AboutView.xib
index 3aa5423fe..286b802c2 100644
--- a/Classes/Base.lproj/AboutView.xib
+++ b/Classes/Base.lproj/AboutView.xib
@@ -1,9 +1,9 @@
-
+
-
+
diff --git a/Classes/Base.lproj/AssistantLinkView.xib b/Classes/Base.lproj/AssistantLinkView.xib
index 9990a74f9..ca7f16313 100644
--- a/Classes/Base.lproj/AssistantLinkView.xib
+++ b/Classes/Base.lproj/AssistantLinkView.xib
@@ -154,7 +154,7 @@
+
@@ -244,7 +245,8 @@
-
+
+
diff --git a/Classes/Base.lproj/AssistantViewScreens.xib b/Classes/Base.lproj/AssistantViewScreens.xib
index 8d7a06be5..3a2eca6f0 100644
--- a/Classes/Base.lproj/AssistantViewScreens.xib
+++ b/Classes/Base.lproj/AssistantViewScreens.xib
@@ -72,6 +72,7 @@
+
@@ -880,7 +882,7 @@ Once it is done, come back here and click on the button.
-
+
@@ -913,7 +915,7 @@ Once it is done, come back here and click on the button.
-
+
@@ -966,6 +968,7 @@ Once it is done, come back here and click on the button.
+
@@ -975,7 +978,7 @@ Once it is done, come back here and click on the button.
diff --git a/Classes/Base.lproj/PhoneMainView.xib b/Classes/Base.lproj/PhoneMainView.xib
index 5031dcf17..91d01a21f 100644
--- a/Classes/Base.lproj/PhoneMainView.xib
+++ b/Classes/Base.lproj/PhoneMainView.xib
@@ -1,9 +1,10 @@
-
+
-
+
+
@@ -26,7 +27,7 @@
-
+
@@ -43,6 +44,12 @@
+
+
+
+
+
+
diff --git a/Classes/Base.lproj/SettingsView.xib b/Classes/Base.lproj/SettingsView.xib
index 8a41e58f2..2437492a8 100644
--- a/Classes/Base.lproj/SettingsView.xib
+++ b/Classes/Base.lproj/SettingsView.xib
@@ -38,7 +38,7 @@
-
+
@@ -58,7 +58,8 @@
-
+
+
diff --git a/Classes/Base.lproj/SideMenuView.xib b/Classes/Base.lproj/SideMenuView.xib
index 4f3fa7364..c2b04fff6 100644
--- a/Classes/Base.lproj/SideMenuView.xib
+++ b/Classes/Base.lproj/SideMenuView.xib
@@ -1,9 +1,10 @@
-
+
-
+
+
@@ -49,6 +50,7 @@
diff --git a/Classes/HistoryListTableView.m b/Classes/HistoryListTableView.m
index b572b55a6..447c4d9de 100644
--- a/Classes/HistoryListTableView.m
+++ b/Classes/HistoryListTableView.m
@@ -277,6 +277,7 @@
[PhoneMainView.instance changeCurrentView:ConferenceWaitingRoomFragment.compositeViewDescription];
} else {
const LinphoneAddress *addr = linphone_call_log_get_remote_address(callLog);
+ [tableView deselectRowAtIndexPath:indexPath animated:NO];
[LinphoneManager.instance call:addr];
}
}
diff --git a/Classes/LinphoneAppDelegate.m b/Classes/LinphoneAppDelegate.m
index 4d904dfa5..e043b0f1b 100644
--- a/Classes/LinphoneAppDelegate.m
+++ b/Classes/LinphoneAppDelegate.m
@@ -52,7 +52,7 @@
if (self != nil) {
startedInBackground = FALSE;
}
- _onlyPortrait = FALSE;
+ _onlyPortrait = FALSE;
return self;
[[UIApplication sharedApplication] setDelegate:self];
}
diff --git a/Classes/LinphoneCoreSettingsStore.m b/Classes/LinphoneCoreSettingsStore.m
index 72dae4570..f9cb15c71 100644
--- a/Classes/LinphoneCoreSettingsStore.m
+++ b/Classes/LinphoneCoreSettingsStore.m
@@ -407,7 +407,6 @@
{
[self setBool:[lm lpConfigBoolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"];
- [self setBool:linphone_core_is_record_aware_enabled(LC) forKey:@"record_aware"];
[self setBool:linphone_core_get_use_info_for_dtmf(LC) forKey:@"sipinfo_dtmf_preference"];
[self setBool:linphone_core_get_use_rfc2833_for_dtmf(LC) forKey:@"rfc_dtmf_preference"];
@@ -490,6 +489,7 @@
[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 setBool:linphone_core_adaptive_rate_control_enabled(LC) forKey:@"adaptive_rate_control_preference"];
+ [self setObject:[lm lpConfigStringForKey:@"dns_server_ip"] forKey:@"dns_server_preference"];
}
// tunnel section
@@ -934,8 +934,6 @@
[lm lpConfigSetBool:[self boolForKey:@"use_device_ringtone"] forKey:@"use_device_ringtone"];
[ProviderDelegate resetSharedProviderConfiguration];
- linphone_core_set_record_aware_enabled(LC, [self boolForKey:@"record_aware"]);
-
linphone_core_set_use_info_for_dtmf(LC, [self boolForKey:@"sipinfo_dtmf_preference"]);
linphone_core_set_inc_timeout(LC, [self integerForKey:@"incoming_call_timeout_preference"]);
linphone_core_set_in_call_timeout(LC, [self integerForKey:@"in_call_timeout_preference"]);
@@ -1033,6 +1031,12 @@
}
linphone_core_enable_adaptive_rate_control(LC, [self boolForKey:@"adaptive_rate_control_preference"]);
+
+ if ([self stringForKey:@"dns_server_preference"] != [lm lpConfigStringForKey:@"dns_server_ip"]) {
+ [lm lpConfigSetString:[self stringForKey:@"dns_server_preference"] forKey:@"dns_server_ip"];
+ [lm setDnsServer];
+ }
+
// tunnel section
if (linphone_core_tunnel_available()) {
diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h
index 2ee225e9b..c186faa55 100644
--- a/Classes/LinphoneManager.h
+++ b/Classes/LinphoneManager.h
@@ -190,7 +190,8 @@ typedef struct _LinphoneManagerSounds {
- (void)setupGSMInteraction;
- (BOOL)isCTCallCenterExist;
-- (void) checkLocalNetworkPermission;
+- (void)checkLocalNetworkPermission;
+- (void)setDnsServer;
@property (readonly) BOOL isTesting;
@@ -214,5 +215,6 @@ typedef struct _LinphoneManagerSounds {
@property(strong, nonatomic) OrderedDictionary *linphoneManagerAddressBookMap;
@property (nonatomic, assign) BOOL contactsUpdated;
@property UIImage *avatar;
+@property NSString *customCoreDNS;
@end
diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m
index 06a9b8fef..85f4fb606 100644
--- a/Classes/LinphoneManager.m
+++ b/Classes/LinphoneManager.m
@@ -1118,6 +1118,14 @@ static void linphone_iphone_is_composing_received(LinphoneCore *lc, LinphoneChat
}
}
+- (void)setDnsServer {
+ NSString *dns_server_ip = [self lpConfigStringForKey:@"dns_server_ip"];
+ if ([dns_server_ip isEqualToString:@""]) {dns_server_ip = NULL;}
+ bctbx_list_t *dns_server_list = dns_server_ip?bctbx_list_new((void *)[dns_server_ip UTF8String]):NULL;
+ linphone_core_set_dns_servers_app(LC, dns_server_list);
+ bctbx_list_free(dns_server_list);
+}
+
#pragma mark -
// scheduling loop
@@ -1247,6 +1255,8 @@ static BOOL libStarted = FALSE;
// go directly to bg mode
[self enterBackgroundMode];
}
+
+
}
void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreatorStatus status, const char *resp) {
@@ -1436,6 +1446,8 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
linphone_core_reload_ms_plugins(theLinphoneCore, NULL);
[self migrationAllPost];
+
+ linphone_core_enable_record_aware(theLinphoneCore, true); //force record aware enable
/* Use the rootca from framework, which is already set*/
//linphone_core_set_root_ca(theLinphoneCore, [LinphoneManager bundleFile:@"rootca.pem"].UTF8String);
@@ -1453,6 +1465,7 @@ void popup_link_account_cb(LinphoneAccountCreator *creator, LinphoneAccountCreat
/*call iterate once immediately in order to initiate background connections with sip server or remote provisioning
* grab, if any */
+ [self setDnsServer]; //configure DNS if custom DNS server is set
[self iterate];
}
@@ -1686,6 +1699,9 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
completionHandler:^(BOOL granted){
}];
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio
+ completionHandler:^(BOOL granted){
+ }];
/*start the video preview in case we are in the main view*/
if (linphone_core_video_display_enabled(theLinphoneCore) && [self lpConfigBoolForKey:@"preview_preference"]) {
diff --git a/Classes/LinphoneUI/Base.lproj/StatusBarView.xib b/Classes/LinphoneUI/Base.lproj/StatusBarView.xib
index b4738d425..ead83cd58 100644
--- a/Classes/LinphoneUI/Base.lproj/StatusBarView.xib
+++ b/Classes/LinphoneUI/Base.lproj/StatusBarView.xib
@@ -34,8 +34,8 @@
-
-
+
+
@@ -52,6 +52,7 @@
+
@@ -61,6 +62,7 @@
+
@@ -69,7 +71,9 @@
-
+
+
+
@@ -93,7 +97,7 @@
-
+
diff --git a/Classes/LinphoneUI/Base.lproj/UICompositeView.xib b/Classes/LinphoneUI/Base.lproj/UICompositeView.xib
index 97e5a08a0..1989d890d 100644
--- a/Classes/LinphoneUI/Base.lproj/UICompositeView.xib
+++ b/Classes/LinphoneUI/Base.lproj/UICompositeView.xib
@@ -1,10 +1,11 @@
-
+
-
+
+
@@ -28,13 +29,15 @@
-
+
+
-
+
+
@@ -43,21 +46,24 @@
-
+
+
+
-
+
+
+
-
@@ -70,7 +76,7 @@
-
+
@@ -89,9 +95,9 @@
+
-
@@ -101,4 +107,9 @@
+
+
+
+
+
diff --git a/Classes/LinphoneUI/StatusBarView.m b/Classes/LinphoneUI/StatusBarView.m
index 381f54ab6..7bc204673 100644
--- a/Classes/LinphoneUI/StatusBarView.m
+++ b/Classes/LinphoneUI/StatusBarView.m
@@ -405,11 +405,13 @@
[ControlsViewModelBridge toggleStatsVisibility];
}
+
- (IBAction)onSideMenuClick:(id)sender {
UICompositeView *cvc = PhoneMainView.instance.mainViewController;
[cvc hideSideMenu:(cvc.sideMenuView.frame.origin.x == 0)];
}
+
- (IBAction)onRegistrationStateClick:(id)sender {
if (linphone_core_get_default_account(LC)) {
linphone_core_refresh_registers(LC);
diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m
index 1d3393bd6..6334dfa09 100644
--- a/Classes/PhoneMainView.m
+++ b/Classes/PhoneMainView.m
@@ -172,6 +172,7 @@ static RootViewManager *rootViewManagerInstance = nil;
volumeView.userInteractionEnabled = false;
[self.view addSubview:mainViewController.view];
+ self.view.accessibilityIdentifier = @"phone_view";
}
- (void)viewWillAppear:(BOOL)animated {
@@ -720,6 +721,9 @@ static RootViewManager *rootViewManagerInstance = nil;
[errView addAction:defaultAction];
[self presentViewController:errView animated:YES completion:nil];
+ errView.view.accessibilityIdentifier = @"call_failed_error_view";
+ errView.view.subviews.firstObject.accessibilityIdentifier = @"call_failed_error_view";
+ errView.actions.firstObject.accessibilityIdentifier = @"call_failed_error_view_action";
}
- (void)addInhibitedEvent:(id)event {
diff --git a/Classes/Swift/CallManager.swift b/Classes/Swift/CallManager.swift
index dc40ce286..619b677c7 100644
--- a/Classes/Swift/CallManager.swift
+++ b/Classes/Swift/CallManager.swift
@@ -625,16 +625,7 @@ import AVFoundation
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 {
- CallManager.instance().referedToCall = callId
- }
+ CallManager.instance().referedToCall = callId
}
}
break
diff --git a/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift b/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift
index 819e8dec3..02f7d8362 100644
--- a/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift
+++ b/Classes/Swift/Conference/Views/ConferenceSchedulingView.swift
@@ -47,7 +47,8 @@ import IQKeyboardManager
},
nextActionEnableCondition: ConferenceSchedulingViewModel.shared.continueEnabled,
title:VoipTexts.conference_group_call_title)
-
+ view.accessibilityIdentifier = "start_group_call_view"
+
let subjectLabel = StyledLabel(VoipTheme.conference_scheduling_font, VoipTexts.conference_schedule_subject_title)
subjectLabel.addIndicatorIcon(iconName: "voip_mandatory")
contentView.addSubview(subjectLabel)
diff --git a/Classes/Swift/Extensions/IOS/UIColorExtensions.swift b/Classes/Swift/Extensions/IOS/UIColorExtensions.swift
index 2d4b8efc9..2d50661a2 100644
--- a/Classes/Swift/Extensions/IOS/UIColorExtensions.swift
+++ b/Classes/Swift/Extensions/IOS/UIColorExtensions.swift
@@ -18,6 +18,7 @@
*/
import Foundation
+import UIKit
extension UIColor {
public convenience init(hex: String) {
diff --git a/Classes/Swift/Voip/Theme/VoipTexts.swift b/Classes/Swift/Voip/Theme/VoipTexts.swift
index 5bbc9cf72..e4e252760 100644
--- a/Classes/Swift/Voip/Theme/VoipTexts.swift
+++ b/Classes/Swift/Voip/Theme/VoipTexts.swift
@@ -152,6 +152,8 @@ import UIKit
// Added in iOS
static let camera_required_for_video = NSLocalizedString("Camera use is not Authorized for &appName;. This permission is required to activate Video.",comment:"").replacingOccurrences(of: "&appName;", with: appName)
+ static let microphone_non_authorized_warning = NSLocalizedString("Warning : Microphone access is not Authorized for &appName;. To enable access, tap Settings and turn on Microphone, this will end your call",comment:"").replacingOccurrences(of: "&appName;", with: appName)
+ static let system_app_settings = NSLocalizedString("SETTINGS",comment:"")
static let conference_edit_error = NSLocalizedString("Unable to edit conference this time, date is invalid",comment:"")
static let ok = NSLocalizedString("ok",comment:"")
static let conference_info_confirm_removal_delete = NSLocalizedString("DELETE",comment:"")
diff --git a/Classes/Swift/Voip/ViewModels/ControlsViewModel.swift b/Classes/Swift/Voip/ViewModels/ControlsViewModel.swift
index bf9d01cc7..6ff261234 100644
--- a/Classes/Swift/Voip/ViewModels/ControlsViewModel.swift
+++ b/Classes/Swift/Voip/ViewModels/ControlsViewModel.swift
@@ -229,17 +229,24 @@ class ControlsViewModel {
func toggleMuteMicrophone() {
if (!micAuthorized()) {
- AVAudioSession.sharedInstance().requestRecordPermission { granted in
- if granted {
- self.core.micEnabled = !self.core.micEnabled
- self.updateMicState()
- }
- }
+ askMicrophoneAccess()
}
core.micEnabled = !core.micEnabled
updateMicState()
}
+ var microphoneAsking = false
+ func askMicrophoneAccess() {
+ microphoneAsking = true
+ let settings = ButtonAttributes(text:VoipTexts.system_app_settings, action: {
+ self.microphoneAsking = false
+ try! self.core.terminateAllCalls()
+ UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
+ }, isDestructive:false)
+ let cancel = ButtonAttributes(text:VoipTexts.cancel, action: {self.microphoneAsking = false}, isDestructive:true)
+ VoipDialog(message:VoipTexts.microphone_non_authorized_warning, givenButtons: [cancel,settings]).show()
+ }
+
func forceEarpieceAudioRoute() {
if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) {
Log.i("[Call Controls] Headset found, route audio to it instead of earpiece")
@@ -271,15 +278,12 @@ class ControlsViewModel {
}
+
@objc class ControlsViewModelBridge: NSObject {
@objc static func showParticipants() {
ControlsViewModel.shared.goToConferenceParticipantsListEvent.value = true
}
- @objc static func toggleStatsVisibility() -> Void {
- if (ControlsViewModel.shared.callStatsVisible.value == true) {
- ControlsViewModel.shared.callStatsVisible.value = false
- } else {
- ControlsViewModel.shared.callStatsVisible.value = true
- }
+ @objc static func toggleStatsVisibility() {
+ ControlsViewModel.shared.callStatsVisible.value = !(ControlsViewModel.shared.callStatsVisible.value ?? false)
}
}
diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift
index af14974cb..f3eff9492 100644
--- a/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift
+++ b/Classes/Swift/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift
@@ -60,6 +60,7 @@ import linphonesw
super.viewDidLoad()
view.backgroundColor = VoipTheme.voipBackgroundColor.get()
+ view.accessibilityIdentifier = "active_call_view"
// Hangup
let hangup = CallControlButton(width: 65, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
@@ -67,6 +68,8 @@ import linphonesw
})
view.addSubview(hangup)
hangup.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ hangup.accessibilityIdentifier = "active_call_view_hangup"
+ hangup.accessibilityLabel = "Hangup"
// Controls
@@ -119,9 +122,9 @@ import linphonesw
callPausedByRemoteView?.isHidden = true
// Paused by local (Call)
- callPausedByLocalView = PausedCallOrConferenceView(iconName: "voip_conference_play_big",titleText: VoipTexts.call_locally_paused_title,subTitleText: VoipTexts.call_locally_paused_subtitle, onClickAction: {
- CallsViewModel.shared.currentCallData.value??.togglePause()
- })
+ callPausedByLocalView = PausedCallOrConferenceView(iconName: "voip_conference_play_big",titleText: VoipTexts.call_locally_paused_title,subTitleText: VoipTexts.call_locally_paused_subtitle, onClickAction: {
+ CallsViewModel.shared.currentCallData.value??.togglePause()
+ })
view.addSubview(callPausedByLocalView!)
callPausedByLocalView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
callPausedByLocalView?.isHidden = true
@@ -222,6 +225,7 @@ import linphonesw
shadingMask.isHidden = true
self.view.addSubview(shadingMask)
shadingMask.matchParentDimmensions().done()
+ shadingMask.accessibilityIdentifier = "active_call_view_shading_mask"
// Extra Buttons
let showextraButtons = CallControlButton(imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_more, onClickAction: {
@@ -230,7 +234,9 @@ import linphonesw
})
view.addSubview(showextraButtons)
showextraButtons.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
-
+ showextraButtons.accessibilityIdentifier = "active_call_view_extra_buttons"
+ showextraButtons.accessibilityLabel = "More actions"
+
let boucingCounter = BouncingCounter(inButton:showextraButtons)
view.addSubview(boucingCounter)
boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount
@@ -255,11 +261,10 @@ import linphonesw
self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:self.currentCallView?.centerSection.frame.origin.y ?? 0.0, above:self.controlsView, onDismissAction: {
ControlsViewModel.shared.numpadVisible.value = false
})
- } else {
- self.numpadView?.removeFromSuperview()
- self.shadingMask.isHidden = true
- }
-
+ } else {
+ self.numpadView?.removeFromSuperview()
+ self.shadingMask.isHidden = true
+ }
}
// Call stats
@@ -270,18 +275,21 @@ import linphonesw
self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!,marginTop:self.currentCallView?.centerSection.frame.origin.y ?? 0.0, above:self.controlsView, onDismissAction: {
ControlsViewModel.shared.callStatsVisible.value = false
})
- } else {
- self.currentCallStatsVew?.removeFromSuperview()
- self.shadingMask.isHidden = true
- }
+ } else {
+ self.currentCallStatsVew?.removeFromSuperview()
+ self.shadingMask.isHidden = true
+ }
}
- // Video activation dialog request
+ // Video and Microphone activation dialog request
CallsViewModel.shared.callUpdateEvent.observe { (call) in
let core = Core.get()
if (call?.state == .StreamsRunning) {
self.videoAcceptDialog?.removeFromSuperview()
self.videoAcceptDialog = nil
+ if (!ControlsViewModel.shared.micAuthorized() && !ControlsViewModel.shared.microphoneAsking) {
+ ControlsViewModel.shared.askMicrophoneAccess()
+ }
} else if (call?.state == .UpdatedByRemote) {
if (core.videoCaptureEnabled || core.videoDisplayEnabled) {
if (call?.currentParams?.videoEnabled != call?.remoteParams?.videoEnabled) {
@@ -351,8 +359,8 @@ import linphonesw
participantsListView?.removeFromSuperview()
participantsListView = nil
- ControlsViewModel.shared.numpadVisible.value = false
- ControlsViewModel.shared.callStatsVisible.value = false
+ ControlsViewModel.shared.numpadVisible.value = false
+ ControlsViewModel.shared.callStatsVisible.value = false
ControlsViewModel.shared.fullScreenMode.value = false
super.viewWillDisappear(animated)
}
diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift
index e1a7693bc..6be6a4eeb 100644
--- a/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift
+++ b/Classes/Swift/Voip/Views/CompositeViewControllers/IncomingCallView.swift
@@ -43,13 +43,17 @@ import linphonesw
})
view.addSubview(accept)
accept.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
-
+ accept.accessibilityIdentifier = "I_call_view_accept"
+ accept.accessibilityLabel = "Accept"
+
// Decline
let decline = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)}
})
view.addSubview(decline)
decline.centerX(withDx: -buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ decline.accessibilityIdentifier = "I_call_view_decline"
+ decline.accessibilityLabel = "Decline"
}
@objc override func setCall(call:OpaquePointer) {
diff --git a/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift
index 9692d692a..8ce0b811f 100644
--- a/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift
+++ b/Classes/Swift/Voip/Views/CompositeViewControllers/OutgoingCallView.swift
@@ -45,6 +45,8 @@ import linphonesw
})
view.addSubview(cancelCall)
cancelCall.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ cancelCall.accessibilityIdentifier = "O_call_view_cancel"
+ cancelCall.accessibilityLabel = "Cancel"
// Controls
let controlsView = ControlsView(showVideo: false, controlsViewModel: ControlsViewModel.shared)
diff --git a/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift
index 1d90beee2..86cc63438 100644
--- a/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift
@@ -81,7 +81,7 @@ class ActiveCallView: UIView { // = currentCall
}
}
callData?.isRemotelyRecorded.readCurrentAndObserve { (remotelyRecorded) in
- self.centerSection.removeConstraints().matchParentSideBorders().alignUnder(view:remotelyRecorded == true ? self.remotelyRecordedIndicator : self.upperSection ,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
+ self.centerSection.removeConstraints().matchParentSideBorders().alignUnder(view:remotelyRecorded == true ? self.remotelyRecordedIndicator : self.upperSection ,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
self.setNeedsLayout()
}
@@ -117,6 +117,7 @@ class ActiveCallView: UIView { // = currentCall
displayNameDurationSipAddress.addSubview(duration)
duration.toRightOf(displayNameTop).alignParentRight().done()
+ duration.accessibilityIdentifier = "active_call_upper_section_duration"
displayNameDurationSipAddress.addSubview(sipAddress)
sipAddress.matchParentSideBorders().alignUnder(view: displayNameTop,withMargin:sip_address_margin_top).done()
@@ -138,6 +139,8 @@ class ActiveCallView: UIView { // = currentCall
})
recordCallButtons.append(recordCall)
recordPauseView.addArrangedSubview(recordCall)
+ recordCall.accessibilityIdentifier = "active_call_upper_section_record"
+ recordCall.accessibilityLabel = "Record Call"
// Pause (with video)
var pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
@@ -146,6 +149,8 @@ class ActiveCallView: UIView { // = currentCall
pauseCallButtons.append(pauseCall)
recordPauseView.addArrangedSubview(pauseCall)
upperSection.addArrangedSubview(recordPauseView)
+ pauseCall.accessibilityIdentifier = "active_call_upper_section_pause"
+ pauseCall.accessibilityLabel = "Pause Call"
stack.addArrangedSubview(upperSection)
@@ -153,12 +158,13 @@ class ActiveCallView: UIView { // = currentCall
stack.addArrangedSubview(remotelyRecordedIndicator)
- remotelyRecordedIndicator.matchParentSideBorders().height(CGFloat(ActiveCallView.remote_recording_height)).done()
-
+ remotelyRecordedIndicator.matchParentSideBorders().height(CGFloat(ActiveCallView.remote_recording_height)).done()
+
// Center Section : Avatar + video + record/pause buttons + videos
centerSection.layer.cornerRadius = ActiveCallView.center_view_corner_radius
centerSection.clipsToBounds = true
centerSection.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
+ //centerSection.removeConstraints().matchParentSideBorders().alignUnder(view: remotelyRecordedIndicator, withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
// Record (w/o video)
recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
@@ -167,6 +173,8 @@ class ActiveCallView: UIView { // = currentCall
recordCallButtons.append(recordCall)
centerSection.addSubview(recordCall)
recordCall.alignParentLeft(withMargin:record_pause_button_margin).alignParentTop(withMargin:record_pause_button_margin).done()
+ recordCall.accessibilityIdentifier = "active_call_center_section_record"
+ recordCall.accessibilityLabel = "Record Call"
// Pause (w/o video)
pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
@@ -175,9 +183,12 @@ class ActiveCallView: UIView { // = currentCall
pauseCallButtons.append(pauseCall)
centerSection.addSubview(pauseCall)
pauseCall.alignParentRight(withMargin:record_pause_button_margin).alignParentTop(withMargin:record_pause_button_margin).done()
+ pauseCall.accessibilityIdentifier = "active_call_center_section_pause"
+ pauseCall.accessibilityLabel = "Pause Call"
// Avatar
centerSection.addSubview(avatar)
+ avatar.square(Avatar.diameter_for_call_views).center().done()
// Remote Video Display
centerSection.addSubview(remoteVideo)
diff --git a/Classes/Swift/Voip/Views/Fragments/CallStatsView.swift b/Classes/Swift/Voip/Views/Fragments/CallStatsView.swift
index 0c10ae3e1..4413d1007 100644
--- a/Classes/Swift/Voip/Views/Fragments/CallStatsView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/CallStatsView.swift
@@ -20,7 +20,8 @@
import Foundation
import linphonesw
-@objc class CallStatsView: UIView {
+
+@objc class CallStatsView: UIView{
// Layout constants
let side_margins = 10.0
@@ -34,38 +35,44 @@ import linphonesw
layer.cornerRadius = corner_radius
clipsToBounds = true
superView.addSubview(self)
- matchParentSideBorders(insetedByDx: side_margins).alignParentTop(withMargin: marginTop).alignAbove(view: above,withMargin: SharedLayoutConstants.buttons_bottom_margin).done()
+ matchParentSideBorders(insetedByDx: side_margins).alignParentTop(withMargin: marginTop).alignParentBottom().done()
+ accessibilityIdentifier = "call_stats_view"
+ accessibilityViewIsModal = true
callData.callState.observe { state in
if (state == Call.State.End) {
onDismissAction()
}
}
-
+
+ // Hide call stats button
let hide = CallControlButton(buttonTheme: VoipTheme.voip_cancel_light, onClickAction: {
onDismissAction()
})
addSubview(hide)
hide.alignParentRight(withMargin: side_margins).alignParentTop(withMargin: side_margins).done()
+ hide.accessibilityIdentifier = "call_stats_view_hide"
+ hide.accessibilityLabel = "Hide"
-
+ // Audio Stats Title
let model = CallStatisticsData(call: callData.call)
let audioTitle = StyledLabel(VoipTheme.call_stats_font_title,NSLocalizedString("Audio", comment: ""))
addSubview(audioTitle)
audioTitle.matchParentSideBorders().alignParentTop(withMargin: margin_top).done()
-
+
+ // Audio Stats Corp
let audioStats = StyledLabel(VoipTheme.call_stats_font)
-
audioStats.numberOfLines = 0
addSubview(audioStats)
audioStats.matchParentSideBorders().alignUnder(view: audioTitle).done()
+ // Video Stats Title
let videoTitle = StyledLabel(VoipTheme.call_stats_font_title,NSLocalizedString("Video", comment: ""))
addSubview(videoTitle)
videoTitle.alignUnder(view: audioStats, withMargin:audio_video_margin).matchParentSideBorders().done()
+ // Video Stats Corp
let videoStats = StyledLabel(VoipTheme.call_stats_font)
-
videoStats.numberOfLines = 0
addSubview(videoStats)
videoStats.matchParentSideBorders().alignUnder(view: videoTitle).done()
diff --git a/Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift b/Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift
index 57324d918..dceaed100 100644
--- a/Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/CallsList/CallsListView.swift
@@ -37,6 +37,7 @@ import linphonesw
init() {
super.init(title: VoipTexts.call_action_calls_list)
+ accessibilityIdentifier = "calls_list_view"
// New Call
let newCall = CallControlButton(width: buttons_size,height: buttons_size, imageInset:UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), buttonTheme: VoipTheme.call_add, onClickAction: {
diff --git a/Classes/Swift/Voip/Views/Fragments/ControlsView.swift b/Classes/Swift/Voip/Views/Fragments/ControlsView.swift
index b091a049c..7d058a93a 100644
--- a/Classes/Swift/Voip/Views/Fragments/ControlsView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/ControlsView.swift
@@ -43,6 +43,8 @@ class ControlsView: UIStackView {
controlsViewModel.isMuteMicrophoneEnabled.readCurrentAndObserve { (enabled) in
mute.isEnabled = enabled == true
}
+ mute.accessibilityIdentifier = "call_control_view_mute"
+ mute.accessibilityLabel = "Mute"
// Speaker
let speaker = CallControlButton(buttonTheme: VoipTheme.call_speaker, onClickAction: {
@@ -52,6 +54,8 @@ class ControlsView: UIStackView {
controlsViewModel.isSpeakerSelected.readCurrentAndObserve { (selected) in
speaker.isSelected = selected == true
}
+ speaker.accessibilityIdentifier = "call_control_view_speaker"
+ speaker.accessibilityLabel = "Speaker"
// Audio routes
let routes = CallControlButton(buttonTheme: VoipTheme.call_audio_route, onClickAction: {
@@ -93,6 +97,8 @@ class ControlsView: UIStackView {
controlsViewModel.isVideoUpdateInProgress.readCurrentAndObserve { (updateInProgress) in
video.isEnabled = updateInProgress != true && controlsViewModel.isVideoAvailable.value == true
}
+ video.accessibilityIdentifier = "call_control_view_video"
+ video.accessibilityLabel = "Video"
}
diff --git a/Classes/Swift/Voip/Views/Fragments/DismissableView.swift b/Classes/Swift/Voip/Views/Fragments/DismissableView.swift
index 9bb1f7d9b..56214da40 100644
--- a/Classes/Swift/Voip/Views/Fragments/DismissableView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/DismissableView.swift
@@ -44,6 +44,7 @@ class DismissableView: UIView {
})
headerView.addSubview(dismiss!)
dismiss?.alignParentRight(withMargin: dismiss_right_margin).centerY().done()
+ dismiss?.accessibilityIdentifier = "dismissable_view_close"
let title = StyledLabel(VoipTheme.calls_list_header_font,title)
headerView.addSubview(title)
diff --git a/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift b/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift
index 4ceb0020f..bfedfa57d 100644
--- a/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift
@@ -43,7 +43,8 @@ import linphonesw
var callData: CallData? = nil {
didSet {
- duration.call = callData?.call.dir == .Incoming ? callData?.call : nil
+ //duration.call = callData?.call.dir == .Incoming ? callData?.call : nil
+ duration.call = callData?.call
callData?.call.remoteAddress.map {
avatar.fillFromAddress(address: $0)
displayName.text = $0.addressBookEnhancedDisplayName()
@@ -56,9 +57,11 @@ import linphonesw
super.viewDidLoad()
view.backgroundColor = VoipTheme.voipBackgroundColor.get()
+ view.accessibilityIdentifier = "IO_call_view"
view.addSubview(spinner)
spinner.square(IncomingOutgoingCommonView.spinner_size).matchParentSideBorders().alignParentTop(withMargin:IncomingOutgoingCommonView.spinner_margin_top + UIDevice.notchHeight()).done()
+ spinner.accessibilityIdentifier = "IO_call_view_spinner"
let callType = StyledLabel(VoipTheme.call_header_title,forCallType)
view.addSubview(callType)
@@ -66,16 +69,19 @@ import linphonesw
self.view.addSubview(duration)
duration.matchParentSideBorders().alignUnder(view:callType,withMargin:IncomingOutgoingCommonView.duration_margin_top).done()
+ duration.accessibilityIdentifier = "IO_call_view_duration"
// Center : Avatar + Display name + SIP Address
let centerSection = UIView()
centerSection.addSubview(avatar)
+ avatar.square(Avatar.diameter_for_call_views).center().done()
centerSection.addSubview(displayName)
displayName.height(IncomingOutgoingCommonView.display_name_height).matchParentSideBorders().alignUnder(view:avatar,withMargin:IncomingOutgoingCommonView.display_name_margin_top).done()
centerSection.addSubview(sipAddress)
sipAddress.height(IncomingOutgoingCommonView.sip_address_height).matchParentSideBorders().alignUnder(view:displayName,withMargin:IncomingOutgoingCommonView.sip_address_margin_top).done()
self.view.addSubview(centerSection)
- centerSection.matchParentSideBorders().center().done()
+ centerSection.matchParentDimmensions().center().done()
+
layoutRotatableElements()
}
@@ -101,6 +107,7 @@ import linphonesw
override func viewWillDisappear(_ animated: Bool) {
spinner.stopRotation()
+ duration.call = nil
super.viewWillDisappear(animated)
}
diff --git a/Classes/Swift/Voip/Views/Fragments/NumpadView.swift b/Classes/Swift/Voip/Views/Fragments/NumpadView.swift
index f69b5d3da..a63373430 100644
--- a/Classes/Swift/Voip/Views/Fragments/NumpadView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/NumpadView.swift
@@ -41,7 +41,9 @@ import linphonesw
layer.cornerRadius = corner_radius
clipsToBounds = true
superView.addSubview(self)
- matchParentSideBorders(insetedByDx: side_margins).alignParentTop(withMargin: marginTop).alignAbove(view: above,withMargin: SharedLayoutConstants.buttons_bottom_margin).done()
+ matchParentSideBorders(insetedByDx: side_margins).alignParentTop(withMargin: marginTop).alignParentBottom().done()
+ accessibilityIdentifier = "call_numpad_view"
+ accessibilityViewIsModal = true
callData.callState.observe { state in
if (state == Call.State.End) {
@@ -56,6 +58,8 @@ import linphonesw
})
addSubview(hide)
hide.alignParentRight(withMargin: side_margins).alignParentTop(withMargin: side_margins).done()
+ hide.accessibilityIdentifier = "call_numpad_view_hide"
+ hide.accessibilityLabel = "Hide"
// DTMF History :
@@ -65,6 +69,7 @@ import linphonesw
callData.enteredDTMF.readCurrentAndObserve { (dtmfs) in
eneteredDtmf.text = dtmfs
}
+ eneteredDtmf.accessibilityIdentifier = "call_numpad_view_text_field"
// Digit buttons
@@ -86,6 +91,7 @@ import linphonesw
callData.sendDTMF(dtmf: "\(subkey)")
})
newRow.addArrangedSubview(digit)
+ digit.accessibilityIdentifier = "call_numpad_view_digit_\(subkey)"
}
}
}
diff --git a/Classes/Swift/Voip/Views/Fragments/PausedCallOrConferenceView.swift b/Classes/Swift/Voip/Views/Fragments/PausedCallOrConferenceView.swift
index 381fce7e9..50c9b7b92 100644
--- a/Classes/Swift/Voip/Views/Fragments/PausedCallOrConferenceView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/PausedCallOrConferenceView.swift
@@ -33,8 +33,8 @@ class PausedCallOrConferenceView: UIView {
var icon : UIImageView? = nil
let title = StyledLabel(VoipTheme.call_or_conference_title)
let subtitle = StyledLabel(VoipTheme.call_or_conference_subtitle)
-
- var onClickAction : (()->Void)? = nil
+
+ var onClickAction : (()->Void)? = nil
required init?(coder: NSCoder) {
super.init(coder: coder)
@@ -44,6 +44,8 @@ class PausedCallOrConferenceView: UIView {
super.init(frame: .zero)
backgroundColor = VoipTheme.voip_translucent_popup_background
+ accessibilityIdentifier = "paused_call_view"
+ accessibilityViewIsModal = true
let centeredView = UIView()
icon = UIImageView(image: UIImage(named:iconName)?.withPadding(padding: icon_padding))
@@ -53,7 +55,8 @@ class PausedCallOrConferenceView: UIView {
icon!.contentMode = .scaleAspectFit
centeredView.addSubview(icon!)
icon!.square(icon_size).centerX().done()
-
+ icon?.accessibilityIdentifier = "paused_call_view_icon"
+
title.numberOfLines = 0
centeredView.addSubview(title)
title.alignUnder(view:icon!, withMargin:title_margin_top).matchParentSideBorders().done()
@@ -66,6 +69,11 @@ class PausedCallOrConferenceView: UIView {
self.addSubview(centeredView)
centeredView.center().matchParentSideBorders().wrapContentY().done()
+
+ self.onClickAction = onClickAction
+ icon!.onClick {
+ self.onClickAction?()
+ }
self.onClickAction = onClickAction
icon!.onClick {
diff --git a/Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift b/Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift
index abaaba42c..ff9eb55eb 100644
--- a/Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift
+++ b/Classes/Swift/Voip/Views/Fragments/RemotelyRecording.swift
@@ -54,7 +54,7 @@ class RemotelyRecordingView: UIView {
addSubview(icon)
icon.square(height).toLeftOf(label).done()
- isHidden = true
+ isHidden = true
}
diff --git a/Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift b/Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift
index 5b70663d7..d656081ca 100644
--- a/Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift
+++ b/Classes/Swift/Voip/Views/Fragments/VoipExtraButtonsView.swift
@@ -39,9 +39,10 @@ class VoipExtraButtonsView: UIStackView {
axis = .vertical
distribution = .fillEqually
alignment = .center
-
layer.cornerRadius = corner_radius
clipsToBounds = true
+ accessibilityIdentifier = "active_call_extra_buttons_view"
+ accessibilityViewIsModal = true
let background = UIView()
background.backgroundColor = VoipTheme.voipExtraButtonsBackgroundColor.get()
@@ -60,19 +61,22 @@ class VoipExtraButtonsView: UIStackView {
// First row
let numpad = VoipExtraButton(text: VoipTexts.call_action_numpad, buttonTheme: VoipTheme.call_action("voip_call_numpad"),onClickAction: {
- ControlsViewModel.shared.numpadVisible.value = true
+ ControlsViewModel.shared.numpadVisible.value = true
})
row1.addArrangedSubview(numpad)
+ numpad.accessibilityIdentifier = "active_call_extra_buttons_numpad"
let stats = VoipExtraButton(text: VoipTexts.call_action_statistics, buttonTheme: VoipTheme.call_action("voip_call_stats"),onClickAction: {
ControlsViewModel.shared.callStatsVisible.value = true
})
row1.addArrangedSubview(stats)
+ stats.accessibilityIdentifier = "active_call_extra_buttons_stats"
let chats = VoipExtraButton(text: VoipTexts.call_action_chat, buttonTheme: VoipTheme.call_action("voip_call_chat"),withbBoucinCounterDataSource:CallsViewModel.shared.currentCallUnreadChatMessageCount, onClickAction: {
ControlsViewModel.shared.goToChatEvent.notifyAllObservers(with: true)
})
row1.addArrangedSubview(chats)
+ chats.accessibilityIdentifier = "active_call_extra_buttons_chats"
addArrangedSubview(row1)
row1.matchParentSideBorders().done()
@@ -91,12 +95,13 @@ class VoipExtraButtonsView: UIStackView {
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
})
row2.addArrangedSubview(transfer)
+ transfer.accessibilityIdentifier = "active_call_extra_buttons_transfer"
let participants = VoipExtraButton(text: VoipTexts.call_action_participants_list, buttonTheme: VoipTheme.call_action("voip_call_participants"),onClickAction: {
ControlsViewModel.shared.goToConferenceParticipantsListEvent.notifyAllObservers(with: true)
})
row2.addArrangedSubview(participants)
-
+ participants.accessibilityIdentifier = "active_call_extra_buttons_participants"
let addcall = VoipExtraButton(text: VoipTexts.call_action_add_call, buttonTheme: VoipTheme.call_action("voip_call_add"),onClickAction: {
let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
@@ -105,17 +110,19 @@ class VoipExtraButtonsView: UIStackView {
PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
})
row2.addArrangedSubview(addcall)
-
+ addcall.accessibilityIdentifier = "active_call_extra_buttons_add_call"
let layoutselect = VoipExtraButton(text: VoipTexts.call_action_change_conf_layout, buttonTheme: VoipTheme.call_action("voip_conference_mosaic"),onClickAction: {
ControlsViewModel.shared.goToConferenceLayoutSettings.notifyAllObservers(with: true)
})
row2.addArrangedSubview(layoutselect)
+ layoutselect.accessibilityIdentifier = "active_call_extra_buttons_layout_select"
let calls = VoipExtraButton(text: VoipTexts.call_action_calls_list, buttonTheme: VoipTheme.call_action("voip_calls_list"), withbBoucinCounterDataSource: CallsViewModel.shared.inactiveCallsCount, onClickAction: {
ControlsViewModel.shared.goToCallsListEvent.notifyAllObservers(with: true)
})
row2.addArrangedSubview(calls)
+ calls.accessibilityIdentifier = "active_call_extra_buttons_calls"
addArrangedSubview(row2)
row2.matchParentSideBorders().done()
diff --git a/Classes/Swift/Voip/VoipDialog_BACKUP_1064.swift b/Classes/Swift/Voip/VoipDialog_BACKUP_1064.swift
new file mode 100644
index 000000000..47c1bd059
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BACKUP_1064.swift
@@ -0,0 +1,134 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+<<<<<<< HEAD
+ let title_margin_sides = 10.0
+=======
+ let title_side_margin = 10
+>>>>>>> 4b91fc131 (all)
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+<<<<<<< HEAD
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+=======
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+>>>>>>> 4b91fc131 (all)
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BACKUP_1156.swift b/Classes/Swift/Voip/VoipDialog_BACKUP_1156.swift
new file mode 100644
index 000000000..47c1bd059
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BACKUP_1156.swift
@@ -0,0 +1,134 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+<<<<<<< HEAD
+ let title_margin_sides = 10.0
+=======
+ let title_side_margin = 10
+>>>>>>> 4b91fc131 (all)
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+<<<<<<< HEAD
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+=======
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+>>>>>>> 4b91fc131 (all)
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BACKUP_884.swift b/Classes/Swift/Voip/VoipDialog_BACKUP_884.swift
new file mode 100644
index 000000000..47c1bd059
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BACKUP_884.swift
@@ -0,0 +1,134 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+<<<<<<< HEAD
+ let title_margin_sides = 10.0
+=======
+ let title_side_margin = 10
+>>>>>>> 4b91fc131 (all)
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+<<<<<<< HEAD
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+=======
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+>>>>>>> 4b91fc131 (all)
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BACKUP_973.swift b/Classes/Swift/Voip/VoipDialog_BACKUP_973.swift
new file mode 100644
index 000000000..47c1bd059
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BACKUP_973.swift
@@ -0,0 +1,134 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+<<<<<<< HEAD
+ let title_margin_sides = 10.0
+=======
+ let title_side_margin = 10
+>>>>>>> 4b91fc131 (all)
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+<<<<<<< HEAD
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+=======
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+>>>>>>> 4b91fc131 (all)
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BASE_1064.swift b/Classes/Swift/Voip/VoipDialog_BASE_1064.swift
new file mode 100644
index 000000000..055ded827
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BASE_1064.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders().done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BASE_1156.swift b/Classes/Swift/Voip/VoipDialog_BASE_1156.swift
new file mode 100644
index 000000000..055ded827
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BASE_1156.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders().done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BASE_884.swift b/Classes/Swift/Voip/VoipDialog_BASE_884.swift
new file mode 100644
index 000000000..055ded827
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BASE_884.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders().done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_BASE_973.swift b/Classes/Swift/Voip/VoipDialog_BASE_973.swift
new file mode 100644
index 000000000..055ded827
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_BASE_973.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders().done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_LOCAL_1064.swift b/Classes/Swift/Voip/VoipDialog_LOCAL_1064.swift
new file mode 100644
index 000000000..84231fc65
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_LOCAL_1064.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_margin_sides = 10.0
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_LOCAL_1156.swift b/Classes/Swift/Voip/VoipDialog_LOCAL_1156.swift
new file mode 100644
index 000000000..84231fc65
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_LOCAL_1156.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_margin_sides = 10.0
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_LOCAL_884.swift b/Classes/Swift/Voip/VoipDialog_LOCAL_884.swift
new file mode 100644
index 000000000..84231fc65
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_LOCAL_884.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_margin_sides = 10.0
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_LOCAL_973.swift b/Classes/Swift/Voip/VoipDialog_LOCAL_973.swift
new file mode 100644
index 000000000..84231fc65
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_LOCAL_973.swift
@@ -0,0 +1,126 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_margin_sides = 10.0
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: title_margin_sides).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_REMOTE_1064.swift b/Classes/Swift/Voip/VoipDialog_REMOTE_1064.swift
new file mode 100644
index 000000000..1ab6afcc4
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_REMOTE_1064.swift
@@ -0,0 +1,127 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_side_margin = 10
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_REMOTE_1156.swift b/Classes/Swift/Voip/VoipDialog_REMOTE_1156.swift
new file mode 100644
index 000000000..1ab6afcc4
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_REMOTE_1156.swift
@@ -0,0 +1,127 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_side_margin = 10
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_REMOTE_884.swift b/Classes/Swift/Voip/VoipDialog_REMOTE_884.swift
new file mode 100644
index 000000000..1ab6afcc4
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_REMOTE_884.swift
@@ -0,0 +1,127 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_side_margin = 10
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/VoipDialog_REMOTE_973.swift b/Classes/Swift/Voip/VoipDialog_REMOTE_973.swift
new file mode 100644
index 000000000..1ab6afcc4
--- /dev/null
+++ b/Classes/Swift/Voip/VoipDialog_REMOTE_973.swift
@@ -0,0 +1,127 @@
+/*
+ * 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 .
+ */
+
+import Foundation
+import UIKit
+
+class VoipDialog : UIView{
+
+ // Layout constants
+ let center_corner_radius = 7.0
+ let title_margin_top = 20
+ let title_side_margin = 10
+ let button_margin = 20.0
+ let button_width = 135.0
+ let button_height = 40.0
+ let button_radius = 3.0
+ let button_spacing = 15.0
+
+ let center_view_sides_margin = 13.0
+
+
+ let title = StyledLabel(VoipTheme.basic_popup_title)
+
+ init(message:String, givenButtons:[ButtonAttributes]? = nil) {
+
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centerView = UIView()
+ centerView.backgroundColor = VoipTheme.dark_grey_color.withAlphaComponent(0.8)
+ centerView.layer.cornerRadius = center_corner_radius
+ centerView.clipsToBounds = true
+ addSubview(centerView)
+
+ title.numberOfLines = 0
+ centerView.addSubview(title)
+ title.alignParentTop(withMargin:title_margin_top).matchParentSideBorders(insetedByDx: CGFloat(title_side_margin)).done()
+ title.text = message
+
+ let buttonsView = UIStackView()
+ buttonsView.axis = .horizontal
+ buttonsView.spacing = button_spacing
+
+ var buttons = givenButtons
+
+ if (buttons == nil) { // assuming info popup, just putting an ok button
+ let ok = ButtonAttributes(text:VoipTexts.ok, action: {}, isDestructive:false)
+ buttons = [ok]
+ }
+
+ buttons?.forEach {
+ let b = ButtonWithStateBackgrounds(backgroundStateColors: $0.isDestructive ? VoipTheme.primary_colors_background_gray : VoipTheme.primary_colors_background)
+ b.setTitle($0.text, for: .normal)
+ b.layer.cornerRadius = button_radius
+ b.clipsToBounds = true
+ buttonsView.addArrangedSubview(b)
+ b.applyTitleStyle(VoipTheme.form_button_bold)
+ let action = $0.action
+ b.onClick {
+ self.removeFromSuperview()
+ action()
+ }
+ b.size(w: button_width,h: button_height).done()
+ }
+ centerView.addSubview(buttonsView)
+ buttonsView.alignUnder(view:title,withMargin:button_margin).alignParentBottom(withMargin:button_margin).centerX().done()
+
+
+
+ centerView.matchParentSideBorders(insetedByDx: center_view_sides_margin).center().done()
+ }
+
+ func show() {
+ VoipDialog.rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private static func rootVC() -> UIViewController? {
+ return PhoneMainView.instance().mainViewController
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ static var toastQueue: [String] = []
+
+ static func toast(message:String, timeout:CGFloat = 1.5) {
+ if (toastQueue.count > 0) {
+ toastQueue.append(message)
+ return
+ }
+ let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
+ rootVC()?.present(alert, animated: true)
+ DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout) {
+ alert.dismiss(animated: true)
+ if (toastQueue.count > 0) {
+ let message = toastQueue.first
+ toastQueue.remove(at: 0)
+ self.toast(message: message!)
+ }
+ }
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Swift/Voip/Widgets/Avatar.swift b/Classes/Swift/Voip/Widgets/Avatar.swift
index 7152b6d2f..140d47996 100644
--- a/Classes/Swift/Voip/Widgets/Avatar.swift
+++ b/Classes/Swift/Voip/Widgets/Avatar.swift
@@ -42,6 +42,8 @@ class Avatar : UIImageView {
self.backgroundColor = color.get()
addSubview(initialsLabel)
_ = initialsLabel.matchParentSideBorders().matchParentHeight()
+ accessibilityLabel = "Avatar"
+ isAccessibilityElement = true
}
diff --git a/Classes/Swift/Voip/Widgets/StyledValuePicker.swift b/Classes/Swift/Voip/Widgets/StyledValuePicker.swift
index 979e61c30..25d8b82d2 100644
--- a/Classes/Swift/Voip/Widgets/StyledValuePicker.swift
+++ b/Classes/Swift/Voip/Widgets/StyledValuePicker.swift
@@ -85,7 +85,7 @@ class StyledValuePicker: UIView {
onClick {
self.dropDown.anchorView = self.superview
- self.dropDown.tableView.scrollToRow(at: IndexPath(row: liveIndex.value!, section: 0), at: .top, animated: true) // Change visibility to public instead of fileprivate in DropDown.swift
+ /*self.dropDown.tableView.scrollToRow(at: IndexPath(row: liveIndex.value!, section: 0), at: .top, animated: true) // Change visibility to public instead of fileprivate in DropDown.swift*/
self.dropDown.show()
}
diff --git a/Classes/Swift/Voip/Widgets/UICallTimer.swift b/Classes/Swift/Voip/Widgets/UICallTimer.swift
index 154062bbf..9dc683f1f 100644
--- a/Classes/Swift/Voip/Widgets/UICallTimer.swift
+++ b/Classes/Swift/Voip/Widgets/UICallTimer.swift
@@ -25,19 +25,16 @@ class CallTimer : StyledLabel {
let min_width = 50.0
let formatter = DateComponentsFormatter()
+ var startDate = Date()
var call:Call? = nil {
didSet {
- if (self.call != nil) {
- self.format()
- }
+ self.format()
}
}
var conference:Conference? = nil {
didSet {
- if (self.conference != nil) {
- self.format()
- }
+ self.format()
}
}
@@ -51,28 +48,24 @@ class CallTimer : StyledLabel {
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second ]
formatter.zeroFormattingBehavior = [ .pad ]
- let startDate = Date()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
+ var elapsedTime: TimeInterval = 0
if (self.call != nil || self.conference != nil) {
- self.format()
- } else {
- let elapsedTime = Date().timeIntervalSince(startDate)
- self.formatter.string(from: elapsedTime).map {
- self.text = $0.hasPrefix("0:") ? "0" + $0 : $0
- }
+ elapsedTime = Date().timeIntervalSince(self.startDate)
+ }
+ self.formatter.string(from: elapsedTime).map {
+ self.text = $0.hasPrefix("0:") ? "0" + $0 : $0
}
}
minWidth(min_width).done()
}
+
func format() {
guard let duration = self.call != nil ? self.call!.duration : self.conference != nil ? self.conference!.duration: nil else {
return
}
- formatter.string(from: TimeInterval(duration)).map {
- self.text = $0.hasPrefix("0:") ? "0" + $0 : $0
- }
+ startDate = Date().advanced(by: -TimeInterval(duration))
}
-
}
diff --git a/Podfile b/Podfile
index d4693c415..41dc80f25 100644
--- a/Podfile
+++ b/Podfile
@@ -51,6 +51,15 @@ target 'msgNotificationContent' do
end
+target 'CallUITests' do
+ # Uncomment the next line if you're using Swift or would like to use dynamic frameworks
+ use_frameworks!
+
+ # Pods for CallUITests
+ all_pods
+
+end
+
post_install do |installer|
system("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")
# Get the version of linphone-sdk
diff --git a/Resources/images/color_A.png b/Resources/images/color_A.png
index cd49d4b5e..649910430 100644
Binary files a/Resources/images/color_A.png and b/Resources/images/color_A.png differ
diff --git a/Settings/InAppSettings.bundle/Call.plist b/Settings/InAppSettings.bundle/Call.plist
index 2ce526c4a..ff9d16866 100644
--- a/Settings/InAppSettings.bundle/Call.plist
+++ b/Settings/InAppSettings.bundle/Call.plist
@@ -14,16 +14,6 @@
DefaultValue
-
- Type
- PSToggleSwitchSpecifier
- Title
- Notify and get notified when call is recorded
- Key
- record_aware
- DefaultValue
-
-
Type
PSToggleSwitchSpecifier
diff --git a/Settings/InAppSettings.bundle/Network.plist b/Settings/InAppSettings.bundle/Network.plist
index 56ea3c298..65fa5e8a6 100644
--- a/Settings/InAppSettings.bundle/Network.plist
+++ b/Settings/InAppSettings.bundle/Network.plist
@@ -164,6 +164,22 @@
Type
PSGroupSpecifier
+
+ Key
+ dns_server_preference
+ Title
+ DNS Server
+ Type
+ PSTextFieldSpecifier
+ AutocapitalizationType
+ None
+ AutocorrectionType
+ No
+ DefaultValue
+
+ IASKTextAlignment
+ IASKUITextAlignmentRight
+
Key
adaptive_rate_control_group
diff --git a/UITests/CallUITests/ActiveCallUITests.swift b/UITests/CallUITests/ActiveCallUITests.swift
new file mode 100644
index 000000000..44733cc49
--- /dev/null
+++ b/UITests/CallUITests/ActiveCallUITests.swift
@@ -0,0 +1,96 @@
+import XCTest
+
+class ActiveCallUITests: XCTestCase {
+ var methods: ActiveCallViewUITestsMethods!
+
+ override func setUpWithError() throws {
+ continueAfterFailure = true
+ UITestsUtils.testAppSetup()
+ methods = ActiveCallViewUITestsMethods() //to reload accounts infos if testAppSetup change them
+ }
+
+
+ func testViewDisplay() throws {
+ methods.startActiveCall()
+ methods.endCall()
+ }
+
+ func testOpenCallStats() throws {
+ methods.startActiveCall()
+ methods.openCallStatsFromStatusBar()
+ methods.endCall()
+ }
+
+ func testCloseCallStats() throws {
+ methods.startActiveCall()
+ methods.openCallStatsFromStatusBar()
+ methods.closeCallStatsFromStatusBar()
+ methods.endCall()
+ }
+
+ func testCallRecord() throws {
+ methods.startActiveCall()
+ methods.startCallRecord()
+ methods.stopCallRecord()
+ methods.endCall()
+ }
+
+ func testRemoteCallRecord() throws {
+ methods.startActiveCall()
+ methods.startCallRecord(remote: true)
+ methods.stopCallRecord(remote: true)
+ methods.endCall()
+ }
+
+ func testPauseCall() throws {
+ methods.startActiveCall()
+ methods.pauseActiveCall()
+ methods.endCall()
+ }
+
+ func testResumeCall() throws {
+ methods.startActiveCall()
+ methods.pauseActiveCall()
+ methods.resumeActiveCall()
+ methods.endCall()
+ }
+
+ func testRemotePauseCall() throws {
+ methods.startActiveCall()
+ methods.pauseRemoteCall()
+ methods.endCall()
+ }
+
+ func testRemoteResumeCall() throws {
+ methods.startActiveCall()
+ methods.pauseRemoteCall()
+ methods.resumeRemoteCall()
+ methods.endCall()
+ }
+
+ func testToggleControls() throws {
+ methods.startActiveCall()
+ methods.toggleCallControls(buttonTag: "speaker", parentView: methods.app.activeCallView)
+ methods.toggleCallControls(buttonTag: "mute",parentView: methods.app.activeCallView)
+ methods.endCall()
+ }
+
+ func testOpenExtraMenu() throws {
+ methods.startActiveCall()
+ methods.openExtraButtonMenu()
+ methods.endCall()
+ }
+
+ func testCloseExtraMenu() throws {
+ methods.startActiveCall()
+ methods.openExtraButtonMenu()
+ methods.closeExtraButtonMenu()
+ methods.endCall()
+ }
+
+ func testHangup() throws {
+ methods.startActiveCall()
+ methods.hangupActiveCall()
+ }
+}
+
diff --git a/UITests/CallUITests/ExtraMenuUITests.swift b/UITests/CallUITests/ExtraMenuUITests.swift
new file mode 100644
index 000000000..7026b0e50
--- /dev/null
+++ b/UITests/CallUITests/ExtraMenuUITests.swift
@@ -0,0 +1,53 @@
+import XCTest
+
+class ExtraMenuUITests: XCTestCase {
+ var methods: ExtraMenuActiveCallActionsUITestsMethods!
+
+ override func setUpWithError() throws {
+ continueAfterFailure = true
+ UITestsUtils.testAppSetup()
+ methods = ExtraMenuActiveCallActionsUITestsMethods() //to reload accounts infos if testAppSetup change them
+ }
+
+
+ func testViewDisplay() throws {
+ methods.displayExtraMenuButtonView()
+ methods.endCall()
+ }
+
+ func testOpenCallStats() throws {
+ methods.displayExtraMenuButtonView()
+ methods.openCallStatsFromExtraMenuButtonView()
+ methods.endCall()
+ }
+
+ func testCloseCallStats() throws {
+ methods.displayExtraMenuButtonView()
+ methods.openCallStatsFromExtraMenuButtonView()
+ methods.closeCallStatsFromItself()
+ methods.endCall()
+ }
+
+ func testOpenCallNumpad() throws {
+ methods.displayExtraMenuButtonView()
+ methods.openCallNumpad()
+ methods.endCall()
+ }
+
+ func testCloseCallNumpad() throws {
+ methods.displayExtraMenuButtonView()
+ methods.openCallNumpad()
+ methods.closeCallNumpad()
+ methods.endCall()
+ }
+
+ func testNumpadtyping() {
+ methods.displayExtraMenuButtonView()
+ methods.openCallNumpad()
+ methods.composeNumpadNumbers()
+ methods.endCall()
+ }
+
+
+ //to complete with other buttons
+}
diff --git a/UITests/CallUITests/IncomingCallUITests.swift b/UITests/CallUITests/IncomingCallUITests.swift
new file mode 100644
index 000000000..81036d30d
--- /dev/null
+++ b/UITests/CallUITests/IncomingCallUITests.swift
@@ -0,0 +1,36 @@
+import XCTest
+
+class IncomingCallUITests: XCTestCase {
+ var methods: IncomingOutgoingCallViewUITestsMethods!
+
+ override func setUpWithError() throws {
+ continueAfterFailure = true
+ UITestsUtils.testAppSetup()
+ methods = IncomingOutgoingCallViewUITestsMethods() //to reload accounts infos if testAppSetup changes them
+ }
+
+ func testViewDisplay() throws {
+ methods.startIncomingCall()
+ methods.endCall()
+ }
+
+ func testNoAnswer() throws {
+ methods.startIncomingCall()
+ methods.noAnswerIncomingCall()
+ }
+
+ func testDecline() throws {
+ methods.startIncomingCall()
+ methods.declineIncomingCall()
+ methods.endCall()
+ }
+
+ func testAccept() throws {
+ methods.startIncomingCall()
+ methods.acceptIncomingCall()
+ methods.endCall()
+ }
+}
+
+
+
diff --git a/UITests/CallUITests/OutgoingCallUITests.swift b/UITests/CallUITests/OutgoingCallUITests.swift
new file mode 100644
index 000000000..247ac06c0
--- /dev/null
+++ b/UITests/CallUITests/OutgoingCallUITests.swift
@@ -0,0 +1,39 @@
+import XCTest
+
+class OutgoingCallUITests: XCTestCase {
+ var methods: IncomingOutgoingCallViewUITestsMethods!
+
+ override func setUpWithError() throws {
+ continueAfterFailure = true
+ UITestsUtils.testAppSetup()
+ methods = IncomingOutgoingCallViewUITestsMethods() //to reload accounts infos if testAppSetup change them
+ }
+
+ func testViewDisplay() throws {
+ methods.startOutgoingCall()
+ methods.endCall()
+ }
+
+ func testNoAnswer() throws {
+ methods.startOutgoingCall()
+ methods.noAnswerOutgoingCall()
+
+ }
+
+ func testToggleMute() throws {
+ methods.startOutgoingCall()
+ methods.toggleCallControls(buttonTag: "mute", parentView: methods.app.callView)
+ methods.endCall()
+ }
+
+ func testToggleSpeaker() throws {
+ methods.startOutgoingCall()
+ methods.toggleCallControls(buttonTag: "speaker", parentView: methods.app.callView)
+ methods.endCall()
+ }
+
+ func testCancel() throws {
+ methods.startOutgoingCall()
+ methods.cancelOutgoingCall()
+ }
+}
diff --git a/UITests/Methods/ActiveCallViewUITestsMethods.swift b/UITests/Methods/ActiveCallViewUITestsMethods.swift
new file mode 100644
index 000000000..4f95d5727
--- /dev/null
+++ b/UITests/Methods/ActiveCallViewUITestsMethods.swift
@@ -0,0 +1,120 @@
+import XCTest
+
+class ActiveCallViewUITestsMethods : IncomingOutgoingCallViewUITestsMethods {
+
+ func startActiveCall() {
+ XCTContext.runActivity(named: "Start Active Call") { _ in
+ startIncomingCall()
+ acceptIncomingCall()
+ }
+ }
+
+ func openCallStatsFromStatusBar() {
+ XCTContext.runActivity(named: "Display Call Stats From Status Bar") { _ in
+ app.representationWithElements.makeBackup(named: "call_stats_closed")
+ app.statusBar.buttons["status_bar_incall_quality"].tap()
+
+ //app.callStatsView.representation.reMake()
+ app.representationWithElements.otherElement = app.callStatsView
+ app.representationWithElements.withElementVariations(mainView: ["shadow"], statusBar: ["call_view"], tabBar: []).check()
+
+ }
+ }
+
+ func closeCallStatsFromStatusBar() {
+ XCTContext.runActivity(named: "Hide Call Stats From Status Bar") { _ in
+ app.statusBar.buttons["status_bar_incall_quality"].tap()
+ app.representationWithElements.reloadBackup(named: "call_stats_closed").check()
+ }
+ }
+
+ func startCallRecord(remote: Bool = false) {
+ XCTContext.runActivity(named: "Start \(remote ? "Remote" : "") Call Record") { _ in
+ app.representationWithElements.makeBackup(named: "record_end")
+ if (!remote) {
+ app.activeCallView.buttons["active_call_center_section_record"].tap()
+ //app.activeCallView.representation.withVariations(named: ["record"]).reMake()
+ app.representationWithElements.updateElementVariations(mainView: ["record"], statusBar: [], tabBar: []).check()
+ } else {
+ ghostAccount.startRecording()
+ //app.activeCallView.representation.withVariations(named: ["remote_record"]).reMake()
+ app.representationWithElements.updateElementVariations(mainView: ["remote_record"], statusBar: [], tabBar: []).check()
+ }
+ ghostAccount.waitForRecordingState(recording: true, onRemote: !remote, timeout: 5)
+ }
+ }
+
+ func stopCallRecord(remote: Bool = false) {
+ XCTContext.runActivity(named: "Stop \(remote ? "Remote" : "") Call Record") { _ in
+ if (!remote) {
+ app.activeCallView.buttons["active_call_center_section_record"].tap()
+ } else {
+ ghostAccount.mCore.currentCall?.stopRecording()
+ }
+ ghostAccount.waitForRecordingState(recording: false, onRemote: !remote, timeout: 5)
+ app.representationWithElements.reloadBackup(named: "record_end").check()
+ }
+ }
+
+ func pauseActiveCall() {
+ XCTContext.runActivity(named: "Pause Active Call") { _ in
+ app.representationWithElements.makeBackup(named: "pause_end")
+ app.activeCallView.buttons["active_call_center_section_pause"].tap()
+ //app.activeCallView.representation.withVariations(named: ["pause"]).reMake()
+ app.representationWithElements.updateElementVariations(mainView: ["pause_shadow","pause"], statusBar: [], tabBar: []).check()
+ ghostAccount.waitForCallState(callState: .PausedByRemote, timeout: 5)
+ }
+ }
+
+ func resumeActiveCall() {
+ XCTContext.runActivity(named: "Resume Active Call") { _ in
+ app.activeCallView.images["paused_call_view_icon"].tap()
+ app.representationWithElements.reloadBackup(named: "pause_end").check()
+ ghostAccount.waitForCallState(callState: .StreamsRunning, timeout: 5)
+ }
+ }
+
+ func pauseRemoteCall() {
+ XCTContext.runActivity(named: "Pause Remote Call") { _ in
+ app.representationWithElements.makeBackup(named: "pause_end")
+ ghostAccount.pauseCall()
+ ghostAccount.waitForCallState(callState: .Paused, timeout: 5)
+ //app.activeCallView.representation.withVariations(named: ["remote_pause"]).reMake()
+ app.representationWithElements.updateElementVariations(mainView: ["pause_shadow","remote_pause"], statusBar: [], tabBar: []).check()
+ }
+ }
+
+ func resumeRemoteCall() {
+ XCTContext.runActivity(named: "Resume Remote Call") { _ in
+ ghostAccount.resumeCall()
+ ghostAccount.waitForCallState(callState: .StreamsRunning, timeout: 5)
+ app.representationWithElements.reloadBackup(named: "pause_end").check()
+ }
+ }
+
+ func openExtraButtonMenu() {
+ XCTContext.runActivity(named: "Open Extra Menu Button") { _ in
+ app.representationWithElements.makeBackup(named: "extra_menu_closed")
+ app.activeCallView.buttons["active_call_view_extra_buttons"].tap()
+ //app.activeCallView.representation.withVariations(named: ["extra_menu"]).reMake()
+ app.representationWithElements.updateElementVariations(mainView: ["shadow","extra_menu"], statusBar: [], tabBar: []).check()
+ }
+ }
+
+ func closeExtraButtonMenu() {
+ XCTContext.runActivity(named: "Check Extra Menu View Integrity") { _ in
+ app.activeCallView.otherElements["active_call_view_shading_mask"].tap()
+ app.representationWithElements.reloadBackup(named: "extra_menu_closed").check()
+ }
+ }
+
+ func hangupActiveCall() {
+ XCTContext.runActivity(named: "Hangup Active Call") { _ in
+ app.activeCallView.buttons["active_call_view_hangup"].tap()
+ app.representationWithElements.reloadBackup(named: "call_end").check()
+ ghostAccount.waitForCallState(callState: .Released, timeout: 5)
+ }
+ }
+
+}
+
diff --git a/UITests/Methods/ConferenceCallViewUITestsMethods.swift b/UITests/Methods/ConferenceCallViewUITestsMethods.swift
new file mode 100644
index 000000000..18e25a476
--- /dev/null
+++ b/UITests/Methods/ConferenceCallViewUITestsMethods.swift
@@ -0,0 +1,24 @@
+//
+// ConferenceCallViewUITestsMethods.swift
+// ConferenceUITests
+//
+// Created by Quentin Monnier on 05/08/2022.
+//
+
+import XCTest
+import linphonesw
+
+class ConferrenceCallViewUITestsMethods {
+
+ let app: XCUIApplication
+ let appAccountAuthInfo = UITestsCoreManager.instance.appAccountAuthInfo!
+ let ghostAccounts = UITestsCoreManager.instance.ghostAccounts
+
+ init(app: XCUIApplication) {
+ self.app = app
+ }
+
+ func startOutgoingConference() {
+
+ }
+}
diff --git a/UITests/Methods/ExtraMenuActiveCallActionsUITestsMethods.swift b/UITests/Methods/ExtraMenuActiveCallActionsUITestsMethods.swift
new file mode 100644
index 000000000..988728198
--- /dev/null
+++ b/UITests/Methods/ExtraMenuActiveCallActionsUITestsMethods.swift
@@ -0,0 +1,87 @@
+import XCTest
+
+class ExtraMenuActiveCallActionsUITestsMethods : ActiveCallViewUITestsMethods {
+
+ func displayExtraMenuButtonView() {
+ startActiveCall()
+ openExtraButtonMenu()
+ }
+
+ func openCallStatsFromExtraMenuButtonView() {
+ app.extraMenuView.buttons["active_call_extra_buttons_stats"].tap()
+
+ app.representationWithElements.otherElement = app.callStatsView
+ app.representationWithElements.withElementVariations(mainView: ["shadow"], statusBar: ["call_view"], tabBar: []).check()
+ }
+
+ func closeCallStatsFromItself() {
+ app.callStatsView.buttons["call_stats_view_hide"].tap()
+
+ app.representationWithElements.reloadBackup(named: "extra_menu_closed").check()
+ }
+
+ func openCallNumpad() {
+ app.extraMenuView.buttons["active_call_extra_buttons_numpad"].tap()
+
+ //app.numpadCallView.representation.reMake()
+ app.representationWithElements.otherElement = app.numpadCallView
+ app.representationWithElements.withElementVariations(mainView: ["shadow"], statusBar: ["call_view"], tabBar: []).check()
+ }
+
+ func closeCallNumpad() {
+ app.numpadCallView.buttons["call_numpad_view_hide"].tap()
+
+ app.representationWithElements.reloadBackup(named: "extra_menu_closed").check()
+ }
+
+ func composeNumpadNumbers() {
+
+ let textField = app.staticTexts["call_numpad_view_text_field"]
+ var digitsLabel = ["1","2","3","4","5","6","7","8","9","*","0","#"]
+ digitsLabel += digitsLabel
+ digitsLabel.shuffle()
+ for label in digitsLabel {
+ app.numpadCallView.buttons["call_numpad_view_digit_\(label)"].tap()
+
+ }
+
+ XCTAssertEqual(textField.label, digitsLabel.joined(), "Text Field value differs from the sequence typed (is equal to \"\(textField.label)\")")
+ }
+
+ /*
+ func openView(buttonTag: String, view: View) {
+ let button = app.extraMenuView.buttons["active_call_extra_buttons_\(buttonTag)"].tap()
+ //button.tap(action: .displayView, on: view)
+ }
+
+ func closeView(contextView: View) {
+ let hide = UIObject(identifier: "\(contextView.rawValue)_hide", type: .button, contextView: contextView)
+ //hide.tap(action: .hideView, on: contextView)
+ }
+
+ func backToCall(contextView: View) {
+ let button = UIObject(identifier: "back_to_call", type: .button, contextView: contextView)
+ //button.tap(action: .displayView, on: .ActiveCallView)
+ }
+
+ func checkNumpadView() {
+
+ UIObject(identifier: "call_numpad_view_hide", type: .button, contextView: .NumpadView)
+ let textField = UIObject(identifier: "call_numpad_view_text_field", type: .staticText, contextView: .NumpadView).element
+
+ var digitsLabel = ["1","2","3","4","5","6","7","8","9","*","0","#"]
+ digitsLabel += digitsLabel
+ digitsLabel.shuffle()
+ for label in digitsLabel {
+ UIObject(identifier: "call_numpad_view_digit_\(label)", type: .button, contextView: .NumpadView).element.tap()
+ }
+
+ XCTAssertEqual(textField.label, digitsLabel.joined(), "Text Field value differs from the sequence typed (is equal to \"\(textField.label)\")")
+ }
+
+ func closeCallsList() {
+ let hide = UIObject(identifier: "dismissable_view_close", type: .button, contextView: .CallsListView)
+ //hide.tap(action: .hideView, on: .CallsListView)
+ }*/
+}
+
diff --git a/UITests/Methods/IncomingOutgoingCallViewUITestsMethods.swift b/UITests/Methods/IncomingOutgoingCallViewUITestsMethods.swift
new file mode 100644
index 000000000..fa3aff06c
--- /dev/null
+++ b/UITests/Methods/IncomingOutgoingCallViewUITestsMethods.swift
@@ -0,0 +1,133 @@
+import XCTest
+import linphonesw
+
+class IncomingOutgoingCallViewUITestsMethods {
+
+ let app = XCUIApplication()
+ let manager = UITestsCoreManager.instance
+ let appAccountAuthInfo: AuthInfo = UITestsCoreManager.instance.appAccountAuthInfo!
+ let ghostAccount: UITestsRegisteredLinphoneCore = UITestsCoreManager.instance.ghostAccounts[0]
+
+ func startIncomingCall() {
+ XCTContext.runActivity(named: "Start Incoming Call") { _ in
+ if (ghostAccount.callState != .Released) {ghostAccount.terminateCall()}
+ app.representationWithElements.makeBackup(named: "call_end")
+
+ ghostAccount.startCall(adress: manager.createAdress(authInfo: appAccountAuthInfo))
+ ghostAccount.waitForCallState(callState: .OutgoingRinging, timeout: 5)
+
+ _ = app.callView.waitForExistence(timeout: 5)
+ checkCallTime(element: app.callView.staticTexts["IO_call_view_duration"])
+ app.callView.images["IO_call_view_spinner"].representation.isAnimated(timeInterval: 0.5)
+
+ //app.callView.representation.reMake()
+ //app.statusBar.representation.withVariations(named: ["call_view"]).reMake()
+ app.representationWithElements.mainView = app.callView
+ app.representationWithElements.withElementVariations(mainView: [], statusBar: ["call_view"], tabBar: []).check()
+ }
+ }
+
+ func startOutgoingCall() {
+ XCTContext.runActivity(named: "Start Outgoing Call") { _ in
+ if (ghostAccount.callState != .Released) {ghostAccount.terminateCall()}
+ if (!app.dialerView.exists) { app.launch()}
+ app.representationWithElements.makeBackup(named: "call_end")
+
+ app.dialerView.textFields["adress_field"].fillTextField(ghostAccount.mAuthInfo.username)
+ checkCallTime(element: app.callView.staticTexts["IO_call_view_duration"])
+
+ //app.callView.representation.withVariations(named: ["outgoing"]).reMake()
+ //app.statusBar.representation.withVariations(named: ["call_view"]).reMake()
+ app.representationWithElements.mainView = app.callView
+ app.representationWithElements.withElementVariations(mainView: ["outgoing"], statusBar: ["call_view"], tabBar: []).check()
+
+ ghostAccount.waitForCallState(callState: .IncomingReceived, timeout: 5)
+ }
+ }
+
+ func endCall() {
+ XCTContext.runActivity(named: "End Call (from remote)") { _ in
+ if (ghostAccount.callState == .Released) {return}
+
+ ghostAccount.terminateCall()
+ ghostAccount.waitForCallState(callState: .Released, timeout: 5)
+
+ app.representationWithElements.reloadBackup(named: "call_end").check()
+ }
+ }
+
+ //expected format : "mm:ss"
+ func checkCallTime(element: XCUIElement) {
+ XCTContext.runActivity(named: "Check call time increment") { _ in
+ let timerArray: [Int] = (0..<3).map{_ in
+ sleep(1)
+ return Int(element.label.split(separator: ":").last ?? "") ?? 0
+ }
+ XCTAssert(Set(timerArray).count >= 2, "Call Time is not correctly incremented, less than 2 differents values are displayed in 3 seconds")
+ XCTAssert(timerArray == timerArray.sorted(), "Call Time is not correctly incremented, it is not increasing")
+ XCTAssert(timerArray.first! <= 3, "Call Time is not correctly initialized, it is more than 3 right after the start (found: \(timerArray.first!))")
+ }
+ }
+
+
+ func toggleCallControls(buttonTag: String, parentView: XCUIElement) {
+ XCTContext.runActivity(named: "Toggle call control Button : \"\(buttonTag)\"") { _ in
+ app.representationWithElements.makeBackup(named: buttonTag)
+ parentView.buttons["call_control_view_\(buttonTag)"].tap()
+ app.representationWithElements.updateElementVariations(mainView: [buttonTag], statusBar: [], tabBar: []).check()
+
+ parentView.buttons["call_control_view_\(buttonTag)"].tap()
+ app.representationWithElements.reloadBackup(named: buttonTag).check()
+ }
+ }
+
+ func noAnswerIncomingCall() {
+ XCTContext.runActivity(named: "Let Incoming Call Ring Until Stop") { _ in
+ XCTAssert(app.callView.waitForExistence(timeout: 5), "call already abort after less than 10 seconds ringing")
+ XCTAssert(app.callView.waitForNonExistence(timeout: 30), "call still not abort after 30 seconds ringing")
+ ghostAccount.waitForCallState(callState: .Released, timeout: 5)
+ app.representationWithElements.reloadBackup(named: "call_end").check()
+ }
+ }
+
+ func noAnswerOutgoingCall() {
+ XCTContext.runActivity(named: "Check Outgoing Call Failed Popup Integrity And Close") { context in
+ XCTAssert(app.callView.waitForExistence(timeout: 5), "call already abort after less than 10 seconds ringing")
+ XCTAssert(app.callView.waitForNonExistence(timeout: 30), "call still not abort after 30 seconds ringing")
+ ghostAccount.waitForCallState(callState: .Released, timeout: 5)
+ app.callFailedView.representation.check()
+ app.callFailedView.buttons["call_failed_error_view_action"].tap()
+ app.representationWithElements.reloadBackup(named: "call_end").check()
+ }
+
+ }
+
+ func cancelOutgoingCall() {
+ XCTContext.runActivity(named: "Cancel Outgoing Call") { _ in
+ app.callView.buttons["O_call_view_cancel"].tap()
+ app.representationWithElements.reloadBackup(named: "call_end").check()
+ ghostAccount.waitForCallState(callState: .Released, timeout: 5)
+ }
+ }
+
+ func declineIncomingCall() {
+ XCTContext.runActivity(named: "Decline Incoming Call") { _ in
+ app.callView.buttons["I_call_view_decline"].tap()
+ app.representationWithElements.reloadBackup(named: "call_end").check()
+ ghostAccount.waitForCallState(callState: .Released, timeout: 5)
+ }
+ }
+
+ func acceptIncomingCall() {
+ XCTContext.runActivity(named: "Accept Incoming Call") { _ in
+ app.callView.buttons["I_call_view_accept"].tap()
+ checkCallTime(element: app.activeCallView.staticTexts["active_call_upper_section_duration"])
+ app.representationWithElements.mainView = app.activeCallView
+ //app.activeCallView.representation.reMake()
+ app.representationWithElements.check()
+ ghostAccount.waitForCallState(callState: .StreamsRunning, timeout: 5)
+ }
+ }
+
+}
+
diff --git a/UITests/Methods/UITestsCoreManager.swift b/UITests/Methods/UITestsCoreManager.swift
new file mode 100644
index 000000000..3a605c7ab
--- /dev/null
+++ b/UITests/Methods/UITestsCoreManager.swift
@@ -0,0 +1,380 @@
+import XCTest
+import linphonesw
+
+
+class UITestsCoreManager {
+
+ private var mCore: Core!
+ private var coreVersion: String = Core.getVersion
+
+ private var mAccountCreator: AccountCreator!
+
+ var appAccountAuthInfo: AuthInfo!
+ var ghostAccounts: UITestsGhostAccounts!
+ let dnsServer = "51.255.123.121"
+
+ static let instance = UITestsCoreManager()
+
+
+ init() {
+ LoggingService.Instance.logLevel = LogLevel.Debug
+ Core.enableLogCollection(state: .Enabled)
+
+ //Config account creator for flexiapi
+ let config: Config! = try! Factory.Instance.createConfig(path: "\(Factory.Instance.getConfigDir(context: nil))/linphonerc")
+ config.setInt(section: "account_creator", key: "backend", value: AccountCreatorBackend.FlexiAPI.rawValue)
+ config.setString(section: "account_creator", key: "url", value: "http://subscribe.example.org/flexiapi/api/")
+ try! mCore = Factory.Instance.createCoreWithConfig(config: config, systemContext: nil)
+ mCore.dnsServersApp = [dnsServer]
+ mAccountCreator = try! mCore.createAccountCreator(xmlrpcUrl: nil)
+
+ try? mCore.start()
+
+ ghostAccounts = UITestsGhostAccounts(coreCreationFunction: newRegisteredLinphoneCore)
+
+ appAccountAuthInfo = mCore.authInfoList.isEmpty ? createAccount() : mCore.authInfoList[0]
+ }
+
+ deinit {
+ mCore.stop()
+ mCore = nil
+ }
+
+ func newRegisteredLinphoneCore() -> UITestsRegisteredLinphoneCore {
+ let authInfo = mCore.authInfoList.indices.contains(ghostAccounts.count+1) ? mCore.authInfoList[ghostAccounts.count+1] : createAccount()
+ return UITestsRegisteredLinphoneCore(authInfo: authInfo)
+ }
+
+ func createAccount() -> AuthInfo {
+ XCTContext.runActivity(named: "Create new account") { _ in
+ mAccountCreator.username = "uitester_\(String(Int(Date().timeIntervalSince1970*1000)).suffix(5))"
+ mAccountCreator.password = String((0..<15).map{ _ in mAccountCreator!.username.randomElement()! })
+ mAccountCreator.domain = "sip.example.org"
+ mAccountCreator.email = "\(mAccountCreator!.username)@\(mAccountCreator!.domain)"
+ mAccountCreator.transport = TransportType.Tcp
+ _ = try! mAccountCreator.createAccount()
+ waitForAccountCreationStatus(status: .RequestOk, timeout: 5)
+
+ let authInfo = try! Factory.Instance.createAuthInfo(username: mAccountCreator.username, userid: "", passwd: mAccountCreator.password, ha1: "", realm: "", domain: mAccountCreator.domain)
+ mCore.addAuthInfo(info: authInfo)
+ XCTContext.runActivity(named: "username : \(mAccountCreator.username)\npassword : \(mAccountCreator.password)\ndomain : \(mAccountCreator.domain)") { _ in}
+ return authInfo
+ }
+ }
+
+ func accountsReset() {
+ XCTContext.runActivity(named: "Clear all accounts") { _ in
+ mCore.clearAllAuthInfo()
+ ghostAccounts.reset()
+ appAccountAuthInfo = createAccount()
+ }
+ }
+
+ func createAdress(authInfo: AuthInfo) -> Address {
+ return try! Factory.Instance.createAddress(addr: "sip:\(authInfo.username)@\(authInfo.domain)")
+ }
+
+ func waitForAccountCreationStatus(status: AccountCreator.Status, timeout: Double) {
+ let expectation = XCTestExpectation(description: "account status is successfully : \(status)")
+ XCTContext.runActivity(named: "Waiting for account status : \(status)") { _ in
+ let accountCreatorDelegate = AccountCreatorDelegateStub(onCreateAccount: { (creator: AccountCreator, status: AccountCreator.Status, response: String) in
+ if (status == status) {
+ expectation.fulfill()
+ }
+ })
+ self.mAccountCreator?.addDelegate(delegate: accountCreatorDelegate)
+ let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
+ self.mAccountCreator?.removeDelegate(delegate: accountCreatorDelegate)
+ XCTAssert(result == .completed, "\"\(status)\" account status still not verified after \(timeout) seconds")
+ }
+ }
+}
+
+
+class UITestsGhostAccounts {
+
+ private var mCores = [UITestsRegisteredLinphoneCore]() {
+ didSet {
+ count = mCores.count
+ }
+ }
+ private(set) var count: Int!
+
+ private let newCore: (()->UITestsRegisteredLinphoneCore)!
+
+ init(coreCreationFunction: @escaping ()->UITestsRegisteredLinphoneCore) {
+ count = mCores.count
+ newCore = coreCreationFunction
+ }
+
+ func reset() {
+ mCores = []
+ }
+
+ subscript (index: Int) -> UITestsRegisteredLinphoneCore {
+ while (index >= mCores.count) {
+ mCores.append(newCore())
+ }
+ return mCores[index]
+ }
+
+}
+
+
+class UITestsRegisteredLinphoneCore {
+
+ var mCore: Core!
+ var coreVersion: String = Core.getVersion
+ var description: String
+
+ private let manager = UITestsCoreManager.instance
+
+ private(set) var mCoreDelegate : CoreDelegate!
+ private(set) var mAccount: Account!
+ private(set) var mAuthInfo: AuthInfo!
+
+ private(set) var callState : Call.State = .Released
+ private(set) var registrationState : RegistrationState = .Cleared
+
+ init(authInfo: AuthInfo) {
+
+ description = "Ghost Account (\(authInfo.username))"
+ LoggingService.Instance.logLevel = LogLevel.Debug
+ Core.enableLogCollection(state: .Enabled)
+
+ try! mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil)
+ mCore.dnsServers = [manager.dnsServer]
+
+ mCore.videoCaptureEnabled = true
+ mCore.videoDisplayEnabled = true
+ mCore.recordAwareEnabled = true
+ mCore.videoActivationPolicy!.automaticallyAccept = true
+
+ mCoreDelegate = CoreDelegateStub(onCallStateChanged: { (core: Core, call: Call, state: Call.State, message: String) in
+ self.callState = state
+ NSLog("\(call.params?.account?.params?.identityAddress) current call state is \(self.callState)\n")
+
+ }, onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in
+ self.registrationState = state
+ NSLog("New registration state is \(state) for user id \(account.params?.identityAddress)\n")
+ })
+ mCore.addDelegate(delegate: mCoreDelegate)
+
+ mCore.playFile = "sounds/hello8000.wav"
+ mCore.useFiles = true
+
+ try? mCore.start()
+
+ mAuthInfo = authInfo
+ login(transport: TransportType.Tcp)
+ }
+
+ deinit {
+ mCore.stop()
+ mCore = nil
+ mCoreDelegate = nil
+ }
+
+ func login(transport: TransportType) {
+ XCTContext.runActivity(named: "\(description) : Login") { _ in
+ do {
+ let accountParams = try mCore.createAccountParams()
+ let identity = manager.createAdress(authInfo: mAuthInfo)
+ try accountParams.setIdentityaddress(newValue: identity)
+ let address = try Factory.Instance.createAddress(addr: String("sip:" + mAuthInfo.domain))
+ try address.setTransport(newValue: transport)
+ try accountParams.setServeraddress(newValue: address)
+ accountParams.registerEnabled = true
+ let account = try mCore.createAccount(params: accountParams)
+ mCore.addAuthInfo(info: mAuthInfo)
+ try mCore.addAccount(account: account)
+ mAccount = account
+ mCore.defaultAccount = mAccount
+ waitForRegistrationState(registrationState: .Ok, timeout: 5)
+
+ } catch { NSLog(error.localizedDescription) }
+ }
+ }
+
+ private func makeRecordFilePath() -> String{
+ var filePath = "recording_"
+ let now = Date()
+ let dateFormat = DateFormatter()
+ dateFormat.dateFormat = "E-d-MMM-yyyy-HH-mm-ss"
+ let date = dateFormat.string(from: now)
+ filePath = filePath.appending("\(date).mkv")
+
+ let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
+ let writablePath = paths[0]
+ return writablePath.appending("/\(filePath)")
+ }
+
+ func startCall(adress: Address) {
+ XCTContext.runActivity(named: "\(description) : Start calling \(adress.username)") { _ in
+ do {
+ let params = try mCore.createCallParams(call: nil)
+ params.mediaEncryption = MediaEncryption.None
+ params.recordFile = makeRecordFilePath()
+ let _ = mCore.inviteAddressWithParams(addr: adress, params: params)
+ } catch { NSLog(error.localizedDescription) }
+ }
+
+ }
+
+ func terminateCall() {
+ XCTContext.runActivity(named: "\(description) : End call") { _ in
+ do {
+ if (mCore.callsNb == 0) { return }
+ let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
+ if let call = coreCall {
+ try call.terminate()
+ }
+ } catch { NSLog(error.localizedDescription) }
+ }
+ }
+
+ func acceptCall() {
+ XCTContext.runActivity(named: "\(description) : Accept call from \(mAuthInfo.username)") { _ in
+ // IMPORTANT : Make sure you allowed the use of the microphone (see key "Privacy - Microphone usage description" in Info.plist) !
+ do {
+ // if we wanted, we could create a CallParams object
+ // and answer using this object to make changes to the call configuration
+ // (see OutgoingCall tutorial)
+ try mCore.currentCall?.accept()
+ } catch { NSLog(error.localizedDescription) }
+ }
+ }
+
+ func toggleMicrophone() {
+ // The following toggles the microphone, disabling completely / enabling the sound capture
+ // from the device microphone
+ mCore.micEnabled = !mCore.micEnabled
+ }
+
+ func toggleSpeaker() {
+ // Get the currently used audio device
+ let currentAudioDevice = mCore.currentCall?.outputAudioDevice
+ let speakerEnabled = currentAudioDevice?.type == AudioDeviceType.Speaker
+
+ let test = currentAudioDevice?.deviceName
+ // We can get a list of all available audio devices using
+ // Note that on tablets for example, there may be no Earpiece device
+ for audioDevice in mCore.audioDevices {
+
+ // For IOS, the Speaker is an exception, Linphone cannot differentiate Input and Output.
+ // This means that the default output device, the earpiece, is paired with the default phone microphone.
+ // Setting the output audio device to the microphone will redirect the sound to the earpiece.
+ if (speakerEnabled && audioDevice.type == AudioDeviceType.Microphone) {
+ mCore.currentCall?.outputAudioDevice = audioDevice
+ return
+ } else if (!speakerEnabled && audioDevice.type == AudioDeviceType.Speaker) {
+ mCore.currentCall?.outputAudioDevice = audioDevice
+ return
+ }
+ /* If we wanted to route the audio to a bluetooth headset
+ else if (audioDevice.type == AudioDevice.Type.Bluetooth) {
+ core.currentCall?.outputAudioDevice = audioDevice
+ }*/
+ }
+ }
+
+ func toggleVideo() {
+ do {
+ if (mCore.callsNb == 0) { return }
+ let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
+ if let call = coreCall {
+ let params = try mCore.createCallParams(call: call)
+ params.videoEnabled = !(call.currentParams!.videoEnabled)
+ try call.update(params: params)
+ }
+ } catch { NSLog(error.localizedDescription) }
+ }
+
+ func toggleCamera() {
+ do {
+ let currentDevice = mCore.videoDevice
+ for camera in mCore.videoDevicesList {
+ if (camera != currentDevice && camera != "StaticImage: Static picture") {
+ try mCore.setVideodevice(newValue: camera)
+ break
+ }
+ }
+ } catch { NSLog(error.localizedDescription) }
+ }
+
+ func pauseCall() {
+ do {
+ if (mCore.callsNb == 0) { return }
+ let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
+ try coreCall!.pause()
+ } catch { NSLog(error.localizedDescription) }
+ }
+
+ func resumeCall() {
+ do {
+ if (mCore.callsNb == 0) { return }
+ let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
+ try coreCall!.resume()
+ } catch { NSLog(error.localizedDescription) }
+ }
+
+ func startRecording() {
+ mCore.currentCall?.startRecording()
+ }
+
+ func stopRecording() {
+ mCore.currentCall?.stopRecording()
+ }
+
+ func waitForRegistrationState(registrationState: RegistrationState, timeout: TimeInterval) {
+ let expectation = XCTestExpectation(description: "registration state is successfully : \(registrationState)")
+ XCTContext.runActivity(named: "Waiting for registration state : \(registrationState)") { _ in
+ if (registrationState == self.registrationState) { return}
+ let registeredDelegate = AccountDelegateStub(onRegistrationStateChanged: { (account: Account, state: RegistrationState, message: String) in
+ if (registrationState == state) {
+ expectation.fulfill()
+ }
+ })
+ self.mCore.defaultAccount!.addDelegate(delegate: registeredDelegate)
+ let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
+ self.mCore.defaultAccount!.removeDelegate(delegate: registeredDelegate)
+ XCTAssert(result == .completed, "\"\(registrationState)\" registration state still not verified after \(timeout) seconds")
+ }
+ }
+
+ func waitForCallState(callState: Call.State, timeout: TimeInterval) {
+ let expectation = XCTestExpectation(description: "call state is successfully : \(callState)")
+ XCTContext.runActivity(named: "Waiting for call state : \(callState)") { _ in
+ if (callState == self.callState) { return}
+ let callStateDelegate = CoreDelegateStub(onCallStateChanged: { (lc: Core, call: Call, state: Call.State, message: String) in
+ if (callState == state) {
+ expectation.fulfill()
+ }
+ })
+ self.mCore.addDelegate(delegate: callStateDelegate)
+ let result = XCTWaiter().wait(for: [expectation], timeout: timeout)
+ self.mCore.removeDelegate(delegate: callStateDelegate)
+ XCTAssert(result == .completed, "\"\(callState)\" call state still not verified after \(timeout) seconds")
+ }
+ }
+
+ func waitForRecordingState(recording: Bool, onRemote: Bool = false, timeout: TimeInterval) {
+ XCTContext.runActivity(named: "Waiting for call recording state : \(recording)") { _ in
+ var result = XCTWaiter.Result.timedOut
+ for _ in 0...Int(timeout) {
+ if (!onRemote && recording == mCore.currentCall?.params?.isRecording) {
+ result = .completed
+ break
+ }
+ if (onRemote && recording == mCore.currentCall?.remoteParams?.isRecording) {
+ result = .completed
+ break
+ }
+ _ = XCTWaiter().wait(for: [XCTestExpectation()], timeout: 1)
+ }
+ let remoteText = onRemote ? "remote" : ""
+ XCTAssert(result == .completed, "\(remoteText) call recording is still not \(recording) after \(timeout) seconds")
+ }
+ }
+
+}
diff --git a/UITests/Methods/UITestsScreenshots.swift b/UITests/Methods/UITestsScreenshots.swift
new file mode 100644
index 000000000..15fbdc5f7
--- /dev/null
+++ b/UITests/Methods/UITestsScreenshots.swift
@@ -0,0 +1,778 @@
+import SwiftUI
+import XCTest
+
+extension XCUIElement {
+ private static var _representation = [String:UITestsScreenshots]()
+
+ // hack to add variable in extensions
+ var representation: UITestsScreenshots {
+ get {
+ let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
+ if (XCUIElement._representation[tmpAddress] == nil) {
+ XCUIElement._representation[tmpAddress] = UITestsScreenshots(element: self)
+ }
+ return XCUIElement._representation[tmpAddress]!
+ }
+ }
+}
+
+extension XCUIApplication {
+ private static var _representationWithElement = UITestsAppRepresentation()
+
+ var representationWithElements: UITestsAppRepresentation {
+ get {
+ return XCUIApplication._representationWithElement
+ }
+ }
+}
+
+class UITestsAppRepresentation : UITestsScreenshots {
+
+ var mainView: XCUIElement
+ var otherElement: XCUIElement?
+ private let statusBar: XCUIElement
+ private let tabBar: XCUIElement
+ private let app = XCUIApplication()
+
+ private(set) var allVariations = [[String](),[String](),[String](),[String]()]
+
+ private static var backup: [String:(XCUIElement,XCUIElement?,[[String]])] = [:]
+
+ private var elementsDescription = ""
+ override var description: String {
+ get {
+ let description = super.description + " (definition = " + elementsDescription + ")"
+ elementsDescription = ""
+ return description
+ }
+ }
+
+ init() {
+ self.mainView = app.dialerView
+ self.statusBar = app.statusBar
+ self.tabBar = app.tabBar
+ super.init(element: app)
+ }
+
+ func withElementVariations(mainView: [String], statusBar: [String], tabBar: [String], other: [String] = []) -> UITestsAppRepresentation {
+ allVariations = [mainView, statusBar, tabBar, other]
+ return self
+ }
+
+ func updateElementVariations(mainView: [String], statusBar: [String], tabBar: [String], other: [String] = []) -> UITestsAppRepresentation {
+ allVariations[0] += mainView
+ allVariations[1] += statusBar
+ allVariations[2] += tabBar
+ allVariations[3] += other
+ return self
+ }
+
+ override func convertForComparaison(screenshot: UIImage) -> UIImage {
+ let elements = getElements()
+ if (svgManager.rects["mask"] == nil) {_=svgManager.parse()}
+ _=elements.map{
+ if ($0 != nil) {
+ if ($0!.representation.svgManager.rects["mask"] == nil) {_=$0!.representation.svgManager.parse()}
+ svgManager.rects["mask"]! += $0!.representation.svgManager.rects["mask"]!
+ }
+ }
+ return super.convertForComparaison(screenshot: screenshot)
+ }
+
+ override func getReference() -> UIImage? {
+ UIGraphicsBeginImageContextWithOptions(UITestsScreenshots.screenSize, false, 1)
+ guard (super.getReference()?.draw(at: CGPoint(x: 0, y: 0)) != nil) else {return nil}
+ let elements = getElements()
+ for i in 0.. [XCUIElement?]{
+ return [mainView,statusBar,tabBar,otherElement].map {($0 != nil && self.element.frame.contains($0!.frame)) ? $0! : nil}
+ }
+
+ func makeBackup(named: String) {
+ UITestsAppRepresentation.backup[named] = (mainView,otherElement,allVariations)
+ }
+
+ func reloadBackup(named: String) -> UITestsAppRepresentation {
+ if let backup = UITestsAppRepresentation.backup[named] {
+ mainView = backup.0
+ allVariations = backup.2
+ otherElement = backup.1
+ UITestsAppRepresentation.backup.removeValue(forKey: named)
+ } else {
+ XCTFail("unable to find an app representation backup named \"\(named)\"")
+ }
+ return self
+ }
+
+}
+
+class UITestsScreenshots {
+
+ static let screenshotDelay: TimeInterval = 0.5
+ static let pixelTreshold: Int = 3
+ static let colorTreshold: Int = 3
+ static let screenSize: CGSize = {
+ var size = XCUIApplication().frame.size
+ let scaleFactor = 3.0
+ size.width.scale(by: scaleFactor)
+ size.height.scale(by: scaleFactor)
+ return size
+ }()
+ static let defaultPath: String = {
+ let path = #filePath
+ return String(path.prefix(path.distance(from: path.startIndex, to: path.range(of: "UITests")!.lowerBound) + "UITests/".count)) + "Screenshots/"
+ }()
+ internal var description: String {
+ get {
+ return viewName + (variations.isEmpty ? "" : "||\(variations.joined(separator: ","))")
+ }
+ }
+ private(set) var debugHistory: String = ""
+
+ internal let element: XCUIElement
+ private var _svgManager: SVGManager?
+ internal var svgManager: SVGManager {
+ get{
+ if (_svgManager == nil) {_svgManager = SVGManager(path: "\(UITestsScreenshots.defaultPath + viewName).svg")}
+ return _svgManager!
+ }
+ }
+ internal var _viewName: String?
+ var viewName: String {
+ get {
+ if (_viewName == nil) {
+ _viewName = element.identifier
+ if (_viewName!.isEmpty) {_viewName = element.label} //for elements wich don't have identifier
+ debugHistory = "UITestsScreenshots : \(_viewName!) : "
+ }
+ return _viewName!
+ }
+ }
+ private(set) var variations = [String]()
+
+ init(element: XCUIElement) {
+ self.element = element
+ }
+
+ // public functions
+
+ func withVariations(named: [String]) -> UITestsScreenshots {
+ variations = named
+ debugHistory += " with varitions named \"\(named.joined(separator: "\", \""))\" -> "
+ return self
+ }
+
+ func make(after time: TimeInterval = screenshotDelay) {
+ XCTContext.runActivity(named: "Make \"\(viewName)\" reference screenshot") { context in
+ debugHistory += "make reference -> "
+ guard checkVariationNonDefinition(), referenceExist(expectedValue: false), var screenshot = takeScreenshot(after: time) else {return}
+ saveImage(image: screenshot, path: getPath(name: viewName))
+ svgManager.createFile(referenceName: viewName, referenceArea: getElementArea(), svgSize: UITestsScreenshots.screenSize)
+ screenshot = UITestsScreenshots.imageInScreenAcrea(image: screenshot, area: getElementArea())
+ let preview = UITestsScreenshots.createPreview(title: "Reference", image: screenshot)
+ context.add(UITestsScreenshots.createAttachement(image: preview, name: description))
+ debugHistory += "done."
+ }
+ }
+
+ func reMake(after time: TimeInterval = screenshotDelay) {
+ XCTContext.runActivity(named: "Remake \"\(viewName)\" reference screenshot") { context in
+ debugHistory += "re make reference -> "
+ guard referenceImagesExist(names: [viewName]+variations, expectedValue: true), var screenshot = takeScreenshot(after: time) else {return}
+ _ = (variations.isEmpty ? [viewName] : variations).map{
+ saveImage(image: screenshot, path: getPath(name: $0))
+ _ = svgManager.updateImage(name: $0, area: getElementArea())
+ }
+ screenshot = UITestsScreenshots.imageInScreenAcrea(image: screenshot, area: getElementArea())
+ let preview = UITestsScreenshots.createPreview(title: "Reference", image: screenshot)
+ context.add(UITestsScreenshots.createAttachement(image: preview, name: description))
+ debugHistory += "done."
+ XCTFail("\"\(#function)\" is a temporary function, you can't succeed a test with it\nafter remaking a reference you have to use \"check()\" if you want to compare")
+ }
+ }
+
+ func check(after time: TimeInterval = screenshotDelay) {
+ XCTContext.runActivity(named: "Check \"\(viewName)\" screenshot with his reference") { context in
+ debugHistory += "compare screenshot to reference -> "
+ guard var screenshot = takeScreenshot(after: time), let reference = getReference() else {return}
+ screenshot = convertForComparaison(screenshot: screenshot)
+ guard let variances = UITestsScreenshots.getVarianceAreas(reference, screenshot) else {return}
+ if (!variances.areas.isEmpty) {
+ let errorMsg = "variances found with the reference view in \(variances.areas.count) areas."
+ debugHistory += errorMsg
+ XCTFail(errorMsg)
+ } else {
+ debugHistory += "done."
+ }
+ let preview = UITestsScreenshots.comparativePreview(reference: reference, screenshot: screenshot, difference: variances.image, areas: variances.areas)
+ context.add(UITestsScreenshots.createAttachement(image: preview, name: description))
+
+ }
+ }
+
+ func addNewVariation(named name: String, after time: TimeInterval = screenshotDelay) {
+ XCTContext.runActivity(named: "Add \"\(viewName)\" reference screenshot new varation named \(name)") { context in
+ debugHistory += "add variances to a new variation named \(name) -> "
+ guard referenceImagesExist(names: [name], expectedValue: false), let screenshot = takeScreenshot(after: time), let reference = getReference() else {return}
+ guard let variances = UITestsScreenshots.getVarianceAreas(reference, convertForComparaison(screenshot: screenshot)) else {return}
+ guard !variances.areas.isEmpty else {
+ XCTFail(debugHistory + "error! : no variances found with the reference view")
+ return
+ }
+ saveImage(image: screenshot, path: getPath(name: name))
+ _ = svgManager.addVariation(referenceName: viewName, name: name, area: getElementArea(), rects: variances.areas)
+ let preview = UITestsScreenshots.variationPreview(reference: reference, areas: variances.areas)
+ context.add(UITestsScreenshots.createAttachement(image: preview, name: description))
+ debugHistory += "done."
+ }
+ }
+
+ func addNewFilterVariation(named name: String, color: UIColor, areas: [CGRect]) {
+ XCTContext.runActivity(named: "Add \"\(viewName)\" reference screenshot new filter varation named \(name)") { context in
+ let size = UITestsScreenshots.screenSize
+ UIGraphicsBeginImageContextWithOptions(size, false, 1)
+ color.setFill()
+ UIRectFillUsingBlendMode(CGRect(x: 0, y: 0, width: size.width, height: size.height), .normal)
+ let image = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ saveImage(image: image, path: getPath(name: name))
+ _ = svgManager.addVariation(referenceName: viewName, name: name, area: getElementArea(), rects: areas)
+ context.add(UITestsScreenshots.createAttachement(image: image, name: description))
+ debugHistory += "done."
+ }
+ }
+
+ func addToMask(after time: TimeInterval = screenshotDelay) {
+ XCTContext.runActivity(named: "Add new areas to \"\(viewName)\" mask") { context in
+ debugHistory += "add variances to mask -> "
+ guard let screenshot = takeScreenshot(after: time), let reference = getReference() else {return}
+ guard let variances = UITestsScreenshots.getVarianceAreas(reference, convertForComparaison(screenshot: screenshot)) else {return}
+ guard !variances.areas.isEmpty else {
+ XCTFail(debugHistory + "error! : no variances found with the reference view")
+ return
+ }
+ _ = svgManager.addToMask(rects: variances.areas)
+ let preview = UITestsScreenshots.variationPreview(reference: reference, areas: variances.areas)
+ context.add(UITestsScreenshots.createAttachement(image: preview, name: description))
+ debugHistory += "done."
+ }
+ }
+
+ func isAnimated(timeInterval: TimeInterval) {
+ XCTContext.runActivity(named: "Check \"\(viewName)\" animation") { context in
+ debugHistory += "check if element is animated -> "
+ if (takeScreenshot(after: UITestsScreenshots.screenshotDelay)?.pngData() == takeScreenshot(after: timeInterval)?.pngData()) {
+ XCTFail("no animation detected for \"\(viewName)\"")
+ }
+ debugHistory += "done."
+ }
+ }
+
+ func takeScreenshot(after time: TimeInterval) -> UIImage? {
+ XCTContext.runActivity(named: "take screenshot") { context in
+ debugHistory += "take screenshot -> "
+ _=XCTWaiter.wait(for: [XCTestExpectation()], timeout: time)
+ return UIImage(data: element.screenshot().pngRepresentation)
+ }
+ }
+
+ func getReference() -> UIImage? {
+ debugHistory += "get reference -> "
+ guard svgManager.parse(withVariations: variations), referenceImagesExist(names: [viewName]+variations, expectedValue: true) == true else {return nil}
+ let filePaths = [getPath(name: viewName)]+variations.map{getPath(name: $0)}
+ let imagesName = [viewName] + variations.map{viewName+"_"+$0}
+ var images = [UIImage]()
+ for i in 0...imagesName.count-1 {
+ let image = getImage(path: filePaths[i])!
+ let area = svgManager.images[imagesName[i]]!.area
+ var clip = [CGRect]()
+ if (i>0) {clip = svgManager.rects[variations[i-1]]!}
+ images.append(UITestsScreenshots.imageInScreenAcrea(image: image, area: area, clip: clip))
+
+ }
+
+ UIGraphicsBeginImageContextWithOptions(UITestsScreenshots.screenSize, false, 1)
+ _ = images.map{$0.draw(at: CGPoint(x: 0,y: 0))}
+ let reference = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ return imageWithMask(image: reference, mask: svgManager.rects["mask"]!)
+ }
+
+ //create attachement to return when calling public functions
+
+ static func createAttachement(image: UIImage, name: String, lifetime: XCTAttachment.Lifetime = .deleteOnSuccess) -> XCTAttachment {
+ let attachment = XCTAttachment(image: image)
+ attachment.lifetime = lifetime
+ attachment.name = name
+ return attachment
+ }
+
+ //image operations to prepare comparison
+
+ static func imageInScreenAcrea(image: UIImage, area: CGRect, clip: [CGRect] = []) -> UIImage {
+ UIGraphicsBeginImageContextWithOptions(UITestsScreenshots.screenSize, false, 1)
+ if (!clip.isEmpty) {UIGraphicsGetCurrentContext()!.clip(to: clip)}
+ image.draw(at: CGPoint(x: area.minX, y: area.minY))
+ let screenImage = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ return screenImage
+ }
+
+ static func setBackground(image: UIImage, color: UIColor) -> UIImage {
+ let size = UITestsScreenshots.screenSize
+ UIGraphicsBeginImageContextWithOptions(size, false, 1)
+ color.setFill()
+ UIRectFill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
+ image.draw(at: CGPoint(x: 0,y: 0))
+ let newImage = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ return newImage
+ }
+
+ internal func imageWithMask(image: UIImage, mask: [CGRect]) -> UIImage {
+ UIGraphicsBeginImageContextWithOptions(UITestsScreenshots.screenSize, false, 1)
+ image.draw(at: CGPoint(x: 0, y: 0))
+ _ = mask.map{UIRectFill($0)}
+ let maskedImage = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ return maskedImage
+ }
+
+ func convertForComparaison(screenshot: UIImage) -> UIImage {
+ var area = getElementArea()
+ if (svgManager.rects["mask"] == nil) {_=svgManager.parse()}
+ var image = UITestsScreenshots.imageInScreenAcrea(image: screenshot, area: area)
+ image = imageWithMask(image: image, mask: svgManager.rects["mask"]!)
+ return image
+ }
+
+ func getElementArea() -> CGRect {
+ XCTContext.runActivity(named: "get element coordinates") { _ in
+ let area = element.frame
+ let rect = CGRect(x: area.minX*3, y: area.minY*3, width: area.width*3, height: area.height*3)
+ return rect
+ }
+ }
+
+ //comparison functions
+ static func getVarianceAreas(_ image1: UIImage, _ image2: UIImage) -> (image: UIImage, areas: [CGRect])? {
+
+ let margin: CGFloat = 20
+ let replacementColor: UInt8 = 255
+ var areas = [CGRect]()
+
+ //compare images
+ UIGraphicsBeginImageContextWithOptions(image1.size, false, 1)
+ setBackground(image: image1, color: UIColor.black).draw(at: CGPoint(x: 0, y: 0))
+ setBackground(image: image2, color: UIColor.black).draw(at: CGPoint(x: 0, y: 0), blendMode: .difference, alpha: 1)
+ let image = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+
+ //findRects
+ let exeptMsg = "error! : unexpected error during image conversion for comparison"
+ guard let inputCGImage = image.cgImage else {
+ XCTFail(exeptMsg)
+ return nil
+ }
+
+ let colorSpace = CGColorSpaceCreateDeviceGray()
+ let width = inputCGImage.width
+ let height = inputCGImage.height
+ let bytesPerPixel = 1
+ let bitsPerComponent = 8
+ let bytesPerRow = bytesPerPixel * width
+ let bitmapInfo = CGImageAlphaInfo.none.rawValue
+
+ guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
+ XCTFail(exeptMsg)
+ return nil
+ }
+ context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
+
+ guard let buffer = context.data else {
+ XCTFail(exeptMsg)
+ return nil
+ }
+
+ let pixelBuffer = buffer.bindMemory(to: UInt8.self, capacity: width * height)
+
+ var rects: [Int:[CGRect]] = [:]
+
+ for row in 0 ..< Int(height) {
+ for column in 0 ..< Int(width) {
+ let offset = row * width + column
+ if (pixelBuffer[offset] > UInt8(colorTreshold)) {
+ pixelBuffer[offset] = replacementColor
+ let point = CGPoint(x: column, y: row)
+
+ var rect = CGRect(x: point.x, y: point.y, width: 1, height: 1)
+ for i in 0...1 {
+ if let prevRects = rects[row-i] {
+ for j in 0..= CGFloat(pixelTreshold) && rect.height >= CGFloat(pixelTreshold)) {
+ areas.append(rect)
+ }
+ mergeCloseAreas(&areas, withMargin: margin)
+
+ }
+ }
+ }
+
+ let diff = UIImage(cgImage: (CGContext(data: pixelBuffer, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo)?.makeImage())!)
+ return (diff,areas)
+ }
+
+ private static func mergeCloseAreas(_ areas: inout [CGRect], withMargin margin: CGFloat) {
+ guard (areas.count >= 1) else {return}
+ let areaSize = areas.count
+ for i in 1...areaSize {
+ if (i != 1 && areas.last!.intersects(areas[areaSize-i].insetBy(dx: -margin, dy: -margin))) {
+ areas[areas.count-1] = areas.last!.union(areas[areaSize-i])
+ areas.remove(at: areaSize-i)
+ mergeCloseAreas(&areas, withMargin: margin)
+ break
+ }
+ }
+ }
+
+ //preview functions
+
+ static func comparativePreview(reference: UIImage, screenshot: UIImage, difference: UIImage, areas: [CGRect]) -> UIImage {
+ let realPreview = createPreview(title: "Real", image: screenshot, areas: areas, strokeColor: UIColor.red)
+ let refPreview = createPreview(title: "Reference", image: reference, areas: areas, strokeColor: UIColor.red)
+ let difPreview = createPreview(title: "Difference", image: difference)
+ return createPreviewTable(images: refPreview,realPreview,difPreview)
+ }
+
+ static func variationPreview(reference: UIImage, areas: [CGRect]) -> UIImage {
+ let variationPreview = createPreview(title: "Reference", image: reference, areas: areas, fillColor: UIColor.blue.withAlphaComponent(0.3),strokeColor: UIColor.blue)
+ return variationPreview
+ }
+
+ static func createPreviewTable(images: UIImage...) -> UIImage {
+ let sideMargin: CGFloat = 6
+ let imageSize = images.first!.size
+ let globalSize = CGSize(width: (imageSize.width+sideMargin)*CGFloat(images.count), height: imageSize.height+sideMargin)
+ UIGraphicsBeginImageContextWithOptions(globalSize, false, 1)
+
+ for i in 0...images.count-1 {
+ images[i].draw(at: CGPoint(x: CGFloat(i)*(imageSize.width+sideMargin), y: sideMargin))
+ }
+
+ let tableImage = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ return tableImage
+ }
+
+ static func createPreview(title: String, image: UIImage, areas: [CGRect] = [], fillColor: UIColor? = nil, strokeColor: UIColor? = nil) -> UIImage {
+ let lineWidth: CGFloat = 3
+ let bottomMargin = image.size.height*0.05
+ let allSize = CGSize(width: image.size.width + lineWidth*2, height: image.size.height + bottomMargin + lineWidth*2)
+
+ UIGraphicsBeginImageContextWithOptions(allSize, false, 1)
+
+ //image
+ image.draw(at: CGPoint(x: lineWidth, y: lineWidth), blendMode: .normal, alpha: 1)
+
+ //rects
+ UIGraphicsGetCurrentContext()?.setLineWidth(lineWidth)
+ for area in areas {
+ let newArea = area.offsetBy(dx: lineWidth, dy: lineWidth)
+ if (fillColor != nil) {
+ fillColor?.setFill()
+ UIRectFillUsingBlendMode(newArea,.normal)
+ }
+ if (strokeColor != nil) {
+ strokeColor?.setStroke()
+ UIRectFrameUsingBlendMode(newArea.insetBy(dx: -3, dy: -3),.normal)
+ }
+ }
+
+ //title
+ let textFont = UIFont(name: "Helvetica Bold", size: bottomMargin/2) ?? UIFont()
+ let textStyle=NSMutableParagraphStyle()
+ textStyle.alignment=NSTextAlignment.center
+ let textColor = UIColor.black
+ let textAttributes = [NSAttributedString.Key.font: textFont, NSAttributedString.Key.paragraphStyle: textStyle, NSAttributedString.Key.foregroundColor: textColor] as [NSAttributedString.Key : Any]
+ let text_h = textFont.lineHeight
+ let text_y = allSize.height-bottomMargin + (bottomMargin-text_h)/2
+ let text_rect = CGRect(x: 0, y: text_y, width: allSize.width, height: bottomMargin)
+ title.draw(in: text_rect, withAttributes: textAttributes)
+
+ let preview = UIGraphicsGetImageFromCurrentImageContext()!
+ UIGraphicsEndImageContext()
+ return preview
+ }
+
+ //util functions
+
+ private func referenceExist(expectedValue: Bool) -> Bool {
+ guard (FileManager.default.fileExists(atPath: svgManager.path) == expectedValue) else {
+ XCTFail(debugHistory + "error! : \(svgManager.path+(expectedValue ? " does not" : " already")) exist")
+ return false
+ }
+ return true
+ }
+
+ private func referenceImagesExist(names: [String], expectedValue: Bool) -> Bool {
+ guard svgManager.parse() else {return false}
+ for name in names {
+ let realName = viewName + (name != viewName ? "_"+name : "")
+ if ((svgManager.images[realName] == nil) == expectedValue) {
+ XCTFail(debugHistory + "error! : \(realName) image \(expectedValue ? "does not" : "already") exist")
+ return false
+ }
+ }
+ return true
+ }
+
+ private func checkVariationNonDefinition(caller: String = #function) -> Bool {
+ guard variations.isEmpty else {
+ XCTFail(debugHistory + "error : \"\(caller)\" function works only for views, not for views variations")
+ return false
+ }
+ return true
+ }
+
+ //disk operation functions
+
+ private func getPath(name: String) -> String{
+ return "\(UITestsScreenshots.defaultPath)images/\(viewName+((name != viewName) ? "_"+name : "")).png"
+ }
+
+ private func saveImage(image: UIImage, path: String) {
+ guard let data = image.pngData() else {
+ XCTFail("error != unable to save image at \(path)")
+ return
+ }
+ do {
+ try data.write(to: URL(fileURLWithPath: path))
+ } catch {
+ NSLog(error.localizedDescription)
+ XCTFail(debugHistory + "error != unable to save image at \(path)")
+ }
+ }
+
+ private func getImage(path: String) -> UIImage? {
+ guard let image = UIImage(contentsOfFile: path) else {
+ XCTFail(debugHistory + "error != unable to get image at \(path)")
+ return nil
+ }
+ return image
+ }
+
+}
+
+
+class SVGManager : NSObject {
+ var path: String
+ var parentPath: String
+ var rects: [String : [CGRect]] = [:]
+ var images: [String : (area: CGRect, line: Int)] = [:]
+ private var parser: XMLParser?
+ private var current: String?
+
+ private var defaultMask = [CGRect(x: 376, y: 2492, width: 418, height: 16)]
+
+ private var referenceStart: Int?
+ private var maskStart: Int?
+ private var clipPathStart: Int?
+ private var variationsStart: Int?
+ private var selectedVariationStarts: [String : Int]?
+
+ init(path: String) {
+ self.path = path
+ let index = path.lastIndex(of: "/")
+ self.parentPath = (index != nil) ? String(path.prefix(path.distance(from: path.startIndex, to: index!)+1)) : ""
+
+ }
+
+ func svgRect(_ rect: CGRect, lock: Bool) -> String {
+ return ""
+ }
+
+ func svgImage(name: String, area: CGRect, lock: Bool) -> String {
+ return ""
+ }
+
+ func createFile(referenceName name: String, referenceArea area: CGRect, svgSize: CGSize) {
+ let width = svgSize.width
+ let height = svgSize.height
+ var content = [String]()
+ content.append("")
+ let svg = content.joined(separator: "\n")
+ try! svg.write(toFile: path, atomically: true, encoding: String.Encoding.utf8)
+ }
+
+ func updateImage(name: String, area: CGRect) -> Bool {
+ guard parse(), images[name] != nil else {return false}
+
+ var svgData = try! String(contentsOf: URL(fileURLWithPath: path)).split(separator: "\n")
+ var lineIndex = images[name]!.line-1
+ var line = svgData[lineIndex]
+ while line.firstIndex(of: "<") == nil {
+ svgData.remove(at: lineIndex)
+ lineIndex += -1
+ line = svgData[lineIndex]
+ }
+ svgData[lineIndex] = line.prefix(line.distance(from: line.startIndex, to: line.firstIndex(of: "<")!)) + Substring(svgImage(name: name, area: area, lock: true))
+ try! String(svgData.joined(separator: "\n")).write(toFile: path, atomically: true, encoding: String.Encoding.utf8)
+ return true
+ }
+
+ func addToMask(rects: [CGRect]) -> Bool {
+ guard parse() else {return false}
+ var content = ""
+ for rect in rects {
+ content += "\(svgRect(rect, lock: false))\n"
+ }
+ svgInsert(content, at: maskStart!)
+ return true
+ }
+
+ func addVariation(referenceName: String, name: String, area: CGRect, rects: [CGRect]) -> Bool {
+ guard parse() else {return false}
+
+ var content = "\n"
+ content += " \(svgImage(name: "\(referenceName)_\(name)", area: area, lock: true))\n"
+ for rect in rects {
+ content += " \(svgRect(rect, lock: false))\n"
+ }
+ content += "\n"
+ svgInsert(content, at: variationsStart!)
+
+ content = "