diff --git a/Classes/AudioHelper.m b/Classes/AudioHelper.m
deleted file mode 100644
index 7298e65b3..000000000
--- a/Classes/AudioHelper.m
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "AudioHelper.h"
-
-@implementation AudioHelper
-
-+ (NSArray *)bluetoothRoutes {
- return @[AVAudioSessionPortBluetoothHFP, AVAudioSessionPortCarAudio, AVAudioSessionPortBluetoothA2DP, AVAudioSessionPortBluetoothLE ];
-}
-
-+ (AVAudioSessionPortDescription *)bluetoothAudioDevice {
- return [AudioHelper audioDeviceFromTypes:[AudioHelper bluetoothRoutes]];
-}
-
-+ (AVAudioSessionPortDescription *)builtinAudioDevice {
- NSArray *builtinRoutes = @[ AVAudioSessionPortBuiltInMic ];
- return [AudioHelper audioDeviceFromTypes:builtinRoutes];
-}
-
-+ (AVAudioSessionPortDescription *)speakerAudioDevice {
- NSArray *builtinRoutes = @[ AVAudioSessionPortBuiltInSpeaker ];
- return [AudioHelper audioDeviceFromTypes:builtinRoutes];
-}
-
-+ (AVAudioSessionPortDescription *)audioDeviceFromTypes:(NSArray *)types {
- NSArray *routes = [[AVAudioSession sharedInstance] availableInputs];
- for (AVAudioSessionPortDescription *route in routes) {
- if ([types containsObject:route.portType]) {
- return route;
- }
- }
- return nil;
-}
-
-@end
diff --git a/Classes/Base.lproj/CallIncomingView.xib b/Classes/Base.lproj/CallIncomingView.xib
deleted file mode 100644
index ae5bd9336..000000000
--- a/Classes/Base.lproj/CallIncomingView.xib
+++ /dev/null
@@ -1,317 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/Base.lproj/CallOutgoingView.xib b/Classes/Base.lproj/CallOutgoingView.xib
deleted file mode 100644
index 890553e00..000000000
--- a/Classes/Base.lproj/CallOutgoingView.xib
+++ /dev/null
@@ -1,623 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/Base.lproj/CallView.xib b/Classes/Base.lproj/CallView.xib
deleted file mode 100644
index 57a17a2c7..000000000
--- a/Classes/Base.lproj/CallView.xib
+++ /dev/null
@@ -1,1857 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T
-S2V5ZWRBcmNoaXZlctEICVRyb290gAGvEBELDBkaHxQkKSoxNDdBSUpOUVUkbnVsbNYNDg8QERITFBUW
-FxhWTlNTaXplXk5TUmVzaXppbmdNb2RlViRjbGFzc1xOU0ltYWdlRmxhZ3NWTlNSZXBzV05TQ29sb3KA
-AhAAgBASIMAAAIADgAtYezMzLCAzM33SGw8cHlpOUy5vYmplY3RzoR2ABIAK0hsPICOiISKABYAGgAnT
-DyUmJygUXxAUTlNUSUZGUmVwcmVzZW50YXRpb25fEBlOU0ludGVybmFsTGF5b3V0RGlyZWN0aW9ugAiA
-B08RGbpNTQAqAAARDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAABAwMDFQYGBiYGBgYiAgICDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAIGBgYhCQkJNgkJCTEJCQkzCQkJMwMDAxQAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAxUKCgo2BQUFIAEBAQgCAgIMCAgIKwkJCTEBAQEHAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAEAwMDFAAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAYGBiMICAgwAAAABQAAAAAAAAAAAgIC
-EQoKCjYCAgISAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAACAgIOCgoKNQcHBy4DAwMSAAAAAQAAAAAAAAAAAAAAAAYGBiEICAgxAQEB
-BwAAAAAAAAAAAwMDFAoKCjYCAgIQAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMPCQkJMgkJCTUJCQk0BAQEGQAAAAAAAAAAAAAA
-AAUFBRsKCgo3BQUFHwAAAAAAAAAEBwcHLAkJCTMCAgIMAAAAAAAAAAABAQEHCAgIKwkJCS0GBgYiAgIC
-EQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQUFBRoJCQkyCQkJNAkJCTMKCgo1BAQE
-FwAAAAAAAAAABAQEGQkJCTMICAgxAwMDFwAAAAAAAAADBQUFIgkJCTQHBwcuAgICDQAAAAAAAAAFBQUF
-IQcHBykJCQkyCgoKNgcHBygBAQEKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBgYGHgoKCjYHBwcnBwcH
-KAoKCjYHBwcnAAAAAgAAAAADAwMUCgoKNgcHBycBAQEJAAAAAAAAAAAAAAAAAAAAAQICAhIJCQkwCAgI
-LwEBAQcAAAAAAAAAAAAAAAEAAAAGBQUFGwkJCTMICAgvAgICCgAAAAAAAAAAAAAAAAAAAAADAwMWCgoK
-NgYGBiEAAAACAwMDEwgICC4CAgIKAAAAAAAAAAIHBwcsBwcHLAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAACAgIQCgoKNAUFBR0AAAAAAAAAAAAAAAAAAAAAAAAAAAMDAw8JCQkyCAgIKgAAAAIAAAAAAAAA
-AAAAAAQHBwcuBwcHLAAAAAQAAAAAAAAAAAAAAAIAAAAAAAAAAAICAgkJCQk0BQUFHAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAABCQkJLQcHBygAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQaCgoK
-NQUFBSEFBQUfAwMDEgMDAxIKCgo2BAQEFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQgJCQkzCAgI
-LwUFBSIFBQUaAwMDFQMDAxQDAwMUBAQEFgUFBRwHBwclCQkJNQcHBycAAAAAAAAAAAAAAAAAAAAAAAAA
-AQUFBR8ICAgwCQkJMwkJCTQKCgo3BQUFHwYGBiEJCQk0AQEBCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAEEBAQYBwcHKQgICDAJCQkzCQkJNQkJCTUJCQk1CQkJNAkJCTMICAgvBgYGJAICAg4AAAAAAAAA
-AAAAAAAAAAAAAAAAAQUFBR8JCQk0CQkJNAkJCTQHBwcqAAAABAYGBiUICAgvAAAABgAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUCAgILAwMDDwICAhEDAwMPAwMDDgEBAQkAAAADAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMTCQkJLQkJCTIDAwMOAAAAAAEBAQcCAgIMAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAAAAQEBBgICAgwAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBBwUFBSAHBwcsCAgIKwUFBRwAAAADAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAgoGBgYjBwcHLAcHByoEBAQYAAAAAQAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCAgIKgoKCjUICAgoCAgIKwkJCTYGBgYjAAAA
-AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBCQgICC8JCQkzBwcHKAcHBywKCgo2BQUF
-HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQXCgoKNgQEBBcAAAAAAAAA
-AgUFBR8JCQk1AwMDDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQUFHwkJCTQCAgIRAAAA
-AAAAAAMGBgYmCQkJMgEBAQkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQUgCQkJ
-MgAAAAQAAAAAAAAAAAICAgwJCQk1BAQEFwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwcH
-KQgICCsAAAABAAAAAAAAAAADAwMUCQkJNQICAhAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAEBAQYCQkJNQICAhAAAAAAAAAAAAQEBBgJCQk1AgICEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAABQUFIQkJCTEBAQEKAAAAAAAAAAEFBQUgCQkJMwEBAQoAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAMGBgYeCgoKNwYGBiYAAAAAAAAAAggICC4JCQk1BAQEGAAAAAEAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAFBgYGJQoKCjgFBQUfAAAAAAEBAQgJCQkyCAgIMgICAhEAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwYGBiMJCQk2BwcHJwICAg0AAAAAAAAAAAMDAxIICAgrCQkJ
-NgUFBR0AAAABAAAAAAAAAAAAAAAAAAAAAAAAAAYHBwcqCQkJNQYGBiMBAQEKAAAAAAAAAAEDAwMXCAgI
-LgkJCTQEBAQWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQUFGgoKCjYFBQUdAAAAAQAAAAAAAAAAAAAA
-AAAAAAAAAAAEBgYGJAoKCjUCAgIRAAAAAAAAAAAAAAAAAAAAAAYGBiEKCgo1BAQEFwAAAAAAAAAAAAAA
-AAAAAAAAAAAAAQEBCAgICCoJCQkzAgICCwAAAAAAAAAAAAAAAAAAAAAAAAAECQkJLQcHBykAAAABAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQgICC8HBwcnAAAAAAAAAAAAAAAAAQEBCAkJCTIFBQUiAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQoJCQk0BQUFHgAAAAAAAAAAAAAAAAAAAAABAQEICQkJ
-MQYGBiMAAAAEAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAQEBBggICCoHBwctAAAAAgAAAAAAAAAAAgIC
-DQoKCjUFBQUcAAAAAwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgEBAQoICAgvBgYGJgAAAAAAAAAAAAAA
-AAAAAAAAAAAEBwcHLAkJCTUICAgxCAgIKQYGBiQGBgYjBgYGIwYGBiQICAgqCQkJMgkJCTUHBwcmAAAA
-AAAAAAAAAAAAAQEBCQgICDAJCQk1CAgILwcHBygGBgYjBgYGIwYGBiMGBgYlBwcHLAkJCTMJCQk2BgYG
-HgAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBCQMDAxYFBQUiCAgIKggICDAICAgxCAgIMAgICC8HBwcpBQUF
-IAMDAxQBAQEGAAAAAAAAAAAAAAAAAAAAAAICAgwEBAQYBgYGIwgICCsICAgwCAgIMAgICDAICAguBwcH
-KAUFBR4DAwMSAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAIAAAADAAAA
-AgAAAAIAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAACAAAA
-AwAAAAIAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAEBAQkDAwMPAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFBQUfBwcHLQkJCTIJCQk1AwMDDwAAAAAAAAAAAAAA
-AAAAAAAEBAQXBQUFHwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMICAguCQkJNAkJCTQHBwctAQEB
-CAAAAAIAAAAFAgICDQYGBiQKCgo2BwcHJwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGBgYlCQkJ
-NQkJCTQJCQk0CQkJLQgICCoICAguCQkJNQkJCTMEBAQfAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAFBQUcCgoKNgUFBR8FBQUaBwcHJgcHBykHBwcmBQUFGwICAgoAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAABAQELBgYGIQAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAQAAAwAAAAEA
-IQAAAQEAAwAAAAEAIQAAAQIAAwAAAAQAABHSAQMAAwAAAAEAAQAAAQYAAwAAAAEAAgAAAQoAAwAAAAEA
-AQAAAREABAAAAAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEABAAAARYAAwAAAAEAIQAAARcABAAAAAEA
-ABEEARwAAwAAAAEAAQAAASgAAwAAAAEAAgAAAVIAAwAAAAEAAQAAAVMAAwAAAAQAABHah3MABwAAB9gA
-ABHiAAAAAAAIAAgACAAIAAEAAQABAAEAAAfYYXBwbAIgAABtbnRyUkdCIFhZWiAH2QACABkACwAaAAth
-Y3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLWFwcGwAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtkZXNjAAABCAAAAG9kc2NtAAABeAAABZxj
-cHJ0AAAHFAAAADh3dHB0AAAHTAAAABRyWFlaAAAHYAAAABRnWFlaAAAHdAAAABRiWFlaAAAHiAAAABRy
-VFJDAAAHnAAAAA5jaGFkAAAHrAAAACxiVFJDAAAHnAAAAA5nVFJDAAAHnAAAAA5kZXNjAAAAAAAAABRH
-ZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJpYyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbWx1YwAAAAAAAAAfAAAADHNrU0sA
-AAAoAAABhGRhREsAAAAuAAABrGNhRVMAAAAkAAAB2nZpVk4AAAAkAAAB/nB0QlIAAAAmAAACInVrVUEA
-AAAqAAACSGZyRlUAAAAoAAACcmh1SFUAAAAoAAACmnpoVFcAAAAWAAACwm5iTk8AAAAmAAAC2GNzQ1oA
-AAAiAAAC/mhlSUwAAAAeAAADIGl0SVQAAAAoAAADPnJvUk8AAAAkAAADZmRlREUAAAAsAAADimtvS1IA
-AAAWAAADtnN2U0UAAAAmAAAC2HpoQ04AAAAWAAADzGphSlAAAAAaAAAD4mVsR1IAAAAiAAAD/HB0UE8A
-AAAmAAAEHm5sTkwAAAAoAAAERGVzRVMAAAAmAAAEHnRoVEgAAAAkAAAEbHRyVFIAAAAiAAAEkGZpRkkA
-AAAoAAAEsmhySFIAAAAoAAAE2nBsUEwAAAAsAAAFAnJ1UlUAAAAiAAAFLmFyRUcAAAAmAAAFUGVuVVMA
-AAAmAAAFdgBWAWEAZQBvAGIAZQBjAG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwARwBlAG4AZQByAGUA
-bAAgAFIARwBCAC0AYgBlAHMAawByAGkAdgBlAGwAcwBlAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUA
-bgDoAHIAaQBjAEMepQB1ACAAaADsAG4AaAAgAFIARwBCACAAQwBoAHUAbgBnAFAAZQByAGYAaQBsACAA
-UgBHAEIAIABHAGUAbgDpAHIAaQBjAG8EFwQwBDMEMAQ7BEwEPQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAA
-UgBHAEIAUAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUAZQAgAFIAVgBCAMEAbAB0AGEAbADhAG4A
-bwBzACAAUgBHAEIAIABwAHIAbwBmAGkAbJAadSgAIABSAEcAQgAggnJfaWPPj/AARwBlAG4AZQByAGkA
-cwBrACAAUgBHAEIALQBwAHIAbwBmAGkAbABPAGIAZQBjAG4A/QAgAFIARwBCACAAcAByAG8AZgBpAGwF
-5AXoBdUF5AXZBdwAIABSAEcAQgAgBdsF3AXcBdkAUAByAG8AZgBpAGwAbwAgAFIARwBCACAAZwBlAG4A
-ZQByAGkAYwBvAFAAcgBvAGYAaQBsACAAUgBHAEIAIABnAGUAbgBlAHIAaQBjAEEAbABsAGcAZQBtAGUA
-aQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGzHfLwYACAAUgBHAEIAINUEuFzTDMd8Zm6QGgAgAFIA
-RwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEwpDDrA5MDtQO9A7kDugPMACADwAPBA78D
-xgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAAZwBlAG4A6QByAGkAYwBvAEEAbABnAGUA
-bQBlAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGUAbA5CDhsOIw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEO
-SA4nDkQOGwBHAGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGkAWQBsAGUAaQBuAGUAbgAgAFIA
-RwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0AawBpACAAUgBHAEIAIABwAHIAbwBmAGkA
-bABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAFIARwBCBB4EMQRJBDgEOQAgBD8E
-QAQ+BEQEOAQ7BEwAIABSAEcAQgZFBkQGQQAgBioGOQYxBkoGQQAgAFIARwBCACAGJwZEBjkGJwZFAEcA
-ZQBuAGUAcgBpAGMAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGV0ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFw
-cGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAAAAAAAADzUgABAAAAARbPWFlaIAAAAAAA
-AHRNAAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAAAAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAA
-AAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH///ui///9owAAA9wAAMBs0issLS5aJGNs
-YXNzbmFtZVgkY2xhc3Nlc18QEE5TQml0bWFwSW1hZ2VSZXCjLS8wWk5TSW1hZ2VSZXBYTlNPYmplY3TS
-KywyM1dOU0FycmF5ojIw0issNTZeTlNNdXRhYmxlQXJyYXmjNTIw1Tg5OjsPPD0+P0BXTlNXaGl0ZVxO
-U0NvbXBvbmVudHNcTlNDb2xvclNwYWNlXxASTlNDdXN0b21Db2xvclNwYWNlRDAgMABDMCAwEAOADIAP
-1EJDRA9FRkdIVE5TSURVTlNJQ0NXTlNNb2RlbBAJgA0QAIAOTxERnAAAEZxhcHBsAgAAAG1udHJHUkFZ
-WFlaIAfcAAgAFwAPAC4AD2Fjc3BBUFBMAAAAAG5vbmUAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMt
-YXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABWRlc2MAAADA
-AAAAeWRzY20AAAE8AAAIGmNwcnQAAAlYAAAAI3d0cHQAAAl8AAAAFGtUUkMAAAmQAAAIDGRlc2MAAAAA
-AAAAH0dlbmVyaWMgR3JheSBHYW1tYSAyLjIgUHJvZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVj
-AAAAAAAAAB8AAAAMc2tTSwAAAC4AAAGEZGFESwAAADoAAAGyY2FFUwAAADgAAAHsdmlWTgAAAEAAAAIk
-cHRCUgAAAEoAAAJkdWtVQQAAACwAAAKuZnJGVQAAAD4AAALaaHVIVQAAADQAAAMYemhUVwAAABoAAANM
-a29LUgAAACIAAANmbmJOTwAAADoAAAOIY3NDWgAAACgAAAPCaGVJTAAAACQAAAPqcm9STwAAACoAAAQO
-ZGVERQAAAE4AAAQ4aXRJVAAAAE4AAASGc3ZTRQAAADgAAATUemhDTgAAABoAAAUMamFKUAAAACYAAAUm
-ZWxHUgAAACoAAAVMcHRQTwAAAFIAAAV2bmxOTAAAAEAAAAXIZXNFUwAAAEwAAAYIdGhUSAAAADIAAAZU
-dHJUUgAAACQAAAaGZmlGSQAAAEYAAAaqaHJIUgAAAD4AAAbwcGxQTAAAAEoAAAcuYXJFRwAAACwAAAd4
-cnVSVQAAADoAAAekZW5VUwAAADwAAAfeAFYBYQBlAG8AYgBlAGMAbgDhACAAcwBpAHYA4QAgAGcAYQBt
-AGEAIAAyACwAMgBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QAgADIALAAyACAAZwBhAG0AbQBhAC0AcABy
-AG8AZgBpAGwARwBhAG0AbQBhACAAZABlACAAZwByAGkAcwBvAHMAIABnAGUAbgDoAHIAaQBjAGEAIAAy
-AC4AMgBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBtACAAQwBoAHUAbgBnACAARwBhAG0AbQBh
-ACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIAaQBjAG8AIABkAGEAIABHAGEAbQBhACAAZABl
-ACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsETAQ9BDAAIABHAHIAYQB5AC0EMwQwBDwEMAAg
-ADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1AGUAIABnAHIAaQBzACAAZwBhAG0AbQBh
-ACAAMgAsADIAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA/AByAGsAZQAgAGcAYQBtAG0AYQAgADIALgAy
-kBp1KHBwlo5RSV6mADIALgAygnJfaWPPj/DHfLwYACDWjMDJACCsELnIACAAMgAuADIAINUEuFzTDMd8
-AEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0AbQBhACAAMgAsADIALQBwAHIAbwBmAGkAbABP
-AGIAZQBjAG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAAMgAuADIF0gXQBd4F1AAgBdAF5AXVBegAIAXb
-BdwF3AXZACAAMgAuADIARwBhAG0AYQAgAGcAcgBpACAAZwBlAG4AZQByAGkAYwEDACAAMgAsADIAQQBs
-AGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBmAGUAbgAtAFAAcgBvAGYAaQBsACAARwBh
-AG0AbQBhACAAMgAsADIAUAByAG8AZgBpAGwAbwAgAGcAcgBpAGcAaQBvACAAZwBlAG4AZQByAGkAYwBv
-ACAAZABlAGwAbABhACAAZwBhAG0AbQBhACAAMgAsADIARwBlAG4AZQByAGkAcwBrACAAZwByAOUAIAAy
-ACwAMgAgAGcAYQBtAG0AYQBwAHIAbwBmAGkAbGZukBpwcF6mfPtlcAAyAC4AMmPPj/Blh072TgCCLDCw
-MOwwpDCsMPMw3gAgADIALgAyACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAOTA7oDwQO5ACADkwOs
-A7wDvAOxACAAMgAuADIAUABlAHIAZgBpAGwAIABnAGUAbgDpAHIAaQBjAG8AIABkAGUAIABjAGkAbgB6
-AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBtAG0AYQAgADIALAAyAEEAbABnAGUAbQBlAGUAbgAgAGcAcgBp
-AGoAcwAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBpAGUAbABQAGUAcgBmAGkAbAAgAGcAZQBu
-AOkAcgBpAGMAbwAgAGQAZQAgAGcAYQBtAG0AYQAgAGQAZQAgAGcAcgBpAHMAZQBzACAAMgAsADIOIw4x
-DgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4iDkwOFw4xDkgOJw5EDhsAIAAyAC4AMgBHAGUAbgBlAGwAIABH
-AHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZAGwAZQBpAG4AZQBuACAAaABhAHIAbQBhAGEAbgAgAGcAYQBt
-AG0AYQAgADIALAAyACAALQBwAHIAbwBmAGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABHAHIAYQB5
-ACAARwBhAG0AbQBhACAAMgAuADIAIABwAHIAbwBmAGkAbABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABw
-AHIAbwBmAGkAbAAgAHMAegBhAHIAbwFbAGMAaQAgAGcAYQBtAG0AYQAgADIALAAyBjoGJwZFBicAIAAy
-AC4AMgAgBkQGSAZGACAGMQZFBicGLwZKACAGOQYnBkUEHgQxBEkEMARPACAEQQQ1BEAEMARPACAEMwQw
-BDwEPAQwACAAMgAsADIALQQ/BEAEPgREBDgEOwRMAEcAZQBuAGUAcgBpAGMAIABHAHIAYQB5ACAARwBh
-AG0AbQBhACAAMgAuADIAIABQAHIAbwBmAGkAbABlAAB0ZXh0AAAAAENvcHlyaWdodCBBcHBsZSBJbmMu
-LCAyMDEyAABYWVogAAAAAAAA81EAAQAAAAEWzGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAt
-ADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADB
-AMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1
-AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJx
-AnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6
-A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVY
-BWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdP
-B2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmk
-CboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxc
-DHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96
-D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMD
-EyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6
-Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtj
-G4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBB
-IGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWX
-Jccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitp
-K50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6
-MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiM
-OMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/i
-QCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fA
-SAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAn
-UHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1ka
-WWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKc
-YvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yv
-bQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdW
-d7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKS
-gvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45m
-js6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrV
-m0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfg
-qFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WK
-tgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPU
-xFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB
-00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT
-4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM
-8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf//0issS0xcTlNDb2xv
-clNwYWNlok0wXE5TQ29sb3JTcGFjZdIrLE9QV05TQ29sb3KiTzDSKyxSU1dOU0ltYWdlolIwAAgAEQAa
-ACQAKQAyADcASQBMAFEAUwBnAG0AegCBAJAAlwCkAKsAswC1ALcAuQC+AMAAwgDLANAA2wDdAN8A4QDm
-AOkA6wDtAO8A9gENASkBKwEtGusa8Br7GwQbFxsbGyYbLxs0GzwbPxtEG1MbVxtiG2obdxuEG5kbnhui
-G6QbphuoG7Ebthu8G8QbxhvIG8obzC1sLXEtfi2BLY4tky2bLZ4toy2rAAAAAAAAAgEAAAAAAAAAVAAA
-AAAAAAAAAAAAAAAALa4
-
-
-
-
-
-YnBsaXN0MDDUAQIDBAUGBwpYJHZlcnNpb25ZJGFyY2hpdmVyVCR0b3BYJG9iamVjdHMSAAGGoF8QD05T
-S2V5ZWRBcmNoaXZlctEICVRyb290gAGvEBELDBkaHxQkKSoxNDdBSUpOUVUkbnVsbNYNDg8QERITFBUW
-FxhWTlNTaXplXk5TUmVzaXppbmdNb2RlViRjbGFzc1xOU0ltYWdlRmxhZ3NWTlNSZXBzV05TQ29sb3KA
-AhAAgBASIMAAAIADgAtYezQwLCAzNn3SGw8cHlpOUy5vYmplY3RzoR2ABIAK0hsPICOiISKABYAGgAnT
-DyUmJygUXxAUTlNUSUZGUmVwcmVzZW50YXRpb25fEBlOU0ludGVybmFsTGF5b3V0RGlyZWN0aW9ugAiA
-B08RHzZNTQAqAAAWiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAABhgAABkYAAAZBQAABgAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAOwABPJsABJ7IAAXMyAAFzJ0ABKA/AAFBAQAAAgAAAAAAAAAAAAAAAAAAAAACAgICAQEB
-AQAAAAAKCgoKBAQEBAAAAAAAAAAACQkJCQkJCQkAAAAAAAAAAAAAAAAAAAAAAAAAAAkJCQkHBwcHAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASQACS9gABtz7AAf/+wAH
-//sAB//7AAf/2wAG31AAAlIAAAAAAAAAAAAAAAAAAAAAW1tbW6KioqKZmZmZ29vb22BgYGIICAgIh4eH
-icnJycnJycnKenp6fAcHBwcAAAAAEhISEpOTk5XS0tLTy8vLzHh4eHoHBwcHAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAAF7wABcD7AAf/+wAH//sAB//7AAf/+wAH//sAB//AAAXEGgAA
-GwAAAAAAAAAAAAAAAHBwcHD/////gYGBgygoKCoNDQ0NiIiIiq6urq8XFxcXFxcXGbm5ublycnJyAAAA
-AJiYmJnBwcHBKCgoKC0tLS/T09PUV1dXVwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AEQAAUbrAAbv+wAH//sAB//7AAf/+wAH//sAB//7AAf/7AAG8EoAAkwAAAAAAAAAAAAAAAB0dHR00NDQ
-0AAAAAAAAAAABwcHB9TU1NWRkZGTY2NjY2NjY2Ojo6OjyMjIyBYWFhbd3d3dOzs7PQAAAAAAAAAAFxcX
-GRUVFRUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSAAJU8gAG9vsAB//7AAf/+wAH
-//sAB//7AAf/+wAH//QABvhcAAJeAAAAAAAAAAAAAAAAdnZ2dre3t7cAAAAAAAAAAA4ODg7g4ODhoaGh
-oYyMjIyMjIyMhoaGhlJSUlQcHBwc4eHh4ioqKioAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAOAABOeQABuj7AAf/+wAH//sAB//7AAf/+wAH//sAB//mAAbqPgAB
-PwAAAAAAAAAAAAAAAHZ2dna4uLi4AAAAAAAAAAAEBAQEwsLCw2JiYmIAAAAAAAAAADMzMzNeXl5eCQkJ
-Cc3Nzc5paWlpAAAAAAAAAABxcXFzWVlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAsAAAyiAASl+wAH//sAB//7AAf/+wAH//sAB//7AAf/pgAEqQ8AABAAAAAAAAAAAAAAAAB4eHh4vLy8
-vAAAAAAAAAAAAAAAAEhISEjX19fYmZmZmouLi43a2trbYmJiZAAAAABRUVFT39/f35ubm5yenp6f3Nzc
-3Tk5OTkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgABK7QABbf1AAb5+wAH
-//sAB//2AAb6tgAFuSwAAS0AAAAAAAAAAAAAAAAAAAAAKioqKkFBQUMAAAAAAAAAAAAAAAAAAAAAJiYm
-KG5ubnBxcXFzMjIyNAAAAAAAAAAAAAAAADAwMDB0dHR0bGxsbiMjIyMAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZAAAaYwACZZYABJmVAASYZQACZxwAAB0AAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAFAAAGBAAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAQAQAAAwAAAAEAKAAAAQEAAwAAAAEAJAAAAQIAAwAAAAQAABdOAQMAAwAAAAEA
-AQAAAQYAAwAAAAEAAgAAAQoAAwAAAAEAAQAAAREABAAAAAEAAAAIARIAAwAAAAEAAQAAARUAAwAAAAEA
-BAAAARYAAwAAAAEAJAAAARcABAAAAAEAABaAARwAAwAAAAEAAQAAASgAAwAAAAEAAgAAAVIAAwAAAAEA
-AQAAAVMAAwAAAAQAABdWh3MABwAAB9gAABdeAAAAAAAIAAgACAAIAAEAAQABAAEAAAfYYXBwbAIgAABt
-bnRyUkdCIFhZWiAH2QACABkACwAaAAthY3NwQVBQTAAAAABhcHBsAAAAAAAAAAAAAAAAAAAAAAAA9tYA
-AQAAAADTLWFwcGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtk
-ZXNjAAABCAAAAG9kc2NtAAABeAAABZxjcHJ0AAAHFAAAADh3dHB0AAAHTAAAABRyWFlaAAAHYAAAABRn
-WFlaAAAHdAAAABRiWFlaAAAHiAAAABRyVFJDAAAHnAAAAA5jaGFkAAAHrAAAACxiVFJDAAAHnAAAAA5n
-VFJDAAAHnAAAAA5kZXNjAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAUR2VuZXJp
-YyBSR0IgUHJvZmlsZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAbWx1YwAAAAAAAAAfAAAADHNrU0sAAAAoAAABhGRhREsAAAAuAAABrGNhRVMAAAAkAAAB2nZpVk4A
-AAAkAAAB/nB0QlIAAAAmAAACInVrVUEAAAAqAAACSGZyRlUAAAAoAAACcmh1SFUAAAAoAAACmnpoVFcA
-AAAWAAACwm5iTk8AAAAmAAAC2GNzQ1oAAAAiAAAC/mhlSUwAAAAeAAADIGl0SVQAAAAoAAADPnJvUk8A
-AAAkAAADZmRlREUAAAAsAAADimtvS1IAAAAWAAADtnN2U0UAAAAmAAAC2HpoQ04AAAAWAAADzGphSlAA
-AAAaAAAD4mVsR1IAAAAiAAAD/HB0UE8AAAAmAAAEHm5sTkwAAAAoAAAERGVzRVMAAAAmAAAEHnRoVEgA
-AAAkAAAEbHRyVFIAAAAiAAAEkGZpRkkAAAAoAAAEsmhySFIAAAAoAAAE2nBsUEwAAAAsAAAFAnJ1UlUA
-AAAiAAAFLmFyRUcAAAAmAAAFUGVuVVMAAAAmAAAFdgBWAWEAZQBvAGIAZQBjAG4A/QAgAFIARwBCACAA
-cAByAG8AZgBpAGwARwBlAG4AZQByAGUAbAAgAFIARwBCAC0AYgBlAHMAawByAGkAdgBlAGwAcwBlAFAA
-ZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDoAHIAaQBjAEMepQB1ACAAaADsAG4AaAAgAFIARwBCACAA
-QwBoAHUAbgBnAFAAZQByAGYAaQBsACAAUgBHAEIAIABHAGUAbgDpAHIAaQBjAG8EFwQwBDMEMAQ7BEwE
-PQQ4BDkAIAQ/BEAEPgREBDAEOQQ7ACAAUgBHAEIAUAByAG8AZgBpAGwAIABnAOkAbgDpAHIAaQBxAHUA
-ZQAgAFIAVgBCAMEAbAB0AGEAbADhAG4AbwBzACAAUgBHAEIAIABwAHIAbwBmAGkAbJAadSgAIABSAEcA
-QgAggnJfaWPPj/AARwBlAG4AZQByAGkAcwBrACAAUgBHAEIALQBwAHIAbwBmAGkAbABPAGIAZQBjAG4A
-/QAgAFIARwBCACAAcAByAG8AZgBpAGwF5AXoBdUF5AXZBdwAIABSAEcAQgAgBdsF3AXcBdkAUAByAG8A
-ZgBpAGwAbwAgAFIARwBCACAAZwBlAG4AZQByAGkAYwBvAFAAcgBvAGYAaQBsACAAUgBHAEIAIABnAGUA
-bgBlAHIAaQBjAEEAbABsAGcAZQBtAGUAaQBuAGUAcwAgAFIARwBCAC0AUAByAG8AZgBpAGzHfLwYACAA
-UgBHAEIAINUEuFzTDMd8Zm6QGgAgAFIARwBCACBjz4/wZYdO9k4AgiwAIABSAEcAQgAgMNcw7TDVMKEw
-pDDrA5MDtQO9A7kDugPMACADwAPBA78DxgOvA7sAIABSAEcAQgBQAGUAcgBmAGkAbAAgAFIARwBCACAA
-ZwBlAG4A6QByAGkAYwBvAEEAbABnAGUAbQBlAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGUAbA5CDhsO
-Iw5EDh8OJQ5MACAAUgBHAEIAIA4XDjEOSA4nDkQOGwBHAGUAbgBlAGwAIABSAEcAQgAgAFAAcgBvAGYA
-aQBsAGkAWQBsAGUAaQBuAGUAbgAgAFIARwBCAC0AcAByAG8AZgBpAGkAbABpAEcAZQBuAGUAcgBpAQ0A
-awBpACAAUgBHAEIAIABwAHIAbwBmAGkAbABVAG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkA
-bAAgAFIARwBCBB4EMQRJBDgEOQAgBD8EQAQ+BEQEOAQ7BEwAIABSAEcAQgZFBkQGQQAgBioGOQYxBkoG
-QQAgAFIARwBCACAGJwZEBjkGJwZFAEcAZQBuAGUAcgBpAGMAIABSAEcAQgAgAFAAcgBvAGYAaQBsAGV0
-ZXh0AAAAAENvcHlyaWdodCAyMDA3IEFwcGxlIEluYy4sIGFsbCByaWdodHMgcmVzZXJ2ZWQuAFhZWiAA
-AAAAAADzUgABAAAAARbPWFlaIAAAAAAAAHRNAAA97gAAA9BYWVogAAAAAAAAWnUAAKxzAAAXNFhZWiAA
-AAAAAAAoGgAAFZ8AALg2Y3VydgAAAAAAAAABAc0AAHNmMzIAAAAAAAEMQgAABd7///MmAAAHkgAA/ZH/
-//ui///9owAAA9wAAMBs0issLS5aJGNsYXNzbmFtZVgkY2xhc3Nlc18QEE5TQml0bWFwSW1hZ2VSZXCj
-LS8wWk5TSW1hZ2VSZXBYTlNPYmplY3TSKywyM1dOU0FycmF5ojIw0issNTZeTlNNdXRhYmxlQXJyYXmj
-NTIw1Tg5OjsPPD0+P0BXTlNXaGl0ZVxOU0NvbXBvbmVudHNcTlNDb2xvclNwYWNlXxASTlNDdXN0b21D
-b2xvclNwYWNlRDAgMABDMCAwEAOADIAP1EJDRA9FRkdIVE5TSURVTlNJQ0NXTlNNb2RlbBAJgA0QAIAO
-TxERnAAAEZxhcHBsAgAAAG1udHJHUkFZWFlaIAfcAAgAFwAPAC4AD2Fjc3BBUFBMAAAAAG5vbmUAAAAA
-AAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAABWRlc2MAAADAAAAAeWRzY20AAAE8AAAIGmNwcnQAAAlYAAAAI3d0cHQAAAl8
-AAAAFGtUUkMAAAmQAAAIDGRlc2MAAAAAAAAAH0dlbmVyaWMgR3JheSBHYW1tYSAyLjIgUHJvZmlsZQAA
-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-AAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAAC4AAAGEZGFESwAAADoAAAGy
-Y2FFUwAAADgAAAHsdmlWTgAAAEAAAAIkcHRCUgAAAEoAAAJkdWtVQQAAACwAAAKuZnJGVQAAAD4AAALa
-aHVIVQAAADQAAAMYemhUVwAAABoAAANMa29LUgAAACIAAANmbmJOTwAAADoAAAOIY3NDWgAAACgAAAPC
-aGVJTAAAACQAAAPqcm9STwAAACoAAAQOZGVERQAAAE4AAAQ4aXRJVAAAAE4AAASGc3ZTRQAAADgAAATU
-emhDTgAAABoAAAUMamFKUAAAACYAAAUmZWxHUgAAACoAAAVMcHRQTwAAAFIAAAV2bmxOTAAAAEAAAAXI
-ZXNFUwAAAEwAAAYIdGhUSAAAADIAAAZUdHJUUgAAACQAAAaGZmlGSQAAAEYAAAaqaHJIUgAAAD4AAAbw
-cGxQTAAAAEoAAAcuYXJFRwAAACwAAAd4cnVSVQAAADoAAAekZW5VUwAAADwAAAfeAFYBYQBlAG8AYgBl
-AGMAbgDhACAAcwBpAHYA4QAgAGcAYQBtAGEAIAAyACwAMgBHAGUAbgBlAHIAaQBzAGsAIABnAHIA5QAg
-ADIALAAyACAAZwBhAG0AbQBhAC0AcAByAG8AZgBpAGwARwBhAG0AbQBhACAAZABlACAAZwByAGkAcwBv
-AHMAIABnAGUAbgDoAHIAaQBjAGEAIAAyAC4AMgBDHqUAdQAgAGgA7ABuAGgAIABNAOAAdQAgAHgA4QBt
-ACAAQwBoAHUAbgBnACAARwBhAG0AbQBhACAAMgAuADIAUABlAHIAZgBpAGwAIABHAGUAbgDpAHIAaQBj
-AG8AIABkAGEAIABHAGEAbQBhACAAZABlACAAQwBpAG4AegBhAHMAIAAyACwAMgQXBDAEMwQwBDsETAQ9
-BDAAIABHAHIAYQB5AC0EMwQwBDwEMAAgADIALgAyAFAAcgBvAGYAaQBsACAAZwDpAG4A6QByAGkAcQB1
-AGUAIABnAHIAaQBzACAAZwBhAG0AbQBhACAAMgAsADIAwQBsAHQAYQBsAOEAbgBvAHMAIABzAHoA/ABy
-AGsAZQAgAGcAYQBtAG0AYQAgADIALgAykBp1KHBwlo5RSV6mADIALgAygnJfaWPPj/DHfLwYACDWjMDJ
-ACCsELnIACAAMgAuADIAINUEuFzTDMd8AEcAZQBuAGUAcgBpAHMAawAgAGcAcgDlACAAZwBhAG0AbQBh
-ACAAMgAsADIALQBwAHIAbwBmAGkAbABPAGIAZQBjAG4A4QAgAWEAZQBkAOEAIABnAGEAbQBhACAAMgAu
-ADIF0gXQBd4F1AAgBdAF5AXVBegAIAXbBdwF3AXZACAAMgAuADIARwBhAG0AYQAgAGcAcgBpACAAZwBl
-AG4AZQByAGkAYwEDACAAMgAsADIAQQBsAGwAZwBlAG0AZQBpAG4AZQBzACAARwByAGEAdQBzAHQAdQBm
-AGUAbgAtAFAAcgBvAGYAaQBsACAARwBhAG0AbQBhACAAMgAsADIAUAByAG8AZgBpAGwAbwAgAGcAcgBp
-AGcAaQBvACAAZwBlAG4AZQByAGkAYwBvACAAZABlAGwAbABhACAAZwBhAG0AbQBhACAAMgAsADIARwBl
-AG4AZQByAGkAcwBrACAAZwByAOUAIAAyACwAMgAgAGcAYQBtAG0AYQBwAHIAbwBmAGkAbGZukBpwcF6m
-fPtlcAAyAC4AMmPPj/Blh072TgCCLDCwMOwwpDCsMPMw3gAgADIALgAyACAw1zDtMNUwoTCkMOsDkwO1
-A70DuQO6A8wAIAOTA7oDwQO5ACADkwOsA7wDvAOxACAAMgAuADIAUABlAHIAZgBpAGwAIABnAGUAbgDp
-AHIAaQBjAG8AIABkAGUAIABjAGkAbgB6AGUAbgB0AG8AcwAgAGQAYQAgAEcAYQBtAG0AYQAgADIALAAy
-AEEAbABnAGUAbQBlAGUAbgAgAGcAcgBpAGoAcwAgAGcAYQBtAG0AYQAgADIALAAyAC0AcAByAG8AZgBp
-AGUAbABQAGUAcgBmAGkAbAAgAGcAZQBuAOkAcgBpAGMAbwAgAGQAZQAgAGcAYQBtAG0AYQAgAGQAZQAg
-AGcAcgBpAHMAZQBzACAAMgAsADIOIw4xDgcOKg41DkEOAQ4hDiEOMg5ADgEOIw4iDkwOFw4xDkgOJw5E
-DhsAIAAyAC4AMgBHAGUAbgBlAGwAIABHAHIAaQAgAEcAYQBtAGEAIAAyACwAMgBZAGwAZQBpAG4AZQBu
-ACAAaABhAHIAbQBhAGEAbgAgAGcAYQBtAG0AYQAgADIALAAyACAALQBwAHIAbwBmAGkAaQBsAGkARwBl
-AG4AZQByAGkBDQBrAGkAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABwAHIAbwBmAGkAbABV
-AG4AaQB3AGUAcgBzAGEAbABuAHkAIABwAHIAbwBmAGkAbAAgAHMAegBhAHIAbwFbAGMAaQAgAGcAYQBt
-AG0AYQAgADIALAAyBjoGJwZFBicAIAAyAC4AMgAgBkQGSAZGACAGMQZFBicGLwZKACAGOQYnBkUEHgQx
-BEkEMARPACAEQQQ1BEAEMARPACAEMwQwBDwEPAQwACAAMgAsADIALQQ/BEAEPgREBDgEOwRMAEcAZQBu
-AGUAcgBpAGMAIABHAHIAYQB5ACAARwBhAG0AbQBhACAAMgAuADIAIABQAHIAbwBmAGkAbABlAAB0ZXh0
-AAAAAENvcHlyaWdodCBBcHBsZSBJbmMuLCAyMDEyAABYWVogAAAAAAAA81EAAQAAAAEWzGN1cnYAAAAA
-AAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCG
-AIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwEl
-ASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gID
-AgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMt
-AzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSo
-BLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7
-BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiq
-CL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5
-C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4u
-DkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGM
-EaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVW
-FXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmR
-GbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5A
-HmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNm
-I5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkG
-KTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8k
-L1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXC
-Nf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzj
-PSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SK
-RM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6
-TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1
-VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69
-Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iW
-aOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMB
-c11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4B
-fmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZ
-if6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJ
-ljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKW
-owajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AA
-sHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74K
-voS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1
-zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF
-3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv7
-7IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY
-/Sn9uv5L/tz/bf//0issS0xcTlNDb2xvclNwYWNlok0wXE5TQ29sb3JTcGFjZdIrLE9QV05TQ29sb3Ki
-TzDSKyxSU1dOU0ltYWdlolIwAAgAEQAaACQAKQAyADcASQBMAFEAUwBnAG0AegCBAJAAlwCkAKsAswC1
-ALcAuQC+AMAAwgDLANAA2wDdAN8A4QDmAOkA6wDtAO8A9gENASkBKwEtIGcgbCB3IIAgkyCXIKIgqyCw
-ILgguyDAIM8g0yDeIOYg8yEAIRUhGiEeISAhIiEkIS0hMiE4IUAhQiFEIUYhSDLoMu0y+jL9MwozDzMX
-MxozHzMnAAAAAAAAAgEAAAAAAAAAVAAAAAAAAAAAAAAAAAAAMyo
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/Base.lproj/CallView~ipad.xib b/Classes/Base.lproj/CallView~ipad.xib
deleted file mode 100644
index 684d42dc6..000000000
--- a/Classes/Base.lproj/CallView~ipad.xib
+++ /dev/null
@@ -1,1397 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/CallConferenceTableView.m b/Classes/CallConferenceTableView.m
deleted file mode 100644
index 1abf56af9..000000000
--- a/Classes/CallConferenceTableView.m
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "CallConferenceTableView.h"
-#import "UICallConferenceCell.h"
-#import "LinphoneManager.h"
-#import "Utils.h"
-#import "linphoneapp-Swift.h"
-
-@implementation CallConferenceTableView
-
-#pragma mark - UI change
-
-- (void)update {
- [self.tableView reloadData];
-}
-
-#pragma mark - UITableViewDataSource Functions
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- NSString *kCellId = NSStringFromClass(UICallConferenceCell.class);
- UICallConferenceCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
- if (cell == nil) {
- cell = [[UICallConferenceCell alloc] initWithIdentifier:kCellId];
- }
- LinphoneConference *c = [CallManager.instance getConference];
- if (c != nil) {
- [cell setParticipant:bctbx_list_nth_data(linphone_conference_get_participant_list(c),(int)indexPath.row)];
- }
- cell.contentView.userInteractionEnabled = NO;
- return cell;
-}
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- return [CallManager.instance getConference] ? linphone_conference_get_participant_count([CallManager.instance getConference]) : 0;
-}
-
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return 1;
-}
-
-- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
- return 1e-5;
-}
-- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
- return 1e-5;
-}
-
-@end
diff --git a/Classes/CallIncomingView.h b/Classes/CallIncomingView.h
deleted file mode 100644
index 8031a7b2e..000000000
--- a/Classes/CallIncomingView.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import
-
-#import "UICompositeView.h"
-#import "TPMultiLayoutViewController.h"
-#import "UIRoundedImageView.h"
-#include "LinphoneManager.h"
-
-@protocol IncomingCallViewDelegate
-
-- (void)incomingCallAccepted:(LinphoneCall *)call evenWithVideo:(BOOL)video;
-- (void)incomingCallDeclined:(LinphoneCall *)call;
-- (void)incomingCallAborted:(LinphoneCall *)call;
-
-@end
-
-@interface CallIncomingView : TPMultiLayoutViewController {
-}
-
-@property(nonatomic) Boolean earlyMedia;
-
-@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
-@property(nonatomic, strong) IBOutlet UILabel *addressLabel;
-@property(nonatomic, strong) IBOutlet UIRoundedImageView *avatarImage;
-@property(nonatomic, assign) LinphoneCall *call;
-@property(nonatomic, strong) id delegate;
-@property(weak, nonatomic) IBOutlet UIView *tabVideoBar;
-@property(weak, nonatomic) IBOutlet UIView *tabBar;
-@property (weak, nonatomic) IBOutlet UIView *earlyMediaView;
-
-
-- (IBAction)onAcceptClick:(id)event;
-- (IBAction)onDeclineClick:(id)event;
-- (IBAction)onAcceptAudioOnlyClick:(id)sender;
-
-@end
diff --git a/Classes/CallIncomingView.m b/Classes/CallIncomingView.m
deleted file mode 100644
index ccf22052b..000000000
--- a/Classes/CallIncomingView.m
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "CallIncomingView.h"
-#import "LinphoneManager.h"
-#import "FastAddressBook.h"
-#import "PhoneMainView.h"
-#import "Utils.h"
-
-@implementation CallIncomingView
-
-#pragma mark - ViewController Functions
-
-- (void)viewWillAppear:(BOOL)animated {
- [super viewWillAppear:animated];
-
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(callUpdateEvent:)
- name:kLinphoneCallUpdate
- object:nil];
-}
-
-- (void)viewWillDisappear:(BOOL)animated {
- [super viewWillDisappear:animated];
- [NSNotificationCenter.defaultCenter removeObserver:self name:kLinphoneCallUpdate object:nil];
- _call = NULL;
-}
-
-#pragma mark - UICompositeViewDelegate Functions
-
-static UICompositeViewDescription *compositeDescription = nil;
-
-+ (UICompositeViewDescription *)compositeViewDescription {
- if (compositeDescription == nil) {
- compositeDescription = [[UICompositeViewDescription alloc] init:self.class
- statusBar:StatusBarView.class
- tabBar:nil
- sideMenu:CallSideMenuView.class
- fullscreen:false
- isLeftFragment:YES
- fragmentWith:nil];
- compositeDescription.darkBackground = true;
- }
- return compositeDescription;
-}
-
-- (UICompositeViewDescription *)compositeViewDescription {
- return self.class.compositeViewDescription;
-}
-
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
- [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
- if (_earlyMedia && [LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"] && linphone_core_get_calls_nb(LC) < 2) {
- _earlyMediaView.hidden = NO;
- linphone_core_set_native_video_window_id(LC, (__bridge void *)(_earlyMediaView));
- }
- if (_call) {
- [self update];
- }
-}
-
-#pragma mark - Event Functions
-
-- (void)callUpdateEvent:(NSNotification *)notif {
- LinphoneCall *acall = [[notif.userInfo objectForKey:@"call"] pointerValue];
- LinphoneCallState astate = [[notif.userInfo objectForKey:@"state"] intValue];
- [self callUpdate:acall state:astate];
-}
-
-- (void)callUpdate:(LinphoneCall *)acall state:(LinphoneCallState)astate {
- if (_call == acall && (astate == LinphoneCallEnd || astate == LinphoneCallError)) {
- [_delegate incomingCallAborted:_call];
- } else if ([LinphoneManager.instance lpConfigBoolForKey:@"auto_answer"]) {
- LinphoneCallState state = linphone_call_get_state(_call);
- if (state == LinphoneCallIncomingReceived) {
- LOGI(@"Auto answering call");
- [self onAcceptClick:nil];
- }
- }
-}
-
-#pragma mark -
-
-- (void)update {
- const LinphoneAddress *addr = linphone_call_get_remote_address(_call);
- [ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr withAddressLabel:_addressLabel];
- char *uri = linphone_address_as_string_uri_only(addr);
- ms_free(uri);
- [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:YES withRoundedRadius:YES];
-
- _tabBar.hidden = linphone_call_params_video_enabled(linphone_call_get_remote_params(_call));
- _tabVideoBar.hidden = !_tabBar.hidden;
-}
-
-#pragma mark - Property Functions
-static void hideSpinner(LinphoneCall *call, void *user_data) {
- CallIncomingView *thiz = (__bridge CallIncomingView *)user_data;
- thiz.earlyMedia = TRUE;
- thiz.earlyMediaView.hidden = NO;
- linphone_core_set_native_video_window_id(LC, (__bridge void *)(thiz.earlyMediaView));
-}
-
-- (void)setCall:(LinphoneCall *)call {
- _call = call;
- _earlyMedia = FALSE;
- if ([LinphoneManager.instance lpConfigBoolForKey:@"pref_accept_early_media"] && linphone_core_get_calls_nb(LC) < 2) {
- linphone_call_accept_early_media(_call);
- // linphone_call_params_get_used_video_codec return 0 if no video stream enabled
- if (linphone_call_params_get_used_video_codec(linphone_call_get_current_params(_call))) {
- linphone_call_set_next_video_frame_decoded_callback(call, hideSpinner, (__bridge void *)(self));
- }
- } else {
- _earlyMediaView.hidden = YES;
- }
-
- [self update];
- [self callUpdate:_call state:linphone_call_get_state(call)];
-}
-
-#pragma mark - Action Functions
-
-- (IBAction)onAcceptClick:(id)event {
- [_delegate incomingCallAccepted:_call evenWithVideo:YES];
-}
-
-- (IBAction)onDeclineClick:(id)event {
- [_delegate incomingCallDeclined:_call];
-}
-
-- (IBAction)onAcceptAudioOnlyClick:(id)sender {
- [_delegate incomingCallAccepted:_call evenWithVideo:NO];
-}
-
-@end
diff --git a/Classes/CallManager.swift b/Classes/CallManager.swift
index b267ae43f..6d3a7930f 100644
--- a/Classes/CallManager.swift
+++ b/Classes/CallManager.swift
@@ -37,15 +37,13 @@ import AVFoundation
static var theCallManager: CallManager?
let providerDelegate: ProviderDelegate! // to support callkit
let callController: CXCallController! // to support callkit
- var lc: Core?
- @objc var speakerBeforePause : Bool = false
+ var core: Core?
@objc var nextCallIsTransfer: Bool = false
var referedFromCall: String?
var referedToCall: String?
var endCallkit: Bool = false
var globalState : GlobalState = .Off
var actionsToPerformOnceWhenCoreIsOn : [(()->Void)] = []
- var conference: Conference?
@@ -65,8 +63,8 @@ import AVFoundation
}
@objc func setCore(core: OpaquePointer) {
- lc = Core.getSwiftObject(cObject: core)
- lc?.addDelegate(delegate: self)
+ self.core = Core.getSwiftObject(cObject: core)
+ self.core?.addDelegate(delegate: self)
}
@objc static func getAppData(call: OpaquePointer) -> CallAppData? {
@@ -106,7 +104,7 @@ import AVFoundation
if (callId == nil) {
return nil
}
- let calls = lc?.calls
+ let calls = core?.calls
if let callTmp = calls?.first(where: { $0.callLog?.callId == callId }) {
return callTmp
}
@@ -114,8 +112,8 @@ import AVFoundation
}
@objc func stopLinphoneCore() {
- if (lc?.callsNb == 0) {
- lc?.stopAsync()
+ if (core?.callsNb == 0) {
+ core?.stopAsync()
}
}
@@ -139,60 +137,6 @@ import AVFoundation
return false
}
- @objc func changeRouteToSpeaker() {
- for device in lc!.audioDevices {
- if (device.type == AudioDeviceType.Speaker) {
- lc!.outputAudioDevice = device
- break
- }
- }
- UIDevice.current.isProximityMonitoringEnabled = false
- }
-
- @objc func changeRouteToBluetooth() {
- for device in lc!.audioDevices {
- if (device.type == AudioDeviceType.Bluetooth || device.type == AudioDeviceType.BluetoothA2DP) {
- lc!.outputAudioDevice = device
- break
- }
- }
- UIDevice.current.isProximityMonitoringEnabled = (lc!.callsNb > 0)
- }
-
- @objc func changeRouteToDefault() {
- lc!.outputAudioDevice = lc!.defaultOutputAudioDevice
- }
-
- @objc func isBluetoothAvailable() -> Bool {
- for device in lc!.audioDevices {
- if (device.type == AudioDeviceType.Bluetooth || device.type == AudioDeviceType.BluetoothA2DP) {
- return true;
- }
- }
- return false;
- }
-
- @objc func isSpeakerEnabled() -> Bool {
- if let outputDevice = lc!.outputAudioDevice {
- return outputDevice.type == AudioDeviceType.Speaker
- }
- return false
- }
-
- @objc func isBluetoothEnabled() -> Bool {
- if let outputDevice = lc!.outputAudioDevice {
- return (outputDevice.type == AudioDeviceType.Bluetooth || outputDevice.type == AudioDeviceType.BluetoothA2DP)
- }
- return false
- }
-
- @objc func isReceiverEnabled() -> Bool {
- if let outputDevice = lc!.outputAudioDevice {
- return outputDevice.type == AudioDeviceType.Microphone
- }
- return false
- }
-
func requestTransaction(_ transaction: CXTransaction, action: String) {
callController.request(transaction) { error in
@@ -238,7 +182,7 @@ import AVFoundation
func acceptCall(call: Call, hasVideo:Bool) {
do {
- let callParams = try lc!.createCallParams(call: call)
+ let callParams = try core!.createCallParams(call: call)
callParams.videoEnabled = hasVideo
if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (AppManager.network() == .network_2g)
@@ -269,7 +213,7 @@ import AVFoundation
}
let sAddr = Address.getSwiftObject(cObject: addr!)
- if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && !isInConference()) {
+ if (CallManager.callKitEnabled() && !CallManager.instance().nextCallIsTransfer && core?.conference?.isIn != true) {
let uuid = UUID()
let name = FastAddressBook.displayName(for: addr) ?? "unknow"
let handle = CXHandle(type: .generic, value: sAddr.asStringUriOnly())
@@ -290,7 +234,7 @@ import AVFoundation
func doCall(addr: Address, isSas: Bool) throws {
let displayName = FastAddressBook.displayName(for: addr.getCobject)
- let lcallParams = try CallManager.instance().lc!.createCallParams(call: nil)
+ let lcallParams = try CallManager.instance().core!.createCallParams(call: nil)
if ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference") && AppManager.network() == .network_2g {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Enabling low bandwidth mode")
lcallParams.lowBandwidthEnabled = true
@@ -305,7 +249,7 @@ import AVFoundation
}
if (CallManager.instance().nextCallIsTransfer) {
- let call = CallManager.instance().lc!.currentCall
+ let call = CallManager.instance().core!.currentCall
try call?.transferTo(referTo: addr)
CallManager.instance().nextCallIsTransfer = false
} else {
@@ -316,7 +260,7 @@ import AVFoundation
if (isSas) {
lcallParams.mediaEncryption = .ZRTP
}
- let call = CallManager.instance().lc!.inviteAddressWithParams(addr: addr, params: lcallParams)
+ let call = CallManager.instance().core!.inviteAddressWithParams(addr: addr, params: lcallParams)
if (call != nil) {
// The LinphoneCallAppData object should be set on call creation with callback
// - (void)onCall:StateChanged:withMessage:. If not, we are in big trouble and expect it to crash
@@ -333,32 +277,6 @@ import AVFoundation
}
}
- @objc func groupCall() {
- if (CallManager.callKitEnabled()) {
- let calls = lc?.calls
- if (calls == nil || calls!.isEmpty) {
- return
- }
- let firstCall = calls!.first?.callLog?.callId ?? ""
- let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
-
- let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
- if (currentUuid == nil) {
- Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
- return
- }
-
- let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
- let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
- let transcation = CXTransaction(action: groupAction)
- requestTransaction(transcation, action: "groupCall")
-
- setResumeCalls()
- } else {
- try? lc?.addAllToConference()
- }
- }
-
@objc func removeAllCallInfos() {
providerDelegate.callInfos.removeAll()
providerDelegate.uuids.removeAll()
@@ -418,7 +336,7 @@ import AVFoundation
}
@objc func setHeldOtherCalls(exceptCallid: String) {
- for call in CallManager.instance().lc!.calls {
+ for call in CallManager.instance().core!.calls {
if (call.callLog?.callId != exceptCallid && call.state != .Paused && call.state != .Pausing && call.state != .PausedByRemote) {
setHeld(call: call, hold: true)
}
@@ -426,7 +344,7 @@ import AVFoundation
}
func setResumeCalls() {
- for call in CallManager.instance().lc!.calls {
+ for call in CallManager.instance().core!.calls {
if (call.state == .Paused || call.state == .Pausing || call.state == .PausedByRemote) {
setHeld(call: call, hold: false)
}
@@ -443,7 +361,7 @@ import AVFoundation
@objc func acceptVideo(call: OpaquePointer, confirm: Bool) {
let sCall = Call.getSwiftObject(cObject: call)
- let params = try? lc?.createCallParams(call: sCall)
+ let params = try? core?.createCallParams(call: sCall)
params?.videoEnabled = confirm
try? sCall.acceptUpdate(params: params)
}
@@ -464,7 +382,7 @@ import AVFoundation
for call in CallManager.instance().providerDelegate.uuids {
let callId = CallManager.instance().providerDelegate.callInfos[call.value]?.callId
if (callId != nil) {
- let call = CallManager.instance().lc?.getCallByCallid(callId: callId!)
+ let call = CallManager.instance().core?.getCallByCallid(callId: callId!)
if (call != nil && call?.state != .PushIncomingReceived) {
// sometimes (for example) due to network, registration failed, in this case, keep the call
continue
@@ -478,12 +396,6 @@ import AVFoundation
CallManager.instance().endCallkit = false
}
}
-
- func onConferenceStateChanged(core: Core, conference: Conference, state: Conference.State) {
- if (state == .Terminated) {
- CallManager.instance().conference = nil
- }
- }
func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) {
let callLog = call.callLog
@@ -535,11 +447,6 @@ import AVFoundation
}
}
}
-
- if (CallManager.instance().speakerBeforePause) {
- CallManager.instance().speakerBeforePause = false
- CallManager.instance().changeRouteToSpeaker()
- }
break
case .OutgoingInit,
.OutgoingProgress,
@@ -568,14 +475,6 @@ import AVFoundation
displayName = contactName
}
- UIDevice.current.isProximityMonitoringEnabled = false
- if (CallManager.instance().lc!.callsNb == 0) {
- CallManager.instance().changeRouteToDefault()
- // disable this because I don't find anygood reason for it: _bluetoothAvailable = FALSE;
- // furthermore it introduces a bug when calling multiple times since route may not be
- // reconfigured between cause leading to bluetooth being disabled while it should not
- //CallManager.instance().bluetoothEnabled = false
- }
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
@@ -634,12 +533,6 @@ import AVFoundation
break
}
- if (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning) {
- let check = call.currentParams?.videoEnabled
- if ((call.currentParams?.videoEnabled ?? false) && CallManager.instance().isReceiverEnabled()) {
- CallManager.instance().changeRouteToSpeaker()
- }
- }
}
// post Notification kLinphoneCallUpdate
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [
@@ -652,13 +545,13 @@ import AVFoundation
// Audio messages
@objc func activateAudioSession() {
- lc?.activateAudioSession(actived: true)
+ core?.activateAudioSession(actived: true)
}
@objc func getSpeakerSoundCard() -> String? {
var speakerCard: String? = nil
var earpieceCard: String? = nil
- lc?.audioDevices.forEach { device in
+ core?.audioDevices.forEach { device in
if (device.hasCapability(capability: .CapabilityPlay)) {
if (device.type == .Speaker) {
speakerCard = device.id
@@ -670,116 +563,46 @@ import AVFoundation
return speakerCard != nil ? speakerCard : earpieceCard
}
-
-
- // Conference
-
- @objc func hostConference() -> Bool {
- return conference != nil
- }
+ // Local Conference
- func addAllToConference() {
- if (conference == nil) {
- guard let cp = try?lc?.createConferenceParams() else {
- Log.directLog(BCTBX_LOG_ERROR, text: "Unable to create conference parameters")
+ @objc func startLocalConference() {
+ if (CallManager.callKitEnabled()) {
+ let calls = core?.calls
+ if (calls == nil || calls!.isEmpty) {
return
}
- if let currentCall = lc?.currentCall, let currentParams = currentCall.currentParams {
- cp.videoEnabled = currentParams.videoEnabled
- }
- conference = try?lc?.createConferenceWithParams(params: cp)
- }
- lc?.calls.forEach { call in
- if (call.conference == nil || call.conference?.participantCount == 1) {
- try?conference?.addParticipant(call: call)
- }
- }
- }
-
- @objc func getConference() -> OpaquePointer? {
- guard let core = lc else {
- return nil
- }
- return (core.conference != nil) ? core.conference?.getCobject : (core.currentCall?.conference != nil) ? core.currentCall!.conference!.getCobject : nil
- }
-
- func getConference() -> Conference? {
- guard let core = lc else {
- return nil
- }
- return (core.conference != nil) ? core.conference : (core.currentCall?.conference != nil) ? core.currentCall!.conference : nil
- }
-
- @objc func isInConference() -> Bool {
- return isInConferenceAsHost()||isInConferenceAsGuest()
- }
-
- @objc func isInConferenceAsGuest() -> Bool {
- guard let core = lc else {
- return false
- }
- return !isInConferenceAsHost() && core.currentCall != nil && core.currentCall?.conference != nil && (core.currentCall?.conference!.participantCount)! > 1
- }
-
- @objc func isInConferenceAsHost() -> Bool {
- guard let core = lc else {
- return false
- }
- return core.conference?.isIn == true
- }
-
- @objc func hasConferenceAsGuest() -> Bool {
- guard let core = lc else {
- return false
- }
- if (core.callsNb<=1) {
- return false
- }
- var found = false
- core.calls.forEach {
- let c = $0.conference
- if (c != nil && c!.participantCount > 1 && hostConference()) {
- found = true
+ let firstCall = calls!.first?.callLog?.callId ?? ""
+ let lastCall = (calls!.count > 1) ? calls!.last?.callLog?.callId ?? "" : ""
+
+ let currentUuid = CallManager.instance().providerDelegate.uuids["\(firstCall)"]
+ if (currentUuid == nil) {
+ Log.directLog(BCTBX_LOG_ERROR, text: "Can not find correspondant call to group.")
return
}
+
+ let newUuid = CallManager.instance().providerDelegate.uuids["\(lastCall)"]
+ let groupAction = CXSetGroupCallAction(call: currentUuid!, callUUIDToGroupWith: newUuid)
+ let transcation = CXTransaction(action: groupAction)
+ requestTransaction(transcation, action: "groupCall")
+
+ setResumeCalls()
+ } else {
+ addAllToLocalConference()
}
- return found
}
- @objc func getCallFor(participant : OpaquePointer) -> OpaquePointer? {
- let p = Participant.getSwiftObject(cObject: participant)
- guard let core = lc else {
- return nil
- }
- var call:Call? = nil
- core.calls.forEach { (callIt) in
- let c = callIt.conference
- c?.participantList.forEach { (p2) in
- if (p2.address?.asStringUriOnly() == p.address?.asStringUriOnly()) {
- call = callIt
- return
- }
+ func addAllToLocalConference() {
+ do {
+ if let core = core, let params = try? core.createConferenceParams() {
+ params.videoEnabled = false // We disable video for local conferencing (cf Android)
+ let conference = core.conference != nil ? core.conference : try core.createConferenceWithParams(params: params)
+ try conference?.addParticipants(calls: core.calls)
}
+ } catch {
+ Log.directLog(BCTBX_LOG_ERROR, text: "accept call failed \(error)")
}
- return call?.getCobject
}
- @objc func inVideoConf() -> Bool {
- guard let core = lc else {
- return false
- }
- let result = isInConference() && (getConference()?.currentParams?.isVideoEnabled == true || core.currentCall?.currentParams?.videoEnabled == true)
- NSLog("cdes \(result) \(core.currentCall?.currentParams?.videoEnabled)")
- return result
- }
-
-
- @objc func inAudioConf() -> Bool {
- guard let core = lc else {
- return false
- }
- return core.conference?.isIn == true && core.conference != nil && core.currentCall?.conference?.currentParams?.isVideoEnabled == false
- }
}
diff --git a/Classes/CallOutgoingView.h b/Classes/CallOutgoingView.h
deleted file mode 100644
index 1d4430efe..000000000
--- a/Classes/CallOutgoingView.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import
-
-#import "UICompositeView.h"
-#import "TPMultiLayoutViewController.h"
-#include "linphone/linphonecore.h"
-#import "UIRoundedImageView.h"
-#import "UIDigitButton.h"
-
-
-@interface CallOutgoingView : TPMultiLayoutViewController {
-}
-@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
-@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
-@property(weak, nonatomic) IBOutlet UISpeakerButton *speakerButton;
-@property(weak, nonatomic) IBOutlet UILabel *addressLabel;
-@property(weak, nonatomic) IBOutlet UIToggleButton *routesButton;
-@property(weak, nonatomic) IBOutlet UIView *routesView;
-@property(weak, nonatomic) IBOutlet UIBluetoothButton *routesBluetoothButton;
-@property(weak, nonatomic) IBOutlet UIButton *routesEarpieceButton;
-@property(weak, nonatomic) IBOutlet UISpeakerButton *routesSpeakerButton;
-@property(weak, nonatomic) IBOutlet UIMutedMicroButton *microButton;
-@property (weak, nonatomic) IBOutlet UIButton *declineButton;
-
-@property (weak, nonatomic) IBOutlet UIToggleButton *numpadButton;
-@property (strong, nonatomic) IBOutlet UIView *numpadView;
-@property(nonatomic, strong) IBOutlet UIDigitButton *oneButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *twoButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *threeButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *fourButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *fiveButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *sixButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *sevenButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *eightButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *nineButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *starButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *zeroButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *hashButton;
-@property (weak, nonatomic) IBOutlet UIButton *declineButton_earlyMedia;
-
-- (IBAction)onDeclineClick:(id)sender;
-
-@end
diff --git a/Classes/CallOutgoingView.m b/Classes/CallOutgoingView.m
deleted file mode 100644
index c5057377d..000000000
--- a/Classes/CallOutgoingView.m
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "CallOutgoingView.h"
-#import "PhoneMainView.h"
-
-@implementation CallOutgoingView
-
-#pragma mark - UICompositeViewDelegate Functions
-
-static UICompositeViewDescription *compositeDescription = nil;
-
-+ (UICompositeViewDescription *)compositeViewDescription {
- if (compositeDescription == nil) {
- compositeDescription = [[UICompositeViewDescription alloc] init:self.class
- statusBar:StatusBarView.class
- tabBar:nil
- sideMenu:CallSideMenuView.class
- fullscreen:false
- isLeftFragment:NO
- fragmentWith:nil];
-
- compositeDescription.darkBackground = true;
- }
- return compositeDescription;
-}
-
-- (UICompositeViewDescription *)compositeViewDescription {
- return self.class.compositeViewDescription;
-}
-
-- (void)viewDidLoad {
- _routesEarpieceButton.enabled = !IPAD;
-
- [_zeroButton setDigit:'0'];
- [_zeroButton setDtmf:true];
- [_oneButton setDigit:'1'];
- [_oneButton setDtmf:true];
- [_twoButton setDigit:'2'];
- [_twoButton setDtmf:true];
- [_threeButton setDigit:'3'];
- [_threeButton setDtmf:true];
- [_fourButton setDigit:'4'];
- [_fourButton setDtmf:true];
- [_fiveButton setDigit:'5'];
- [_fiveButton setDtmf:true];
- [_sixButton setDigit:'6'];
- [_sixButton setDtmf:true];
- [_sevenButton setDigit:'7'];
- [_sevenButton setDtmf:true];
- [_eightButton setDigit:'8'];
- [_eightButton setDtmf:true];
- [_nineButton setDigit:'9'];
- [_nineButton setDtmf:true];
- [_starButton setDigit:'*'];
- [_starButton setDtmf:true];
- [_hashButton setDigit:'#'];
- [_hashButton setDtmf:true];
-
-}
-
-- (void)viewWillAppear:(BOOL)animated {
- [super viewWillAppear:animated];
-
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(bluetoothAvailabilityUpdateEvent:)
- name:kLinphoneBluetoothAvailabilityUpdate
- object:nil];
-
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(callUpdateEvent:)
- name:kLinphoneCallUpdate
- object:nil];
-
- LinphoneCall *call = linphone_core_get_current_call(LC);
- if (!call) {
- return;
- }
-
- const LinphoneAddress *addr = linphone_call_get_remote_address(call);
- [ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr withAddressLabel:_addressLabel];
- char *uri = linphone_address_as_string_uri_only(addr);
- ms_free(uri);
- [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
-
- [self hideSpeaker:LinphoneManager.instance.bluetoothAvailable];
-
- [_speakerButton update];
- [_microButton update];
- [_routesButton update];
- [self hidePad:TRUE animated:FALSE];
- [self callUpdate:call state:linphone_call_get_state(call) animated:true];
-
-}
-
-- (void)viewDidAppear:(BOOL)animated {
- [super viewDidAppear:animated];
- // if there is no call (for whatever reason), we must wait viewDidAppear method
- // before popping current view, because UICompositeView cannot handle view change
- // directly in viewWillAppear (this would lead to crash in deallocated memory - easily
- // reproductible on iPad mini).
- if (!linphone_core_get_current_call(LC)) {
- [PhoneMainView.instance popCurrentView];
- }
-}
-
-- (void)viewWillDisappear:(BOOL)animated {
- [super viewWillDisappear:animated];
- [NSNotificationCenter.defaultCenter removeObserver:self];
-}
-
-- (IBAction)onRoutesBluetoothClick:(id)sender {
- [self hideRoutes:TRUE animated:TRUE];
- [CallManager.instance changeRouteToBluetooth];
-}
-
-- (IBAction)onRoutesEarpieceClick:(id)sender {
- [self hideRoutes:TRUE animated:TRUE];
- [CallManager.instance changeRouteToDefault];
-}
-
-- (IBAction)onRoutesSpeakerClick:(id)sender {
- [self hideRoutes:TRUE animated:TRUE];
- [CallManager.instance changeRouteToSpeaker];
-}
-
-- (IBAction)onRoutesClick:(id)sender {
- if ([_routesView isHidden]) {
- [self hideRoutes:FALSE animated:ANIMATED];
- } else {
- [self hideRoutes:TRUE animated:ANIMATED];
- }
-}
-
-- (IBAction)onNumpadClick:(id)sender {
- if ([_numpadView isHidden]) {
- [self hidePad:FALSE animated:ANIMATED];
- } else {
- [self hidePad:TRUE animated:ANIMATED];
- }
-}
-
-- (IBAction)onDeclineClick:(id)sender {
- LinphoneCall *call = linphone_core_get_current_call(LC);
- if (call) {
- [CallManager.instance terminateCallWithCall:call];
- }
-}
-
-- (void)hidePad:(BOOL)hidden animated:(BOOL)animated {
- if (hidden) {
- [_numpadButton setOff];
- } else {
- [_numpadButton setOn];
- }
- if (hidden != _numpadView.hidden) {
- if (animated) {
- [self hideAnimation:hidden forView:_numpadView completion:nil];
- } else {
- [_numpadView setHidden:hidden];
- }
- }
-}
-
-- (void)hideRoutes:(BOOL)hidden animated:(BOOL)animated {
- if (hidden) {
- [_routesButton setOff];
- } else {
- [_routesButton setOn];
- }
-
- _routesBluetoothButton.selected = [CallManager.instance isBluetoothEnabled];
- _routesSpeakerButton.selected = [CallManager.instance isSpeakerEnabled];
- _routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected;
-
- if (hidden != _routesView.hidden) {
- [_routesView setHidden:hidden];
- }
-}
-
-- (void)hideSpeaker:(BOOL)hidden {
- _speakerButton.hidden = hidden;
- _routesButton.hidden = !hidden;
-}
-
-#pragma mark - Event Functions
-
-- (void)bluetoothAvailabilityUpdateEvent:(NSNotification *)notif {
- bool available = [[notif.userInfo objectForKey:@"available"] intValue];
- dispatch_async(dispatch_get_main_queue(), ^{
- [self hideSpeaker:available];
- });
-}
-
-- (void)callUpdateEvent:(NSNotification *)notif {
- LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue];
- LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue];
- [self callUpdate:call state:state animated:TRUE];
-}
-
-- (void)callUpdate:(LinphoneCall *)call state:(LinphoneCallState)state animated:(BOOL)animated {
- _declineButton_earlyMedia.hidden = linphone_call_get_state(call) != LinphoneCallStateOutgoingEarlyMedia;
- _declineButton.hidden = !_declineButton_earlyMedia.hidden;
- _numpadButton.hidden = _declineButton_earlyMedia.hidden;
-}
-
-#pragma mark - Animation
-
-- (void)hideAnimation:(BOOL)hidden forView:(UIView *)target completion:(void (^)(BOOL finished))completion {
- if (hidden) {
- int original_y = target.frame.origin.y;
- CGRect newFrame = target.frame;
- newFrame.origin.y = self.view.frame.size.height;
- [UIView animateWithDuration:0.5
- delay:0.0
- options:UIViewAnimationOptionCurveEaseIn
- animations:^{
- target.frame = newFrame;
- }
- completion:^(BOOL finished) {
- CGRect originFrame = target.frame;
- originFrame.origin.y = original_y;
- target.hidden = YES;
- target.frame = originFrame;
- if (completion)
- completion(finished);
- }];
- } else {
- CGRect frame = target.frame;
- int original_y = frame.origin.y;
- frame.origin.y = self.view.frame.size.height;
- target.frame = frame;
- frame.origin.y = original_y;
- target.hidden = NO;
-
- [UIView animateWithDuration:0.5
- delay:0.0
- options:UIViewAnimationOptionCurveEaseOut
- animations:^{
- target.frame = frame;
- }
- completion:^(BOOL finished) {
- target.frame = frame; // in case application did not finish
- if (completion)
- completion(finished);
- }];
- }
-}
-
-
-@end
diff --git a/Classes/CallPausedTableView.m b/Classes/CallPausedTableView.m
deleted file mode 100644
index 2dc9b6c29..000000000
--- a/Classes/CallPausedTableView.m
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "CallPausedTableView.h"
-#import "UICallPausedCell.h"
-#import "LinphoneManager.h"
-#import "Utils.h"
-
-@implementation CallPausedTableView
-
-#pragma mark - UI change
-
-- (void)update {
- [self.tableView reloadData];
- CGRect newOrigin = self.tableView.frame;
- newOrigin.size.height =
- [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] *
- [self tableView:self.tableView numberOfRowsInSection:0];
- newOrigin.origin.y += self.tableView.frame.size.height - newOrigin.size.height;
- self.tableView.frame = newOrigin;
-}
-
-#pragma mark - UITableViewDataSource Functions
-- (LinphoneCall *)conferenceCallForRow:(NSInteger)row {
- const MSList *calls = linphone_core_get_calls(LC);
- int i = -1;
- while (calls) {
- if (linphone_call_get_state(calls->data) == LinphoneCallPaused) {
- i++;
- if (i == row)
- break;
- }
- calls = calls->next;
- }
- // we should reach this only when we are querying for conference
- return (calls ? calls->data : NULL);
-}
-
-#pragma mark - UITableViewDataSource Functions
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- NSString *kCellId = NSStringFromClass(UICallPausedCell.class);
- UICallPausedCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellId];
- if (cell == nil) {
- cell = [[UICallPausedCell alloc] initWithIdentifier:kCellId];
- }
- [cell setCall:[self conferenceCallForRow:indexPath.row]];
- cell.contentView.userInteractionEnabled = false;
- return cell;
-}
-
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- const MSList *calls = linphone_core_get_calls(LC);
- int count = 0;
- int conference_in_pause = 0;
- int call_in_conference = 0;
- while (calls) {
- LinphoneCall *call = calls->data;
- BOOL callInConference = linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
- if (linphone_call_get_state(call) == LinphoneCallPaused) {
- count++;
- }
- if(callInConference) {
- call_in_conference++;
- if (!linphone_core_is_in_conference(LC)) {
- conference_in_pause = 1;
- }
- }
- calls = calls->next;
- }
- if(call_in_conference == 1) {
- conference_in_pause = 0;
- }
- return count + conference_in_pause;
-}
-
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
- return 1;
-}
-
-- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
- return 1e-5;
-}
-- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
- return 1e-5;
-}
-
-@end
diff --git a/Classes/CallSideMenuView.m b/Classes/CallSideMenuView.m
deleted file mode 100644
index 27ee4de48..000000000
--- a/Classes/CallSideMenuView.m
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "CallSideMenuView.h"
-#import "LinphoneManager.h"
-#import "PhoneMainView.h"
-
-@implementation CallSideMenuView {
- NSTimer *updateTimer;
-}
-
-#pragma mark - ViewController Functions
-
-- (void)viewWillAppear:(BOOL)animated {
- [super viewWillAppear:animated];
- if (updateTimer != nil) {
- [updateTimer invalidate];
- }
- updateTimer = [NSTimer scheduledTimerWithTimeInterval:1
- target:self
- selector:@selector(updateStats:)
- userInfo:nil
- repeats:YES];
-
- [self updateStats:nil];
-}
-
-- (void)viewWillDisappear:(BOOL)animated {
- [super viewWillDisappear:animated];
- if (updateTimer != nil) {
- [updateTimer invalidate];
- updateTimer = nil;
- }
-}
-
-- (IBAction)onLateralSwipe:(id)sender {
- [PhoneMainView.instance.mainViewController hideSideMenu:YES];
-}
-
-+ (NSString *)iceToString:(LinphoneIceState)state {
- switch (state) {
- case LinphoneIceStateNotActivated:
- return NSLocalizedString(@"Not activated", @"ICE has not been activated for this call");
- break;
- case LinphoneIceStateFailed:
- return NSLocalizedString(@"Failed", @"ICE processing has failed");
- break;
- case LinphoneIceStateInProgress:
- return NSLocalizedString(@"In progress", @"ICE process is in progress");
- break;
- case LinphoneIceStateHostConnection:
- return NSLocalizedString(@"Direct connection",
- @"ICE has established a direct connection to the remote host");
- break;
- case LinphoneIceStateReflexiveConnection:
- return NSLocalizedString(
- @"NAT(s) connection",
- @"ICE has established a connection to the remote host through one or several NATs");
- break;
- case LinphoneIceStateRelayConnection:
- return NSLocalizedString(@"Relay connection", @"ICE has established a connection through a relay");
- break;
- }
-}
-
-+ (NSString*)afinetToString:(int)remote_family {
- return (remote_family == LinphoneAddressFamilyUnspec) ? @"Unspecified":(remote_family == LinphoneAddressFamilyInet) ? @"IPv4" : @"IPv6";
-}
-
-+ (NSString *)mediaEncryptionToString:(LinphoneMediaEncryption)enc {
- switch (enc) {
- case LinphoneMediaEncryptionDTLS:
- return @"DTLS";
- case LinphoneMediaEncryptionSRTP:
- return @"SRTP";
- case LinphoneMediaEncryptionZRTP:
- return @"ZRTP";
- case LinphoneMediaEncryptionNone:
- break;
- }
- return NSLocalizedString(@"None", nil);
-}
-
-- (NSString *)updateStatsForCall:(LinphoneCall *)call stream:(LinphoneStreamType)stream {
- NSMutableString *result = [[NSMutableString alloc] init];
- const PayloadType *payload = NULL;
- const LinphoneCallStats *stats;
- const LinphoneCallParams *params = linphone_call_get_current_params(call);
- NSString *name;
-
- switch (stream) {
- case LinphoneStreamTypeAudio:
- name = @"Audio";
- payload = linphone_call_params_get_used_audio_codec(params);
- stats = linphone_call_get_audio_stats(call);
- break;
- case LinphoneStreamTypeText:
- name = @"Text";
- payload = linphone_call_params_get_used_text_codec(params);
- stats = linphone_call_get_text_stats(call);
- break;
- case LinphoneStreamTypeVideo:
- name = @"Video";
- payload = linphone_call_params_get_used_video_codec(params);
- stats = linphone_call_get_video_stats(call);
- break;
- case LinphoneStreamTypeUnknown:
- break;
- }
- if (payload == NULL) {
- return result;
- }
-
- [result appendString:@"\n"];
- [result appendString:name];
- [result appendString:@"\n"];
-
- [result appendString:[NSString stringWithFormat:@"Codec: %s/%iHz", payload->mime_type, payload->clock_rate]];
- if (stream == LinphoneStreamTypeAudio) {
- [result appendString:[NSString stringWithFormat:@"/%i channels", payload->channels]];
- }
- [result appendString:@"\n"];
- // Encoder & decoder descriptions
- const char *enc_desc = ms_factory_get_encoder(linphone_core_get_ms_factory(LC), payload->mime_type)->text;
- const char *dec_desc = ms_factory_get_decoder(linphone_core_get_ms_factory(LC), payload->mime_type)->text;
- if (strcmp(enc_desc, dec_desc) == 0) {
- [result appendString:[NSString stringWithFormat:@"Encoder/Decoder: %s", enc_desc]];
- [result appendString:@"\n"];
- } else {
- [result appendString:[NSString stringWithFormat:@"Encoder: %s", enc_desc]];
- [result appendString:@"\n"];
- [result appendString:[NSString stringWithFormat:@"Decoder: %s", dec_desc]];
- [result appendString:@"\n"];
- }
-
- if (stats != NULL) {
- [result appendString:[NSString stringWithFormat:@"Download bandwidth: %1.1f kbits/s",
- linphone_call_stats_get_download_bandwidth(stats)]];
- [result appendString:@"\n"];
- [result appendString:[NSString stringWithFormat:@"Upload bandwidth: %1.1f kbits/s",
- linphone_call_stats_get_upload_bandwidth(stats)]];
- [result appendString:@"\n"];
- if (stream == LinphoneStreamTypeVideo) {
- /*[result appendString:[NSString stringWithFormat:@"Estimated download bandwidth: %1.1f kbits/s",
- linphone_call_stats_get_estimated_download_bandwidth(stats)]];
- [result appendString:@"\n"];*/
- }
- [result
- appendString:[NSString stringWithFormat:@"ICE state: %@",
- [self.class iceToString:linphone_call_stats_get_ice_state(stats)]]];
- [result appendString:@"\n"];
- [result
- appendString:[NSString
- stringWithFormat:@"Afinet: %@",
- [self.class afinetToString:linphone_call_stats_get_ip_family_of_remote(
- stats)]]];
- [result appendString:@"\n"];
-
- // RTP stats section (packet loss count, etc)
- const rtp_stats_t rtp_stats = *linphone_call_stats_get_rtp_stats(stats);
- [result
- appendString:[NSString stringWithFormat:
- @"RTP packets: %llu total, %lld cum loss, %llu discarded, %llu OOT, %llu bad",
- rtp_stats.packet_recv, rtp_stats.cum_packet_loss, rtp_stats.discarded,
- rtp_stats.outoftime, rtp_stats.bad]];
- [result appendString:@"\n"];
- [result appendString:[NSString stringWithFormat:@"Sender loss rate: %.2f%%",
- linphone_call_stats_get_sender_loss_rate(stats)]];
- [result appendString:@"\n"];
- [result appendString:[NSString stringWithFormat:@"Receiver loss rate: %.2f%%",
- linphone_call_stats_get_receiver_loss_rate(stats)]];
- [result appendString:@"\n"];
-
- if (stream == LinphoneStreamTypeVideo) {
- const LinphoneVideoDefinition *recv_definition = linphone_call_params_get_received_video_definition(params);
- const LinphoneVideoDefinition *sent_definition = linphone_call_params_get_sent_video_definition(params);
- float sentFPS = linphone_call_params_get_sent_framerate(params);
- float recvFPS = linphone_call_params_get_received_framerate(params);
- [result appendString:[NSString stringWithFormat:@"Sent video resolution: %dx%d (%.1fFPS)", linphone_video_definition_get_width(sent_definition),
- linphone_video_definition_get_height(sent_definition), sentFPS]];
- [result appendString:@"\n"];
- [result appendString:[NSString stringWithFormat:@"Received video resolution: %dx%d (%.1fFPS)",
- linphone_video_definition_get_width(recv_definition),
- linphone_video_definition_get_height(recv_definition), recvFPS]];
- [result appendString:@"\n"];
- }
- }
- return result;
-}
-
-- (void)updateStats:(NSTimer *)timer {
- LinphoneCall *call = linphone_core_get_current_call(LC);
-
- if (!call) {
- _statsLabel.text = NSLocalizedString(@"No call in progress", nil);
- return;
- }
-
- NSMutableString *stats = [[NSMutableString alloc] init];
-
- LinphoneMediaEncryption enc = linphone_call_params_get_media_encryption(linphone_call_get_current_params(call));
- if (enc != LinphoneMediaEncryptionNone) {
- [stats appendString:[NSString
- stringWithFormat:@"Call encrypted using %@", [self.class mediaEncryptionToString:enc]]];
- }
-
- [stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeAudio]];
- [stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeVideo]];
- [stats appendString:[self updateStatsForCall:call stream:LinphoneStreamTypeText]];
-
- _statsLabel.text = stats;
-}
-
-@end
diff --git a/Classes/CallSideMenuView.xib b/Classes/CallSideMenuView.xib
deleted file mode 100644
index a0208f810..000000000
--- a/Classes/CallSideMenuView.xib
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/CallView.h b/Classes/CallView.h
deleted file mode 100644
index fa188895d..000000000
--- a/Classes/CallView.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import
-
-#import "VideoZoomHandler.h"
-#import "UICamSwitch.h"
-
-#import "UICompositeView.h"
-#import "CallPausedTableView.h"
-
-#import "UIMutedMicroButton.h"
-#import "UIPauseButton.h"
-#import "UISpeakerButton.h"
-#import "UIVideoButton.h"
-#import "UIHangUpButton.h"
-#import "UIDigitButton.h"
-#import "UIRoundedImageView.h"
-#import "UIBouncingView.h"
-
-@class VideoView;
-
-@interface CallView : TPMultiLayoutViewController {
- @private
- UITapGestureRecognizer *singleFingerTap;
- NSTimer *hideControlsTimer;
- NSTimer *videoDismissTimer;
- BOOL videoHidden;
- BOOL callRecording;
- VideoZoomHandler *videoZoomHandler;
-}
-
-@property(nonatomic, strong) IBOutlet CallPausedTableView *pausedCallsTable;
-
-@property(nonatomic, strong) IBOutlet UIView *videoGroup;
-@property(nonatomic, strong) IBOutlet UIView *videoView;
-@property(nonatomic, strong) IBOutlet UIView *videoPreview;
-@property(nonatomic, strong) IBOutlet UICamSwitch *videoCameraSwitch;
-@property(nonatomic, strong) IBOutlet UIActivityIndicatorView *videoWaitingForFirstImage;
-@property(weak, nonatomic) IBOutlet UIView *callView;
-
-@property(nonatomic, strong) IBOutlet UIPauseButton *callPauseButton;
-@property(nonatomic, strong) IBOutlet UIButton *optionsConferenceButton;
-@property(nonatomic, strong) IBOutlet UIVideoButton *videoButton;
-@property(nonatomic, strong) IBOutlet UIMutedMicroButton *microButton;
-@property(nonatomic, strong) IBOutlet UISpeakerButton *speakerButton;
-@property(nonatomic, strong) IBOutlet UIToggleButton *routesButton;
-@property(nonatomic, strong) IBOutlet UIToggleButton *optionsButton;
-@property(nonatomic, strong) IBOutlet UIHangUpButton *hangupButton;
-@property(nonatomic, strong) IBOutlet UIView *numpadView;
-@property(nonatomic, strong) IBOutlet UIView *routesView;
-@property(nonatomic, strong) IBOutlet UIView *optionsView;
-@property(nonatomic, strong) IBOutlet UIButton *routesEarpieceButton;
-@property(nonatomic, strong) IBOutlet UIButton *routesSpeakerButton;
-@property(nonatomic, strong) IBOutlet UIButton *routesBluetoothButton;
-@property(nonatomic, strong) IBOutlet UIButton *optionsAddButton;
-@property(nonatomic, strong) IBOutlet UIButton *optionsTransferButton;
-@property(nonatomic, strong) IBOutlet UIToggleButton *numpadButton;
-@property(weak, nonatomic) IBOutlet UIPauseButton *conferencePauseButton;
-@property(weak, nonatomic) IBOutlet UIBouncingView *chatNotificationView;
-@property(weak, nonatomic) IBOutlet UILabel *chatNotificationLabel;
-@property (weak, nonatomic) IBOutlet UIButton *recordButton;
-@property (weak, nonatomic) IBOutlet UIButton *recordButtonOnView;
-
-@property(weak, nonatomic) IBOutlet UIView *bottomBar;
-@property(nonatomic, strong) IBOutlet UIDigitButton *oneButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *twoButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *threeButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *fourButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *fiveButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *sixButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *sevenButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *eightButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *nineButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *starButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *zeroButton;
-@property(nonatomic, strong) IBOutlet UIDigitButton *hashButton;
-@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
-@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
-@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
-@property(weak, nonatomic) IBOutlet UIView *pausedByRemoteView;
-@property(weak, nonatomic) IBOutlet UIView *noActiveCallView;
-@property(weak, nonatomic) IBOutlet UIView *conferenceView;
-@property(strong, nonatomic) IBOutlet CallPausedTableView *conferenceCallsTable;
-@property (weak, nonatomic) IBOutlet UIView *waitView;
-@property (weak, nonatomic) IBOutlet UIView *infoView;
-
-- (IBAction)onRoutesClick:(id)sender;
-- (IBAction)onRoutesBluetoothClick:(id)sender;
-- (IBAction)onRoutesEarpieceClick:(id)sender;
-- (IBAction)onRoutesSpeakerClick:(id)sender;
-- (IBAction)onOptionsClick:(id)sender;
-- (IBAction)onOptionsTransferClick:(id)sender;
-- (IBAction)onOptionsAddClick:(id)sender;
-- (IBAction)onOptionsConferenceClick:(id)sender;
-- (IBAction)onNumpadClick:(id)sender;
-- (IBAction)onChatClick:(id)sender;
-- (IBAction)onRecordClick:(id)sender;
-- (IBAction)onRecordOnViewClick:(id)sender;
-
-@end
diff --git a/Classes/CallView.m b/Classes/CallView.m
deleted file mode 100644
index 2ae10de87..000000000
--- a/Classes/CallView.m
+++ /dev/null
@@ -1,1006 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import
-#import
-#import
-#import
-#import
-#import
-#import
-#import "UICallConferenceCell.h"
-
-#import "CallView.h"
-#import "CallSideMenuView.h"
-#import "LinphoneManager.h"
-#import "PhoneMainView.h"
-#import "Utils.h"
-
-#include "linphone/linphonecore.h"
-
-#import "linphoneapp-Swift.h"
-
-const NSInteger SECURE_BUTTON_TAG = 5;
-
-@implementation CallView {
- BOOL hiddenVolume;
-}
-
-#pragma mark - Lifecycle Functions
-
-- (id)init {
- self = [super initWithNibName:NSStringFromClass(self.class) bundle:[NSBundle mainBundle]];
- if (self != nil) {
- singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleControls:)];
- videoZoomHandler = [[VideoZoomHandler alloc] init];
- videoHidden = TRUE;
- [self updateCallView];
- }
- return self;
-}
-
-#pragma mark - UICompositeViewDelegate Functions
-
-static UICompositeViewDescription *compositeDescription = nil;
-
-+ (UICompositeViewDescription *)compositeViewDescription {
- if (compositeDescription == nil) {
- compositeDescription = [[UICompositeViewDescription alloc] init:self.class
- statusBar:StatusBarView.class
- tabBar:nil
- sideMenu:CallSideMenuView.class
- fullscreen:false
- isLeftFragment:YES
- fragmentWith:nil];
- compositeDescription.darkBackground = true;
- }
- return compositeDescription;
-}
-
-- (UICompositeViewDescription *)compositeViewDescription {
- return self.class.compositeViewDescription;
-}
-
-#pragma mark - ViewController Functions
-
-- (void)viewDidLoad {
- [super viewDidLoad];
-
- _routesEarpieceButton.enabled = !IPAD;
-
-// TODO: fixme! video preview frame is too big compared to openGL preview
-// frame, so until this is fixed, temporary disabled it.
-#if 0
- _videoPreview.layer.borderColor = UIColor.whiteColor.CGColor;
- _videoPreview.layer.borderWidth = 1;
-#endif
- [singleFingerTap setNumberOfTapsRequired:1];
- [singleFingerTap setCancelsTouchesInView:FALSE];
- [self.videoView addGestureRecognizer:singleFingerTap];
-
- [videoZoomHandler setup:_videoGroup];
- _videoGroup.alpha = 0;
-
- [_videoCameraSwitch setPreview:_videoPreview];
-
- UIPanGestureRecognizer *dragndrop =
- [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveVideoPreview:)];
- dragndrop.minimumNumberOfTouches = 1;
- [_videoPreview addGestureRecognizer:dragndrop];
-
- [_zeroButton setDigit:'0'];
- [_zeroButton setDtmf:true];
- [_oneButton setDigit:'1'];
- [_oneButton setDtmf:true];
- [_twoButton setDigit:'2'];
- [_twoButton setDtmf:true];
- [_threeButton setDigit:'3'];
- [_threeButton setDtmf:true];
- [_fourButton setDigit:'4'];
- [_fourButton setDtmf:true];
- [_fiveButton setDigit:'5'];
- [_fiveButton setDtmf:true];
- [_sixButton setDigit:'6'];
- [_sixButton setDtmf:true];
- [_sevenButton setDigit:'7'];
- [_sevenButton setDtmf:true];
- [_eightButton setDigit:'8'];
- [_eightButton setDtmf:true];
- [_nineButton setDigit:'9'];
- [_nineButton setDtmf:true];
- [_starButton setDigit:'*'];
- [_starButton setDtmf:true];
- [_hashButton setDigit:'#'];
- [_hashButton setDtmf:true];
-}
-
-- (void)dealloc {
- [PhoneMainView.instance.view removeGestureRecognizer:singleFingerTap];
- // Remove all observer
- [NSNotificationCenter.defaultCenter removeObserver:self];
-}
-
-- (void)viewWillAppear:(BOOL)animated {
- [super viewWillAppear:animated];
- _waitView.hidden = TRUE;
- CallManager.instance.nextCallIsTransfer = FALSE;
-
- callRecording = FALSE;
- _recordButtonOnView.hidden = TRUE;
-
- // Update on show
- [self hideRoutes:TRUE animated:FALSE];
- [self hideOptions:TRUE animated:FALSE];
- [self hidePad:TRUE animated:FALSE];
- [self hideSpeaker:LinphoneManager.instance.bluetoothAvailable];
- [self callDurationUpdate];
- [self onCurrentCallChange];
- // Set windows (warn memory leaks)
- linphone_core_set_native_video_window_id(LC, (__bridge void *)(_videoView));
- linphone_core_set_native_preview_window_id(LC, (__bridge void *)(_videoPreview));
-
- [self previewTouchLift];
- // Enable tap
- [singleFingerTap setEnabled:TRUE];
-
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(messageReceived:)
- name:kLinphoneMessageReceived
- object:nil];
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(bluetoothAvailabilityUpdateEvent:)
- name:kLinphoneBluetoothAvailabilityUpdate
- object:nil];
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(callUpdateEvent:)
- name:kLinphoneCallUpdate
- object:nil];
-
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(participantListChanged:)
- name:kLinphoneConfStateParticipantListChanged
- object:nil];
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(confStateChanged:)
- name:kLinphoneConfStateChanged
- object:nil];
-
-
-
-
- [NSTimer scheduledTimerWithTimeInterval:1
- target:self
- selector:@selector(callDurationUpdate)
- userInfo:nil
- repeats:YES];
-
-}
-
-- (void)viewDidAppear:(BOOL)animated {
- [super viewDidAppear:animated];
-
- [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
- [[UIDevice currentDevice] setProximityMonitoringEnabled:TRUE];
-
- [PhoneMainView.instance setVolumeHidden:TRUE];
- hiddenVolume = TRUE;
-
- // we must wait didAppear to reset fullscreen mode because we cannot change it in viewwillappear
- LinphoneCall *call = linphone_core_get_current_call(LC);
- LinphoneCallState state = (call != NULL) ? linphone_call_get_state(call) : 0;
- [self callUpdate:call state:state animated:FALSE];
-}
-
-- (void)viewWillDisappear:(BOOL)animated {
- [super viewWillDisappear:animated];
-[[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE];
- [self disableVideoDisplay:TRUE animated:NO];
-
- if (hideControlsTimer != nil) {
- [hideControlsTimer invalidate];
- hideControlsTimer = nil;
- }
-
- if (hiddenVolume) {
- [PhoneMainView.instance setVolumeHidden:FALSE];
- hiddenVolume = FALSE;
- }
-
- if (videoDismissTimer) {
- [self dismissVideoActionSheet:videoDismissTimer];
- [videoDismissTimer invalidate];
- videoDismissTimer = nil;
- }
-
- // Remove observer
- [NSNotificationCenter.defaultCenter removeObserver:self];
-}
-
-- (void)viewDidDisappear:(BOOL)animated {
- [super viewDidDisappear:animated];
-
- [[UIApplication sharedApplication] setIdleTimerDisabled:false];
- [[UIDevice currentDevice] setProximityMonitoringEnabled:FALSE];
-
- [PhoneMainView.instance fullScreen:false];
- // Disable tap
- [singleFingerTap setEnabled:FALSE];
-
- if (linphone_core_get_calls_nb(LC) == 0) {
- // reseting speaker button because no more call
- _speakerButton.selected = FALSE;
- }
-
- NSString *address = [LinphoneManager.instance lpConfigStringForKey:@"sas_dialog_denied"];
- if (address) {
- UIConfirmationDialog *securityDialog = [UIConfirmationDialog ShowWithMessage:NSLocalizedString(@"Trust has been denied. Make a call to start the authentication process again.", nil)
- cancelMessage:NSLocalizedString(@"CANCEL", nil)
- confirmMessage:NSLocalizedString(@"CALL", nil)
- onCancelClick:^() {
- }
- onConfirmationClick:^() {
- LinphoneAddress *addr = linphone_address_new(address.UTF8String);
- [CallManager.instance startCallWithAddr:addr isSas:TRUE];
- linphone_address_unref(addr);
- } ];
- [securityDialog.securityImage setImage:[UIImage imageNamed:@"security_alert_indicator.png"]];
- securityDialog.securityImage.hidden = FALSE;
- [securityDialog setSpecialColor];
- [LinphoneManager.instance lpConfigSetString:nil forKey:@"sas_dialog_denied"];
- }
-}
-
-- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
- [super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
- [self updateUnreadMessage:NO];
- [self previewTouchLift];
- [_recordButtonOnView setHidden:!callRecording];
- [self updateCallView];
- LinphoneCall *call = linphone_core_get_current_call(LC) ;
- if (call && linphone_call_get_state(call) == LinphoneCallStatePausedByRemote) {
- _pausedByRemoteView.hidden = NO;
- [self updateInfoView:TRUE];
- }
- _conferenceView.hidden = ![CallManager.instance isInConference];
- [self onCurrentCallChange];
-}
-
-#pragma mark - UI modification
-
-- (void)updateInfoView:(BOOL)pausedByRemote {
- CGRect infoFrame = _infoView.frame;
- if (pausedByRemote || !videoHidden) {
- infoFrame.origin.y = 0;
- } else {
- infoFrame.origin.y = (_avatarImage.frame.origin.y-66)/2;
- }
- _infoView.frame = infoFrame;
-}
-
-- (void)updateCallView {
- CGRect pauseFrame = _callPauseButton.frame;
- CGRect recordFrame = _recordButtonOnView.frame;
- if (videoHidden) {
- pauseFrame.origin.y = _bottomBar.frame.origin.y - pauseFrame.size.height - 60;
- } else {
- pauseFrame.origin.y = _videoCameraSwitch.frame.origin.y+_videoGroup.frame.origin.y;
- }
- recordFrame.origin.y = _bottomBar.frame.origin.y - pauseFrame.size.height - 60;
- _callPauseButton.frame = pauseFrame;
- _recordButtonOnView.frame = recordFrame;
- [self updateInfoView:FALSE];
-}
-
-- (void)hideSpinnerIndicator:(LinphoneCall *)call {
- _videoWaitingForFirstImage.hidden = TRUE;
-}
-
-static void hideSpinner(LinphoneCall *call, void *user_data) {
- CallView *thiz = (__bridge CallView *)user_data;
- [thiz hideSpinnerIndicator:call];
-}
-
-- (void)updateBottomBar:(LinphoneCall *)call state:(LinphoneCallState)state {
- [_speakerButton update];
- [_microButton update];
- [_callPauseButton update];
- [_conferencePauseButton update];
- [_videoButton update];
- [_hangupButton update];
-
- _optionsButton.enabled = (!call || !linphone_core_sound_resources_locked(LC));
- _optionsTransferButton.enabled = call && !linphone_core_sound_resources_locked(LC);
- // enable conference button if 2 calls are presents and at least one is not in the conference
- int confSize = linphone_core_get_conference_size(LC) - ([CallManager.instance isInConference] ? 1 : 0);
- _optionsConferenceButton.enabled = (linphone_core_get_calls_nb(LC) > 1) && (linphone_core_get_calls_nb(LC) != confSize) && !CallManager.instance.hasConferenceAsGuest;
-
- // Disable transfert in conference
- if (linphone_core_get_current_call(LC) == NULL) {
- [_optionsTransferButton setEnabled:FALSE];
- } else {
- [_optionsTransferButton setEnabled:TRUE];
- }
-
- switch (state) {
- case LinphoneCallEnd:
- case LinphoneCallError:
- case LinphoneCallIncoming:
- case LinphoneCallOutgoing:
- [self hidePad:TRUE animated:TRUE];
- [self hideOptions:TRUE animated:TRUE];
- [self hideRoutes:TRUE animated:TRUE];
- default:
- break;
- }
-}
-
-- (void)toggleControls:(id)sender {
- bool controlsHidden = (_bottomBar.alpha == 0.0);
- [self hideControls:!controlsHidden sender:sender];
-}
-
-- (void)timerHideControls:(id)sender {
- [self hideControls:TRUE sender:sender];
-}
-
-- (void)hideControls:(BOOL)hidden sender:(id)sender {
- if (videoHidden && hidden)
- return;
-
- if (hideControlsTimer) {
- [hideControlsTimer invalidate];
- hideControlsTimer = nil;
- }
-
- if ([[PhoneMainView.instance currentView] equal:CallView.compositeViewDescription]) {
- // show controls
- [UIView beginAnimations:nil context:nil];
- [UIView setAnimationDuration:0.35];
- _pausedCallsTable.tableView.alpha = _videoCameraSwitch.alpha = _callPauseButton.alpha = _routesView.alpha =
- _optionsView.alpha = _numpadView.alpha = _bottomBar.alpha = _conferenceView.alpha = (hidden ? 0 : 1);
- _infoView.alpha = (hidden ? 0 : .8f);
-
- if ([CallManager.instance inVideoConf]) {
- _videoCameraSwitch.frame = CGRectMake(_videoCameraSwitch.frame.origin.x, _bottomBar.frame.origin.y - 75, _videoCameraSwitch.frame.size.width,_videoCameraSwitch.frame.size.height);
- }
-
- if (CallManager.instance.isInConference) {
- _callPauseButton.hidden = true;
- _conferenceView.frame = CGRectMake(_conferenceView.frame.origin.x,_conferenceView.frame.origin.y,_conferenceView.frame.size.width,_conferenceCallsTable.tableView.frame.origin.y+[_conferenceCallsTable.tableView numberOfRowsInSection:0]*CONFERENCE_CELL_HEIGHT+10);
- }
-
-
- [UIView commitAnimations];
-
- [PhoneMainView.instance hideTabBar:hidden];
- [PhoneMainView.instance hideStatusBar:hidden];
-
- if (!hidden) {
- // hide controls in 5 sec
- hideControlsTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
- target:self
- selector:@selector(timerHideControls:)
- userInfo:nil
- repeats:NO];
- }
- }
-}
-
-- (void)disableVideoDisplay:(BOOL)disabled animated:(BOOL)animation {
- if (disabled == videoHidden && animation)
- return;
- videoHidden = disabled;
-
- if (!disabled) {
- [videoZoomHandler resetZoom];
- }
- if (animation) {
- [UIView beginAnimations:nil context:nil];
- [UIView setAnimationDuration:1.0];
- }
-
- [_videoGroup setAlpha:disabled ? 0 : 1];
-
- [self hideControls:!disabled sender:nil];
-
- if (animation) {
- [UIView commitAnimations];
- }
-
- // only show camera switch button if we have more than 1 camera
- _videoCameraSwitch.hidden = (disabled || !LinphoneManager.instance.frontCamId);
- _videoPreview.hidden = (disabled || !linphone_core_self_view_enabled(LC));
-
- if (hideControlsTimer != nil) {
- [hideControlsTimer invalidate];
- hideControlsTimer = nil;
- }
-
- if(![PhoneMainView.instance isIphoneXDevice]){
- [PhoneMainView.instance fullScreen:!disabled];
- }
- [PhoneMainView.instance hideTabBar:!disabled];
-
- if (!disabled) {
-#ifdef TEST_VIDEO_VIEW_CHANGE
- [NSTimer scheduledTimerWithTimeInterval:5.0
- target:self
- selector:@selector(_debugChangeVideoView)
- userInfo:nil
- repeats:YES];
-#endif
- // [self batteryLevelChanged:nil];
-
- [_videoWaitingForFirstImage setHidden:NO];
- [_videoWaitingForFirstImage startAnimating];
-
- if ([CallManager.instance inVideoConf])
- [self hideSpinnerIndicator:nil];
- else {
- LinphoneCall *call = linphone_core_get_current_call(LC);
- // linphone_call_params_get_used_video_codec return 0 if no video stream enabled
- if (call != NULL && linphone_call_params_get_used_video_codec(linphone_call_get_current_params(call))) {
- linphone_call_set_next_video_frame_decoded_callback(call, hideSpinner, (__bridge void *)(self));
- }
- }
- }
-
- if ([CallManager.instance isInConference]) {
- [_conferenceView removeFromSuperview];
- [_callView addSubview:_conferenceView];
- } else {
- [_conferenceView removeFromSuperview];
- [self.view addSubview:_conferenceView];
- [self.view sendSubviewToBack:_conferenceView];
- }
-}
-
-- (void)displayVideoCall:(BOOL)animated {
- [self disableVideoDisplay:FALSE animated:animated];
-}
-
-- (void)displayAudioCall:(BOOL)animated {
- [self disableVideoDisplay:TRUE animated:animated];
-}
-
-- (void)callDurationUpdate {
- int duration =
- linphone_core_get_current_call(LC) ? linphone_call_get_duration(linphone_core_get_current_call(LC)) : 0;
- _durationLabel.text = [LinphoneUtils durationToString:duration];
-
- [_pausedCallsTable update];
- [_conferenceCallsTable update];
-}
-
-- (void)onCurrentCallChange {
- LinphoneCall *call = linphone_core_get_current_call(LC);
-
- _noActiveCallView.hidden = (call || CallManager.instance.isInConference);
- _callView.hidden = !call && !CallManager.instance.isInConference;
- _conferenceView.hidden = !CallManager.instance.isInConference;
- _conferenceView.hidden = !CallManager.instance.isInConference;
- _callPauseButton.hidden = !call;
-
-
- [_callPauseButton setType:UIPauseButtonType_CurrentCall call:call];
- [_conferencePauseButton setType:UIPauseButtonType_Conference call:call];
-
- if (call) {
- const LinphoneAddress *addr = linphone_call_get_remote_address(call);
- [ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
- char *uri = linphone_address_as_string_uri_only(addr);
- ms_free(uri);
- [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:YES withRoundedRadius:YES];
- }
-}
-
-- (void)hidePad:(BOOL)hidden animated:(BOOL)animated {
- if (hidden) {
- [_numpadButton setOff];
- } else {
- [_numpadButton setOn];
- }
- if (hidden != _numpadView.hidden) {
- if (animated) {
- [self hideAnimation:hidden forView:_numpadView completion:nil];
- } else {
- [_numpadView setHidden:hidden];
- }
- }
-}
-
-- (void)hideRoutes:(BOOL)hidden animated:(BOOL)animated {
- if (hidden) {
- [_routesButton setOff];
- } else {
- [_routesButton setOn];
- }
-
- _routesBluetoothButton.selected = [CallManager.instance isBluetoothEnabled];
- _routesSpeakerButton.selected = [CallManager.instance isSpeakerEnabled];
- _routesEarpieceButton.selected = !_routesBluetoothButton.selected && !_routesSpeakerButton.selected;
-
- if (hidden != _routesView.hidden) {
- if (animated) {
- [self hideAnimation:hidden forView:_routesView completion:nil];
- } else {
- [_routesView setHidden:hidden];
- }
- }
-}
-
-- (void)hideOptions:(BOOL)hidden animated:(BOOL)animated {
- if (hidden) {
- [_optionsButton setOff];
- } else {
- [_optionsButton setOn];
- }
- if (hidden != _optionsView.hidden) {
- if (animated) {
- [self hideAnimation:hidden forView:_optionsView completion:nil];
- } else {
- [_optionsView setHidden:hidden];
- }
- }
-}
-
-- (void)hideSpeaker:(BOOL)hidden {
- _speakerButton.hidden = hidden;
- _routesButton.hidden = !hidden;
-}
-
-#pragma mark - Event Functions
-
-- (void)bluetoothAvailabilityUpdateEvent:(NSNotification *)notif {
- bool available = [[notif.userInfo objectForKey:@"available"] intValue];
- dispatch_async(dispatch_get_main_queue(), ^{
- [self hideSpeaker:available];
- });
-}
-
-- (void)callUpdateEvent:(NSNotification *)notif {
- LinphoneCall *call = [[notif.userInfo objectForKey:@"call"] pointerValue];
- LinphoneCallState state = [[notif.userInfo objectForKey:@"state"] intValue];
- [self callUpdate:call state:state animated:TRUE];
-}
-
-- (void)callUpdate:(LinphoneCall *)call state:(LinphoneCallState)state animated:(BOOL)animated {
- [self updateBottomBar:call state:state];
- if (hiddenVolume) {
- [PhoneMainView.instance setVolumeHidden:FALSE];
- hiddenVolume = FALSE;
- }
-
- // Update tables
- [_pausedCallsTable update];
- [_conferenceCallsTable update];
-
- [self onCurrentCallChange];
-
- LinphoneCall *currentCall = linphone_core_get_current_call(LC);
- BOOL shouldDisableVideo = currentCall ? !linphone_call_params_video_enabled(linphone_call_get_current_params(currentCall)): ![CallManager.instance inVideoConf];
-
-
- if (videoHidden != shouldDisableVideo) {
- if (!shouldDisableVideo) {
- [self displayVideoCall:animated];
- } else {
- [self displayAudioCall:animated];
- }
- }
-
- // Fake call update
- if (call == NULL) {
- return;
- }
-
- if (state != LinphoneCallPausedByRemote) {
- _pausedByRemoteView.hidden = YES;
- }
-
- switch (state) {
- case LinphoneCallIncomingReceived:
- case LinphoneCallOutgoingInit:
- case LinphoneCallConnected:
- case LinphoneCallStreamsRunning: {
- // check video, because video can be disabled because of the low bandwidth.
- if (!linphone_call_params_video_enabled(linphone_call_get_current_params(call))) {
- const LinphoneCallParams *param = linphone_call_get_current_params(call);
- CallAppData *data = [CallManager getAppDataWithCall:call];
- if (state == LinphoneCallStreamsRunning && data && data.videoRequested && linphone_call_params_low_bandwidth_enabled(param)) {
- // too bad video was not enabled because low bandwidth
- UIAlertController *errView = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Low bandwidth", nil)
- message:NSLocalizedString(@"Video cannot be activated because of low bandwidth "
- @"condition, only audio is available",
- nil)
- preferredStyle:UIAlertControllerStyleAlert];
-
- UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Continue", nil)
- style:UIAlertActionStyleDefault
- handler:^(UIAlertAction * action) {}];
-
- [errView addAction:defaultAction];
- [self presentViewController:errView animated:YES completion:nil];
- data.videoRequested = FALSE;
- [CallManager setAppDataWithCall:call appData:data];
- }
- }
- break;
- }
- case LinphoneCallUpdatedByRemote: {
- const LinphoneCallParams *current = linphone_call_get_current_params(call);
- const LinphoneCallParams *remote = linphone_call_get_remote_params(call);
-
- /* remote wants to add video */
- if ((linphone_core_video_display_enabled(LC) && !linphone_call_params_video_enabled(current) &&
- linphone_call_params_video_enabled(remote)) &&
- (!linphone_core_get_video_policy(LC)->automatically_accept ||
- (([UIApplication sharedApplication].applicationState != UIApplicationStateActive) &&
- floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max))) {
- linphone_core_defer_call_update(LC, call);
- [self displayAskToEnableVideoCall:call];
- } else if (linphone_call_params_video_enabled(current) && !linphone_call_params_video_enabled(remote) && ![CallManager.instance inVideoConf]) {
- [self displayAudioCall:animated];
- }
- break;
- }
- case LinphoneCallPausing:
- case LinphoneCallPaused:
- [self displayAudioCall:animated];
- break;
- case LinphoneCallPausedByRemote:
- if (![CallManager.instance inVideoConf]) {
- [self displayAudioCall:animated];
- if (call == linphone_core_get_current_call(LC)) {
- _pausedByRemoteView.hidden = NO;
- [self updateInfoView:TRUE];
- }
- }
- break;
- case LinphoneCallEnd:
- case LinphoneCallError:
- default:
- break;
- }
-}
-
-#pragma mark - ActionSheet Functions
-
-- (void)displayAskToEnableVideoCall:(LinphoneCall *)call {
-
- if (CallManager.instance.inVideoConf) { // we are hosting a video conf, so just accept people wanting to activate video.
- LinphoneCallParams *params = linphone_core_create_call_params(LC, call);
- linphone_call_params_enable_video(params, TRUE);
- linphone_call_accept_update(call, params);
- return;
- }
-
- if (linphone_core_get_video_policy(LC)->automatically_accept &&
- !([UIApplication sharedApplication].applicationState != UIApplicationStateActive))
- return;
-
- NSString *username = [FastAddressBook displayNameForAddress:linphone_call_get_remote_address(call)];
- NSString *title = [NSString stringWithFormat:NSLocalizedString(@"%@ would like to enable video", nil), username];
- if ([UIApplication sharedApplication].applicationState != UIApplicationStateActive &&
- floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) {
- UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
- content.title = NSLocalizedString(@"Video request", nil);
- content.body = title;
- content.categoryIdentifier = @"video_request";
- content.userInfo = @{
- @"CallId" : [NSString stringWithUTF8String:linphone_call_log_get_call_id(linphone_call_get_call_log(call))]
- };
-
- UNNotificationRequest *req =
- [UNNotificationRequest requestWithIdentifier:@"video_request" content:content trigger:NULL];
- [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:req
- withCompletionHandler:^(NSError *_Nullable error) {
- // Enable or disable features based on authorization.
- if (error) {
- LOGD(@"Error while adding notification request :");
- LOGD(error.description);
- }
- }];
- } else {
- UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:title
- cancelMessage:nil
- confirmMessage:NSLocalizedString(@"ACCEPT", nil)
- onCancelClick:^() {
- LOGI(@"User declined video proposal");
- if (call == linphone_core_get_current_call(LC)) {
- [CallManager.instance acceptVideoWithCall:call confirm:FALSE];
- [videoDismissTimer invalidate];
- videoDismissTimer = nil;
- }
- }
- onConfirmationClick:^() {
- LOGI(@"User accept video proposal");
- if (call == linphone_core_get_current_call(LC)) {
- [CallManager.instance acceptVideoWithCall:call confirm:TRUE];
- [videoDismissTimer invalidate];
- videoDismissTimer = nil;
- }
- }
- inController:self];
- videoDismissTimer = [NSTimer scheduledTimerWithTimeInterval:30
- target:self
- selector:@selector(dismissVideoActionSheet:)
- userInfo:sheet
- repeats:NO];
- }
-}
-
-- (void)dismissVideoActionSheet:(NSTimer *)timer {
- UIConfirmationDialog *sheet = (UIConfirmationDialog *)timer.userInfo;
- [sheet dismiss];
-}
-
-#pragma mark VideoPreviewMoving
-
-- (void)moveVideoPreview:(UIPanGestureRecognizer *)dragndrop {
- CGPoint center = [dragndrop locationInView:_videoPreview.superview];
- _videoPreview.center = center;
- if (dragndrop.state == UIGestureRecognizerStateEnded) {
- [self previewTouchLift];
- }
-}
-
-- (CGFloat)coerce:(CGFloat)value betweenMin:(CGFloat)min andMax:(CGFloat)max {
- return MAX(min, MIN(value, max));
-}
-
-- (void)previewTouchLift {
- CGRect previewFrame = _videoPreview.frame;
- previewFrame.origin.x = [self coerce:previewFrame.origin.x
- betweenMin:5
- andMax:(UIScreen.mainScreen.bounds.size.width - 5 - previewFrame.size.width)];
- previewFrame.origin.y = [self coerce:previewFrame.origin.y
- betweenMin:5
- andMax:(UIScreen.mainScreen.bounds.size.height - 5 - previewFrame.size.height)];
-
- if (!CGRectEqualToRect(previewFrame, _videoPreview.frame)) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [UIView animateWithDuration:0.3
- animations:^{
- LOGD(@"Recentering preview to %@", NSStringFromCGRect(previewFrame));
- _videoPreview.frame = previewFrame;
- }];
- });
- }
-}
-
-#pragma mark - Action Functions
-
-- (IBAction)onNumpadClick:(id)sender {
- if ([_numpadView isHidden]) {
- [self hidePad:FALSE animated:ANIMATED];
- } else {
- [self hidePad:TRUE animated:ANIMATED];
- }
-}
-
-- (IBAction)onChatClick:(id)sender {
- const LinphoneCall *currentCall = linphone_core_get_current_call(LC);
- const LinphoneAddress *addr = currentCall ? linphone_call_get_remote_address(currentCall) : NULL;
- // TODO encrpted or unencrpted
- [LinphoneManager.instance lpConfigSetBool:TRUE forKey:@"create_chat"];
- [PhoneMainView.instance getOrCreateOneToOneChatRoom:addr waitView:_waitView isEncrypted:FALSE];
-}
-
-- (IBAction)onRecordClick:(id)sender {
- if (![_optionsView isHidden])
- [self hideOptions:TRUE animated:ANIMATED];
- if (callRecording) {
- [self onRecordOnViewClick:nil];
- } else {
- LOGD(@"Recording Starts");
-
- [_recordButton setImage:[UIImage imageNamed:@"rec_off_default.png"] forState:UIControlStateNormal];
- [_recordButtonOnView setHidden:FALSE];
-
- LinphoneCall *call = linphone_core_get_current_call(LC);
- linphone_call_start_recording(call);
-
- callRecording = TRUE;
- }
-}
-
-- (IBAction)onRecordOnViewClick:(id)sender {
- LOGD(@"Recording Stops");
- [_recordButton setImage:[UIImage imageNamed:@"rec_on_default.png"] forState:UIControlStateNormal];
- [_recordButtonOnView setHidden:TRUE];
-
- LinphoneCall *call = linphone_core_get_current_call(LC);
- linphone_call_stop_recording(call);
-
- callRecording = FALSE;
-
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
- NSString *writablePath = [paths objectAtIndex:0];
- writablePath = [writablePath stringByAppendingString:@"/"];
- NSArray *directoryContent = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:writablePath error:NULL];
- if (directoryContent) {
- return;
- }
-}
-
-- (IBAction)onRoutesBluetoothClick:(id)sender {
- [self hideRoutes:TRUE animated:TRUE];
- [CallManager.instance changeRouteToBluetooth];
-}
-
-- (IBAction)onRoutesEarpieceClick:(id)sender {
- [self hideRoutes:TRUE animated:TRUE];
- [CallManager.instance changeRouteToDefault];
-}
-
-- (IBAction)onRoutesSpeakerClick:(id)sender {
- [self hideRoutes:TRUE animated:TRUE];
- [CallManager.instance changeRouteToSpeaker];
-}
-
-- (IBAction)onRoutesClick:(id)sender {
- if ([_routesView isHidden]) {
- [self hideRoutes:FALSE animated:ANIMATED];
- } else {
- [self hideRoutes:TRUE animated:ANIMATED];
- }
-}
-
-- (IBAction)onOptionsClick:(id)sender {
- int confSize = linphone_core_get_conference_size(LC) - (CallManager.instance.isInConference ? 1 : 0);
- _optionsConferenceButton.enabled = (linphone_core_get_calls_nb(LC) > 1) && (linphone_core_get_calls_nb(LC) != confSize) && !CallManager.instance.hasConferenceAsGuest;
-
- if ([_optionsView isHidden]) {
- [self hideOptions:FALSE animated:ANIMATED];
- } else {
- [self hideOptions:TRUE animated:ANIMATED];
- }
-}
-
-- (IBAction)onOptionsTransferClick:(id)sender {
- [self hideOptions:TRUE animated:TRUE];
- DialerView *view = VIEW(DialerView);
- [view setAddress:@""];
- CallManager.instance.nextCallIsTransfer = TRUE;
- [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
-}
-
-- (IBAction)onOptionsAddClick:(id)sender {
- [self hideOptions:TRUE animated:TRUE];
- DialerView *view = VIEW(DialerView);
- [view setAddress:@""];
- CallManager.instance.nextCallIsTransfer = FALSE;
- [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
-}
-
-- (IBAction)onOptionsConferenceClick:(id)sender {
- [self hideOptions:TRUE animated:TRUE];
- [CallManager.instance groupCall];
-}
-
-#pragma mark - Animation
-
-- (void)hideAnimation:(BOOL)hidden forView:(UIView *)target completion:(void (^)(BOOL finished))completion {
- if (hidden) {
- int original_y = target.frame.origin.y;
- CGRect newFrame = target.frame;
- newFrame.origin.y = self.view.frame.size.height;
- [UIView animateWithDuration:0.5
- delay:0.0
- options:UIViewAnimationOptionCurveEaseIn
- animations:^{
- target.frame = newFrame;
- }
- completion:^(BOOL finished) {
- CGRect originFrame = target.frame;
- originFrame.origin.y = original_y;
- target.hidden = YES;
- target.frame = originFrame;
- if (completion)
- completion(finished);
- }];
- } else {
- CGRect frame = target.frame;
- int original_y = frame.origin.y;
- frame.origin.y = self.view.frame.size.height;
- target.frame = frame;
- frame.origin.y = original_y;
- target.hidden = NO;
-
- [UIView animateWithDuration:0.5
- delay:0.0
- options:UIViewAnimationOptionCurveEaseOut
- animations:^{
- target.frame = frame;
- }
- completion:^(BOOL finished) {
- target.frame = frame; // in case application did not finish
- if (completion)
- completion(finished);
- }];
- }
-}
-
-#pragma mark - Bounce
-- (void)messageReceived:(NSNotification *)notif {
- [self updateUnreadMessage:TRUE];
-}
-- (void)updateUnreadMessage:(BOOL)appear {
- int unreadMessage = [LinphoneManager unreadMessageCount];
- if (unreadMessage > 0) {
- _chatNotificationLabel.text = [NSString stringWithFormat:@"%i", unreadMessage];
- [_chatNotificationView startAnimating:appear];
- } else {
- [_chatNotificationView stopAnimating:appear];
- }
-}
-
-#pragma mark - Conference
-
-- (void)participantListChanged:(NSNotification *)notif {
- [self confStateChanged:nil];
- [_conferenceCallsTable update];
- _conferenceView.frame = CGRectMake(_conferenceView.frame.origin.x,_conferenceView.frame.origin.y,_conferenceView.frame.size.width,_conferenceCallsTable.tableView.frame.origin.y+[_conferenceCallsTable.tableView numberOfRowsInSection:0]*CONFERENCE_CELL_HEIGHT+10);
- [self onCurrentCallChange];
- _conferenceView.hidden = !CallManager.instance.isInConference;
-}
-
-- (void)confStateChanged:(NSNotification *)notif {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- if ([CallManager.instance inVideoConf]) {
- [self displayVideoCall:true];
- } else if (CallManager.instance.isInConference) {
- [self displayAudioConference];
- } else {
- [self displayAudioCall:true];
- _callPauseButton.hidden = NO;
- _nameLabel.hidden = NO;
- _durationLabel.hidden = NO;
- _avatarImage.hidden = NO;
- }
- [_conferenceCallsTable update];
- _conferenceView.frame = CGRectMake(_conferenceView.frame.origin.x,_conferenceView.frame.origin.y,_conferenceView.frame.size.width,_conferenceCallsTable.tableView.frame.origin.y+[_conferenceCallsTable.tableView numberOfRowsInSection:0]*CONFERENCE_CELL_HEIGHT+10);
- });
-}
-
--(void) displayAudioConference {
- _callPauseButton.hidden = true;
- _nameLabel.hidden = true;
- _conferenceView.frame = CGRectMake(_conferenceView.frame.origin.x,_conferenceView.frame.origin.y,_conferenceView.frame.size.width,_conferenceCallsTable.tableView.frame.origin.y+[_conferenceCallsTable.tableView numberOfRowsInSection:0]*CONFERENCE_CELL_HEIGHT+10);
- _durationLabel.hidden = true;
- _avatarImage.hidden = true;
-
- [_conferenceView removeFromSuperview];
- [_callView addSubview:_conferenceView];
-
- if ([CallManager.instance isInConference]) {
- [_conferenceView removeFromSuperview];
- [_callView addSubview:_conferenceView];
- _conferenceView.hidden = NO;
- } else {
- [_conferenceView removeFromSuperview];
- [self.view addSubview:_conferenceView];
- [self.view sendSubviewToBack:_conferenceView];
- }
-}
-
-
-
-@end
diff --git a/Classes/ChatConversationImdnView.h b/Classes/ChatConversationImdnView.h
index 78a9042b6..26607f58d 100644
--- a/Classes/ChatConversationImdnView.h
+++ b/Classes/ChatConversationImdnView.h
@@ -24,6 +24,7 @@
#import "UICompositeView.h"
#import "UIRoundBorderedButton.h"
+#import "UIChatBubbleTextCell.h"
@interface ChatConversationImdnView : UIViewController
{
diff --git a/Classes/ChatConversationView.h b/Classes/ChatConversationView.h
index 7c76cb286..7d8fd00dc 100644
--- a/Classes/ChatConversationView.h
+++ b/Classes/ChatConversationView.h
@@ -32,7 +32,6 @@
#import "UIImageViewDeletable.h"
#import "UIConfirmationDialog.h"
#import "UIInterfaceStyleButton.h"
-#import "linphoneapp-Swift.h"
#import "UIChatReplyBubbleView.h"
#include "linphone/linphonecore.h"
diff --git a/Classes/ChatConversationView.m b/Classes/ChatConversationView.m
index 40e86856f..6ae1f25b4 100644
--- a/Classes/ChatConversationView.m
+++ b/Classes/ChatConversationView.m
@@ -28,6 +28,8 @@
#import "SVProgressHUD.h"
#import "EphemeralSettingsView.h"
#import "Utils.h"
+#import "linphoneapp-Swift.h"
+
@implementation FileContext
@@ -1853,7 +1855,7 @@ void on_chat_room_conference_alert(LinphoneChatRoom *cr, const LinphoneEventLog
NSDictionary* userInfo = @{@"path": [NSString stringWithUTF8String:linphone_player_get_user_data(_sharedVoicePlayer)]};
[NSNotificationCenter.defaultCenter postNotificationName:kLinphoneVoiceMessagePlayerLostFocus object:nil userInfo:userInfo];
}
- [CallManager.instance changeRouteToSpeaker];
+ [AudioRouteUtils routeAudioToSpeaker];
linphone_player_set_user_data(_sharedVoicePlayer, (void *)path);
linphone_player_open(_sharedVoicePlayer, path);
linphone_player_start(_sharedVoicePlayer);
diff --git a/Classes/Contact.m b/Classes/Contact.m
index fd27e9078..03a7e4174 100644
--- a/Classes/Contact.m
+++ b/Classes/Contact.m
@@ -120,6 +120,7 @@
return nil;
}
+
- (NSString *)displayName {
if (_friend) {
const char *friend_name = linphone_friend_get_name(_friend);
diff --git a/Classes/DevicesListView.m b/Classes/DevicesListView.m
index 4913b6173..60e005dc9 100644
--- a/Classes/DevicesListView.m
+++ b/Classes/DevicesListView.m
@@ -20,6 +20,8 @@
#import "DevicesListView.h"
#import "PhoneMainView.h"
#import "UIDeviceCell.h"
+#import "linphoneapp-Swift.h"
+
@implementation DevicesMenuEntry
- (id)init:(LinphoneParticipantDevice *)dev isMe:(BOOL)isMe isFirst:(BOOL)first isUnique:(BOOL)unique index:(NSInteger)idx{
diff --git a/Classes/DialerView.m b/Classes/DialerView.m
index df1a5d70f..d24f91ee2 100644
--- a/Classes/DialerView.m
+++ b/Classes/DialerView.m
@@ -21,6 +21,7 @@
#import "LinphoneManager.h"
#import "PhoneMainView.h"
+#import "linphoneapp-Swift.h"
@implementation DialerView
@@ -397,7 +398,7 @@ static UICompositeViewDescription *compositeDescription = nil;
}
- (IBAction)onBackClick:(id)event {
- [PhoneMainView.instance popToView:CallView.compositeViewDescription];
+ [PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
}
- (IBAction)onAddressChange:(id)sender {
diff --git a/Classes/ImagePickerView.m b/Classes/ImagePickerView.m
index ab1850f74..d4e4cebb7 100644
--- a/Classes/ImagePickerView.m
+++ b/Classes/ImagePickerView.m
@@ -301,96 +301,83 @@ static UICompositeViewDescription *compositeDescription = nil;
inView:(UIView *)ipadView
withDocumentMenuDelegate:(id)documentMenuDelegate {
void (^block)(UIImagePickerControllerSourceType) = ^(UIImagePickerControllerSourceType type) {
- ImagePickerView *view = VIEW(ImagePickerView);
- view.sourceType = type;
-
- // Displays a control that allows the user to choose picture or
- // movie capture, if both are available:
- view.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie,(NSString *)kUTTypeImage,nil];
-
- // Hides the controls for moving & scaling pictures, or for
- // trimming movies. To instead show the controls, use YES.
- view.allowsEditing = NO;
- view.imagePickerDelegate = delegate;
-
- if (IPAD && ipadView && ipadPopoverView) {
- UIView *iterview = ipadPopoverView;
- CGRect ipadPopoverPosition = iterview.frame;
- do {
- ipadPopoverPosition =
- [iterview.superview convertRect:ipadPopoverPosition toView:iterview.superview.superview];
- iterview = iterview.superview;
- } while (iterview && iterview.superview != ipadView);
- [view.popoverController presentPopoverFromRect:ipadPopoverPosition
- inView:ipadView
- permittedArrowDirections:UIPopoverArrowDirectionAny
- animated:FALSE];
- } else {
- [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
- }
- };
- if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
- DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
- if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
- [sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
- block:^() {
- if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
- [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
- return;
- }
- block(UIImagePickerControllerSourceTypeCamera);
- }];
- }
- if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
- [sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
- block:^() {
- block(UIImagePickerControllerSourceTypePhotoLibrary);
- }];
- }
+ ImagePickerView *view = VIEW(ImagePickerView);
+ view.sourceType = type;
- if (documentMenuDelegate) {
- [sheet addButtonWithTitle:NSLocalizedString(@"Document",nil) block:^(){
- [self pickDocumentForDelegate:documentMenuDelegate];
- }];
+ // Displays a control that allows the user to choose picture or
+ // movie capture, if both are available:
+ view.mediaTypes = [NSArray arrayWithObjects:(NSString *)kUTTypeMovie,(NSString *)kUTTypeImage,nil];
+
+ // Hides the controls for moving & scaling pictures, or for
+ // trimming movies. To instead show the controls, use YES.
+ view.allowsEditing = NO;
+ view.imagePickerDelegate = delegate;
+
+ if (IPAD && ipadView && ipadPopoverView) {
+ UIView *iterview = ipadPopoverView;
+ CGRect ipadPopoverPosition = iterview.frame;
+ do {
+ ipadPopoverPosition =
+ [iterview.superview convertRect:ipadPopoverPosition toView:iterview.superview.superview];
+ iterview = iterview.superview;
+ } while (iterview && iterview.superview != ipadView);
+ [view.popoverController presentPopoverFromRect:ipadPopoverPosition
+ inView:ipadView
+ permittedArrowDirections:UIPopoverArrowDirectionAny
+ animated:FALSE];
+ } else {
+ [PhoneMainView.instance changeCurrentView:view.compositeViewDescription];
}
- [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
-
- [sheet showInView:PhoneMainView.instance.view];
- } else {
- [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
- dispatch_async(dispatch_get_main_queue(), ^{
- if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
- DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
- if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
- [sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
- block:^() {
- if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
- [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
- return;
- }
- block(UIImagePickerControllerSourceTypeCamera);
- }];
- }
- if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
- [sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
- block:^() {
- block(UIImagePickerControllerSourceTypePhotoLibrary);
- }];
- }
- if (documentMenuDelegate) {
- [sheet addButtonWithTitle:NSLocalizedString(@"Document",nil) block:^(){
- [self pickDocumentForDelegate:documentMenuDelegate];
- }];
- }
- [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
-
- [sheet showInView:PhoneMainView.instance.view];
- } else {
- [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
- }
- });
- }];
- }
+ };
+ DTActionSheet *sheet = [[DTActionSheet alloc] initWithTitle:NSLocalizedString(@"Select the source", nil)];
+ if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
+ [sheet addButtonWithTitle:NSLocalizedString(@"Camera", nil)
+ block:^() {
+ if([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo] != AVAuthorizationStatusAuthorized ) {
+ [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Camera's permission", nil) message:NSLocalizedString(@"Camera not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Continue", nil] show];
+ return;
+ }
+ if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized)
+ block(UIImagePickerControllerSourceTypeCamera);
+ else {
+ [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
+ block(UIImagePickerControllerSourceTypeCamera);
+ } else {
+ [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] show];
+ }
+ });
+ }];
+ }
+ }];
+ }
+ if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
+ [sheet addButtonWithTitle:NSLocalizedString(@"Photo library", nil)
+ block:^() {
+ if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized)
+ block(UIImagePickerControllerSourceTypePhotoLibrary);
+ else {
+ [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
+ block(UIImagePickerControllerSourceTypePhotoLibrary);
+ } else {
+ [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Photo's permission", nil) message:NSLocalizedString(@"Photo not authorized", nil) delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil] show];
+ }
+ });
+ }];
+ }
+ }];
+ }
+ if (documentMenuDelegate) {
+ [sheet addButtonWithTitle:NSLocalizedString(@"Document",nil) block:^(){
+ [self pickDocumentForDelegate:documentMenuDelegate];
+ }];
+ }
+ [sheet addCancelButtonWithTitle:NSLocalizedString(@"Cancel", nil) block:nil];
+
+ [sheet showInView:PhoneMainView.instance.view];
}
+(void) pickDocumentForDelegate:(id)documentMenuDelegate {
diff --git a/Classes/LinphoneAppDelegate.h b/Classes/LinphoneAppDelegate.h
index d3820267b..9a4768d11 100644
--- a/Classes/LinphoneAppDelegate.h
+++ b/Classes/LinphoneAppDelegate.h
@@ -23,7 +23,6 @@
#import
#import
#import
-#import "linphoneapp-Swift.h"
@interface LinphoneAppDelegate : NSObject {
diff --git a/Classes/LinphoneAppDelegate.m b/Classes/LinphoneAppDelegate.m
index be99479de..1d4c152c3 100644
--- a/Classes/LinphoneAppDelegate.m
+++ b/Classes/LinphoneAppDelegate.m
@@ -140,7 +140,7 @@
if ((floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max)) {
if ([LinphoneManager.instance lpConfigBoolForKey:@"autoanswer_notif_preference"]) {
linphone_call_accept(call);
- [PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
+ [PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
} else {
[PhoneMainView.instance displayIncomingCall:call];
}
@@ -321,6 +321,8 @@
return NO;
}
+ VIEW(ActiveCallOrConferenceView); // to get created and all observers added
+
return YES;
}
@@ -409,16 +411,9 @@
}
// used for callkit. Called when active video.
-- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler
-{
+- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler {
-
- if ([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
- LOGI(@"CallKit: satrt video.");
- CallView *view = VIEW(CallView);
- [view.videoButton setOn];
- }
- if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) { // tel URI handler.
+ if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]||[userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) { // tel URI handler.
INInteraction *interaction = userActivity.interaction;
INStartAudioCallIntent *startAudioCallIntent = (INStartAudioCallIntent *)interaction.intent;
INPerson *contact = startAudioCallIntent.contacts[0];
@@ -537,8 +532,7 @@
if ([response.actionIdentifier isEqual:@"Answer"]) {
// use the standard handler
- [PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
- linphone_call_accept(call);
+ [CallManager.instance acceptCallWithCall:call hasVideo:NO];
} else if ([response.actionIdentifier isEqual:@"Decline"]) {
linphone_call_decline(call, LinphoneReasonDeclined);
} else if ([response.actionIdentifier isEqual:@"Reply"]) {
@@ -575,7 +569,6 @@
return;
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
- [PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
[CallManager.instance acceptVideoWithCall:call confirm:TRUE];
} else if ([response.actionIdentifier isEqual:@"Confirm"]) {
if (linphone_core_get_current_call(LC) == call)
@@ -608,7 +601,7 @@
}
} else if ([response.notification.request.content.categoryIdentifier isEqual:@"video_request"]) {
if (!call) return;
- [PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
+ [PhoneMainView.instance changeCurrentView:ActiveCallOrConferenceView.compositeViewDescription];
NSTimer *videoDismissTimer = nil;
UIConfirmationDialog *sheet = [UIConfirmationDialog ShowWithMessage:response.notification.request.content.body
cancelMessage:nil
@@ -692,8 +685,7 @@
if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) {
// use the standard handler
- [PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
- linphone_call_accept(call);
+ [CallManager.instance acceptCallWithCall:call hasVideo:NO];
} else if ([identifier isEqualToString:@"decline"]) {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call)
@@ -730,8 +722,7 @@
if ([notification.category isEqualToString:@"incoming_call"]) {
if ([identifier isEqualToString:@"answer"]) {
// use the standard handler
- [PhoneMainView.instance changeCurrentView:CallView.compositeViewDescription];
- linphone_call_accept(call);
+ [CallManager.instance acceptCallWithCall:call hasVideo:NO];
} else if ([identifier isEqualToString:@"decline"]) {
LinphoneCall *call = linphone_core_get_current_call(LC);
if (call)
diff --git a/Classes/LinphoneCoreSettingsStore.m b/Classes/LinphoneCoreSettingsStore.m
index 219841b65..077c5e84b 100644
--- a/Classes/LinphoneCoreSettingsStore.m
+++ b/Classes/LinphoneCoreSettingsStore.m
@@ -25,6 +25,8 @@
#include "linphone/lpconfig.h"
#include
#include
+#import "linphoneapp-Swift.h"
+
@implementation LinphoneCoreSettingsStore
diff --git a/Classes/LinphoneManager.h b/Classes/LinphoneManager.h
index 53be84967..f0027fb6f 100644
--- a/Classes/LinphoneManager.h
+++ b/Classes/LinphoneManager.h
@@ -35,7 +35,6 @@
#include "bctoolbox/list.h"
#import "OrderedDictionary.h"
-#import "linphoneapp-Swift.h"
extern NSString *const LINPHONERC_APPLICATION_KEY;
@@ -49,7 +48,6 @@ extern NSString *const kLinphoneMainViewChange;
extern NSString *const kLinphoneAddressBookUpdate;
extern NSString *const kLinphoneLogsUpdate;
extern NSString *const kLinphoneSettingsUpdate;
-extern NSString *const kLinphoneBluetoothAvailabilityUpdate;
extern NSString *const kLinphoneConfiguringStateUpdate;
extern NSString *const kLinphoneGlobalStateUpdate;
extern NSString *const kLinphoneNotifyReceived;
@@ -204,7 +202,6 @@ typedef struct _LinphoneManagerSounds {
@property (readonly) sqlite3* database;
@property (readonly) LinphoneManagerSounds sounds;
@property (readonly) NSMutableArray *logs;
-@property (nonatomic, assign) BOOL bluetoothAvailable;
@property (readonly) NSString* contactSipField;
@property (readonly,copy) NSString* contactFilter;
@property (copy) void (^silentPushCompletion)(UIBackgroundFetchResult);
diff --git a/Classes/LinphoneManager.m b/Classes/LinphoneManager.m
index fc2a91d95..a04fd5220 100644
--- a/Classes/LinphoneManager.m
+++ b/Classes/LinphoneManager.m
@@ -31,7 +31,6 @@
#import "LinphoneCoreSettingsStore.h"
#import "LinphoneAppDelegate.h"
#import "LinphoneManager.h"
-#import "Utils/AudioHelper.h"
#import "Utils/FileTransferDelegate.h"
#include "linphone/factory.h"
@@ -46,6 +45,7 @@
#import "ChatsListView.h"
#import "ChatConversationView.h"
#import
+#import "linphoneapp-Swift.h"
#define LINPHONE_LOGS_MAX_ENTRY 5000
@@ -233,11 +233,7 @@ struct codec_name_pref_table codec_pref_table[] = {{"speex", 8000, "speex_8k_pre
- (id)init {
if ((self = [super init])) {
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
-
+
NSString *path = [[NSBundle mainBundle] pathForResource:@"msg" ofType:@"wav"];
self.messagePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL URLWithString:path] error:nil];
@@ -1767,20 +1763,7 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
_configDb = linphone_config_new_for_shared_core(kLinphoneMsgNotificationAppGroupId.UTF8String, @"linphonerc".UTF8String, factory.UTF8String);
linphone_config_clean_entry(_configDb, "misc", "max_calls");
}
-#pragma mark - Audio route Functions
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notif {
- if (IPAD)
- return;
-
- _bluetoothAvailable = [CallManager.instance isBluetoothAvailable];
-
- NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:_bluetoothAvailable], @"available", nil];
- [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneBluetoothAvailabilityUpdate
- object:self
- userInfo:dict];
-
-}
#pragma mark - Call Functions
- (void)send:(NSString *)replyText toChatRoom:(LinphoneChatRoom *)room {
@@ -2162,7 +2145,6 @@ static int comp_call_state_paused(const LinphoneCall *call, const void *param) {
if ([ct currentCalls] != nil) {
if (call) {
LOGI(@"Pausing SIP call because GSM call");
- CallManager.instance.speakerBeforePause = [CallManager.instance isSpeakerEnabled];
linphone_call_pause(call);
[self startCallPausedLongRunningTask];
} else if (linphone_core_is_in_conference(theLinphoneCore)) {
diff --git a/Classes/LinphoneUI/Base.lproj/StatusBarView.xib b/Classes/LinphoneUI/Base.lproj/StatusBarView.xib
index 0830037cb..341bcf9a8 100644
--- a/Classes/LinphoneUI/Base.lproj/StatusBarView.xib
+++ b/Classes/LinphoneUI/Base.lproj/StatusBarView.xib
@@ -1,8 +1,10 @@
-
-
+
+
+
-
+
+
@@ -21,15 +23,15 @@
-
-
+
+
-
+
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -104,15 +106,15 @@
-
+
-
+
-
-
-
-
+
+
+
+
diff --git a/Classes/LinphoneUI/Base.lproj/UICallConferenceCell.xib b/Classes/LinphoneUI/Base.lproj/UICallConferenceCell.xib
deleted file mode 100644
index a64c1fc9a..000000000
--- a/Classes/LinphoneUI/Base.lproj/UICallConferenceCell.xib
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/LinphoneUI/Base.lproj/UICallPausedCell.xib b/Classes/LinphoneUI/Base.lproj/UICallPausedCell.xib
deleted file mode 100644
index 423378a9e..000000000
--- a/Classes/LinphoneUI/Base.lproj/UICallPausedCell.xib
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Classes/LinphoneUI/UIBackToCallButton.m b/Classes/LinphoneUI/UIBackToCallButton.m
index 60c9f4df4..3a272f5a8 100644
--- a/Classes/LinphoneUI/UIBackToCallButton.m
+++ b/Classes/LinphoneUI/UIBackToCallButton.m
@@ -20,6 +20,7 @@
#import "UIBackToCallButton.h"
#import "LinphoneManager.h"
#import "PhoneMainView.h"
+#import "linphoneapp-Swift.h"
@implementation UIBackToCallButton
@@ -46,7 +47,7 @@
}
- (IBAction)onBackToCallClick:(id)sender {
- [PhoneMainView.instance popToView:CallView.compositeViewDescription];
+ [PhoneMainView.instance popToView:ActiveCallOrConferenceView.compositeViewDescription];
}
@end
diff --git a/Classes/LinphoneUI/UIBluetoothButton.m b/Classes/LinphoneUI/UIBluetoothButton.m
index 53abc1818..f14af83b3 100644
--- a/Classes/LinphoneUI/UIBluetoothButton.m
+++ b/Classes/LinphoneUI/UIBluetoothButton.m
@@ -18,10 +18,8 @@
*/
#import "UIBluetoothButton.h"
-#import "../Utils/AudioHelper.h"
#import "Utils.h"
#import
-
#include "linphone/linphonecore.h"
@implementation UIBluetoothButton
diff --git a/Classes/LinphoneUI/UICallButton.m b/Classes/LinphoneUI/UICallButton.m
index c84a7dafd..aa925fc22 100644
--- a/Classes/LinphoneUI/UICallButton.m
+++ b/Classes/LinphoneUI/UICallButton.m
@@ -21,6 +21,8 @@
#import "LinphoneManager.h"
#import
+#import "linphoneapp-Swift.h"
+
@implementation UICallButton
diff --git a/Classes/LinphoneUI/UICallConferenceCell.h b/Classes/LinphoneUI/UICallConferenceCell.h
deleted file mode 100644
index cf59dfe3c..000000000
--- a/Classes/LinphoneUI/UICallConferenceCell.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UIRoundedImageView.h"
-#import "LinphoneManager.h"
-#import "UIInterfaceStyleButton.h"
-
-#define CONFERENCE_CELL_HEIGHT 60
-
-@interface UICallConferenceCell : UITableViewCell
-
-@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
-@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
-@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
-@property (weak, nonatomic) IBOutlet UIInterfaceStyleButton *kickButton;
-@property(nonatomic, setter=setParticipant:) LinphoneParticipant *participant;
-
-- (id)initWithIdentifier:(NSString *)identifier;
-- (IBAction)onKickClick:(id)sender;
-
-@end
diff --git a/Classes/LinphoneUI/UICallConferenceCell.m b/Classes/LinphoneUI/UICallConferenceCell.m
deleted file mode 100644
index 0e64ca4b1..000000000
--- a/Classes/LinphoneUI/UICallConferenceCell.m
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UICallConferenceCell.h"
-#import "Utils.h"
-#import "PhoneMainView.h"
-
-@implementation UICallConferenceCell
-
-- (id)initWithIdentifier:(NSString *)identifier {
- self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
- if (self != nil) {
- NSArray *arrayOfViews =
- [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
- if ([arrayOfViews count] >= 1) {
- // resize cell to match .nib size. It is needed when resized the cell to
- // correctly adapt its height too
- UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:0]);
- [self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
- [self addSubview:sub];
- }
- }
- return self;
-}
-
-- (void)setParticipant:(LinphoneParticipant *)p {
- _participant = p;
- if (!p) {
- return;
- }
- const LinphoneAddress *addr = linphone_participant_get_address(p);
- [ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
- _durationLabel.text = [LinphoneUtils durationToString:[NSDate date].timeIntervalSince1970 - linphone_participant_get_creation_time(p)];
- _kickButton.hidden = CallManager.instance.isInConferenceAsGuest;
-}
-
-
-- (IBAction)onKickClick:(id)sender {
- if (!_participant) {
- return;
- }
-
- if ([CallManager callKitEnabled]) {
- LinphoneCall *call = [CallManager.instance getCallForParticipant:_participant];
- if (call) {
- [CallManager.instance setHeldWithCall:call hold:true];
- }
- }
- linphone_conference_remove_participant_2([CallManager.instance getConference], _participant);
-
-
-}
-@end
diff --git a/Classes/LinphoneUI/UICallPausedCell.h b/Classes/LinphoneUI/UICallPausedCell.h
deleted file mode 100644
index 77bdedd71..000000000
--- a/Classes/LinphoneUI/UICallPausedCell.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UIRoundedImageView.h"
-#import "LinphoneManager.h"
-#import "UIPauseButton.h"
-
-@interface UICallPausedCell : UITableViewCell
-
-@property(weak, nonatomic) IBOutlet UIRoundedImageView *avatarImage;
-@property(weak, nonatomic) IBOutlet UILabel *nameLabel;
-@property(weak, nonatomic) IBOutlet UILabel *durationLabel;
-@property(weak, nonatomic) IBOutlet UIPauseButton *pauseButton;
-
-- (id)initWithIdentifier:(NSString *)identifier;
-- (void)setCall:(LinphoneCall *)call;
-
-@end
diff --git a/Classes/LinphoneUI/UICallPausedCell.m b/Classes/LinphoneUI/UICallPausedCell.m
deleted file mode 100644
index fe51d9c5d..000000000
--- a/Classes/LinphoneUI/UICallPausedCell.m
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UICallPausedCell.h"
-#import "Utils.h"
-
-@implementation UICallPausedCell
-
-- (id)initWithIdentifier:(NSString *)identifier {
- self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
- if (self != nil) {
- NSArray *arrayOfViews =
- [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
- if ([arrayOfViews count] >= 1) {
- // resize cell to match .nib size. It is needed when resized the cell to
- // correctly adapt its height too
- UIView *sub = ((UIView *)[arrayOfViews objectAtIndex:0]);
- [self setFrame:CGRectMake(0, 0, sub.frame.size.width, sub.frame.size.height)];
- [self addSubview:sub];
- }
- }
- return self;
-}
-
-- (void)setCall:(LinphoneCall *)call {
- // if no call is provided, we assume that this is a conference
- if (!call || linphone_call_get_conference(call)) {
- [_pauseButton setType:UIPauseButtonType_Conference call:call];
- _nameLabel.text = NSLocalizedString(@"Conference", nil);
- [_avatarImage setImage:[UIImage imageNamed:@"options_start_conference_default.png"]
- bordered:NO
- withRoundedRadius:YES];
- _durationLabel.text = @"";
- } else {
- [_pauseButton setType:UIPauseButtonType_Call call:call];
- const LinphoneAddress *addr = linphone_call_get_remote_address(call);
- [ContactDisplay setDisplayNameLabel:_nameLabel forAddress:addr];
- [_avatarImage setImage:[FastAddressBook imageForAddress:addr] bordered:NO withRoundedRadius:YES];
- _durationLabel.text = [LinphoneUtils durationToString:linphone_call_get_duration(call)];
- }
- [_pauseButton update];
-}
-
-@end
diff --git a/Classes/LinphoneUI/UIChatBubblePhotoCell.m b/Classes/LinphoneUI/UIChatBubblePhotoCell.m
index ccf47d7aa..01b13c93b 100644
--- a/Classes/LinphoneUI/UIChatBubblePhotoCell.m
+++ b/Classes/LinphoneUI/UIChatBubblePhotoCell.m
@@ -25,6 +25,8 @@
#import
#import
#import
+#import "linphoneapp-Swift.h"
+
#define voicePlayer VIEW(ChatConversationView).sharedVoicePlayer
#define chatView VIEW(ChatConversationView)
diff --git a/Classes/LinphoneUI/UIChatBubbleTextCell.m b/Classes/LinphoneUI/UIChatBubbleTextCell.m
index 4038ac799..079891029 100644
--- a/Classes/LinphoneUI/UIChatBubbleTextCell.m
+++ b/Classes/LinphoneUI/UIChatBubbleTextCell.m
@@ -25,6 +25,8 @@
#import
#import
#import
+#import "linphoneapp-Swift.h"
+
@implementation UIChatBubbleTextCell
diff --git a/Classes/LinphoneUI/UIHangUpButton.m b/Classes/LinphoneUI/UIHangUpButton.m
deleted file mode 100644
index 6ab96297d..000000000
--- a/Classes/LinphoneUI/UIHangUpButton.m
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UIHangUpButton.h"
-#import "LinphoneManager.h"
-
-#import "linphoneapp-Swift.h"
-
-@implementation UIHangUpButton
-
-#pragma mark - Static Functions
-
-+ (bool)isInConference:(LinphoneCall *)call {
- if (!call)
- return false;
- return linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
-}
-
-+ (int)callCount {
- int count = 0;
- const MSList *calls = linphone_core_get_calls(LC);
-
- while (calls != 0) {
- if (![UIHangUpButton isInConference:((LinphoneCall *)calls->data)]) {
- count++;
- }
- calls = calls->next;
- }
- return count;
-}
-
-#pragma mark - Lifecycle Functions
-
-- (void)initUIHangUpButton {
- [self addTarget:self action:@selector(touchUp:) forControlEvents:UIControlEventTouchUpInside];
-}
-
-- (id)init {
- self = [super init];
- if (self) {
- [self initUIHangUpButton];
- }
- return self;
-}
-
-- (id)initWithCoder:(NSCoder *)decoder {
- self = [super initWithCoder:decoder];
- if (self) {
- [self initUIHangUpButton];
- }
- return self;
-}
-
-- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- [self initUIHangUpButton];
- }
- return self;
-}
-
-#pragma mark -
-
-- (void)update {
- if (linphone_core_get_calls_nb(LC) == 1 || // One call
- linphone_core_get_current_call(LC) != NULL || // In call
- linphone_core_is_in_conference(LC) || // In conference
- (linphone_core_get_conference_size(LC) > 0 && [UIHangUpButton callCount] == 0) // Only one conf
- ) {
- [self setEnabled:true];
- return;
- }
- [self setEnabled:false];
-}
-
-#pragma mark - Action Functions
-
-- (void)touchUp:(id)sender {
- LinphoneCall *currentcall = linphone_core_get_current_call(LC);
- if (linphone_core_is_in_conference(LC) || // In conference
- (linphone_core_get_conference_size(LC) > 0 && [UIHangUpButton callCount] == 0) // Only one conf
- ) {
- LinphoneManager.instance.conf = TRUE;
- linphone_core_terminate_conference(LC);
- } else if (currentcall != NULL) {
- [CallManager.instance terminateCallWithCall:currentcall];
- } else {
- const MSList *calls = linphone_core_get_calls(LC);
- if (bctbx_list_size(calls) == 1) { // Only one call
- [CallManager.instance terminateCallWithCall:(calls->data)];
- }
- }
-}
-
-@end
diff --git a/Classes/LinphoneUI/UIPauseButton.m b/Classes/LinphoneUI/UIPauseButton.m
deleted file mode 100644
index 797e88675..000000000
--- a/Classes/LinphoneUI/UIPauseButton.m
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UIPauseButton.h"
-#import "LinphoneManager.h"
-#import "Utils.h"
-
-@implementation UIPauseButton
-
-#pragma mark - Lifecycle Functions
-
-- (void)initUIPauseButton {
- type = UIPauseButtonType_CurrentCall;
-}
-
-- (id)init {
- self = [super init];
- if (self) {
- [self initUIPauseButton];
- }
- return self;
-}
-
-- (id)initWithCoder:(NSCoder *)decoder {
- self = [super initWithCoder:decoder];
- if (self) {
- [self initUIPauseButton];
- }
- return self;
-}
-
-- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- if (self) {
- [self initUIPauseButton];
- }
- return self;
-}
-
-#pragma mark - Static Functions
-
-+ (bool)isInConference:(LinphoneCall *)call {
- if (!call)
- return false;
- return linphone_call_params_get_local_conference_mode(linphone_call_get_current_params(call));
-}
-
-+ (LinphoneCall *)getCall {
- LinphoneCall *currentCall = linphone_core_get_current_call(LC);
- if (currentCall == nil && linphone_core_get_calls_nb(LC) == 1) {
- currentCall = (LinphoneCall *)linphone_core_get_calls(LC)->data;
- }
- return currentCall;
-}
-
-#pragma mark -
-
-- (void)setType:(UIPauseButtonType)atype call:(LinphoneCall *)acall {
- type = atype;
- call = acall;
-}
-
-#pragma mark - UIToggleButtonDelegate Functions
-
-- (void)onOn {
- switch (type) {
- case UIPauseButtonType_Call: {
- if (call != nil) {
- if ([CallManager callKitEnabled]) {
- [CallManager.instance setHeldWithCall:call hold:true];
- } else {
- CallManager.instance.speakerBeforePause = [CallManager.instance isSpeakerEnabled];
- linphone_call_pause(call);
- }
- } else {
- LOGW(@"Cannot toggle pause buttton, because no current call");
- }
- break;
- }
- case UIPauseButtonType_Conference: {
- linphone_core_leave_conference(CallManager.instance.getConference);
- // Fake event
- [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
- break;
- }
- case UIPauseButtonType_CurrentCall: {
- LinphoneCall *currentCall = [UIPauseButton getCall];
- if (currentCall != nil) {
- if ([CallManager callKitEnabled]) {
- [CallManager.instance setHeldWithCall:currentCall hold:true];
- } else {
- CallManager.instance.speakerBeforePause = [CallManager.instance isSpeakerEnabled];
- linphone_call_pause(currentCall);
- }
- } else {
- LOGW(@"Cannot toggle pause buttton, because no current call");
- }
- break;
- }
- }
-}
-
-- (void)onOff {
- switch (type) {
- case UIPauseButtonType_Call: {
- if (call != nil) {
- if ([CallManager callKitEnabled]) {
- [CallManager.instance setHeldWithCall:call hold:false];
- } else {
- linphone_call_resume(call);
- }
- } else {
- LOGW(@"Cannot toggle pause buttton, because no current call");
- }
- break;
- }
- case UIPauseButtonType_Conference: {
- linphone_core_enter_conference(CallManager.instance.getConference);
- // Fake event
- [NSNotificationCenter.defaultCenter postNotificationName:kLinphoneCallUpdate object:self];
- break;
- }
- case UIPauseButtonType_CurrentCall: {
- LinphoneCall *currentCall = [UIPauseButton getCall];
- if ([CallManager callKitEnabled]) {
- [CallManager.instance setHeldWithCall:currentCall hold:false];
- } else {
- linphone_call_resume(currentCall);
- }
- break;
- }
- }
-}
-
-- (bool)onUpdate {
- bool ret = false;
- LinphoneCall *c = call;
- switch (type) {
- case UIPauseButtonType_Conference: {
- self.enabled = CallManager.instance.getConference && (linphone_conference_get_participant_count(CallManager.instance.getConference)> 0);
- if (self.enabled) {
- ret = (!CallManager.instance.isInConference);
- }
- break;
- }
- case UIPauseButtonType_CurrentCall:
- c = [UIPauseButton getCall];
- case UIPauseButtonType_Call: {
- if (c != nil) {
- LinphoneCallState state = linphone_call_get_state(c);
- ret = (state == LinphoneCallPaused || state == LinphoneCallPausing);
- self.enabled = !linphone_core_sound_resources_locked(LC) &&
- (state == LinphoneCallPaused || state == LinphoneCallPausing ||
- state == LinphoneCallStreamsRunning);
- } else {
- self.enabled = FALSE;
- }
- break;
- }
- }
- return ret;
-}
-
-@end
diff --git a/Classes/LinphoneUI/UISpeakerButton.m b/Classes/LinphoneUI/UISpeakerButton.m
deleted file mode 100644
index 64c12d5b9..000000000
--- a/Classes/LinphoneUI/UISpeakerButton.m
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import
-#import "UISpeakerButton.h"
-#import "Utils.h"
-#import "LinphoneManager.h"
-
-#include "linphone/linphonecore.h"
-
-@implementation UISpeakerButton
-
-INIT_WITH_COMMON_CF {
- [NSNotificationCenter.defaultCenter addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
- return self;
-}
-
-- (void)onOn {
- [CallManager.instance changeRouteToSpeaker];
-}
-
-- (void)onOff {
- [CallManager.instance changeRouteToDefault];
-}
-
-
-- (void)dealloc {
- [NSNotificationCenter.defaultCenter removeObserver:self];
-}
-
-#pragma mark - UIToggleButtonDelegate Functions
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notif {
- dispatch_async(dispatch_get_main_queue(), ^{
- [self update];});
-}
-
-- (bool)onUpdate {
- return [CallManager.instance isSpeakerEnabled];
-}
-
-@end
diff --git a/Classes/LinphoneUI/UIVideoButton.m b/Classes/LinphoneUI/UIVideoButton.m
deleted file mode 100644
index af97c4295..000000000
--- a/Classes/LinphoneUI/UIVideoButton.m
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#import "UIVideoButton.h"
-#include "LinphoneManager.h"
-#import "Log.h"
-
-@implementation UIVideoButton {
- BOOL last_update_state;
-}
-
-@synthesize waitView;
-
-INIT_WITH_COMMON_CF {
- last_update_state = FALSE;
- return self;
-}
-
-- (void)onOn {
-
- if (!linphone_core_video_display_enabled(LC))
- return;
-
- [self setEnabled:FALSE];
- [waitView startAnimating];
-
- LinphoneCall *call = linphone_core_get_current_call(LC);
- if (call) {
- CallAppData *data = [CallManager getAppDataWithCall:call];
- data.videoRequested = TRUE;/* will be used later to notify user if video was not activated because of the linphone core*/
- [CallManager setAppDataWithCall:call appData:data];
- LinphoneCallParams *call_params = linphone_core_create_call_params(LC,call);
- linphone_call_params_enable_video(call_params, TRUE);
- linphone_call_update(call, call_params);
- linphone_call_params_unref(call_params);
- } else if (self.inAudioConf) {
- LinphoneConferenceParams *cp = linphone_core_create_conference_params(LC);
- linphone_conference_params_set_video_enabled(cp, true);
- linphone_conference_update_params(linphone_core_get_conference(LC), cp);
- } else {
- LOGW(@"Cannot toggle video button, because no current call");
- }
-}
-
-- (void)onOff {
-
- if (!linphone_core_video_display_enabled(LC))
- return;
- [CallManager.instance changeRouteToDefault];
- //[CallManager.instance enableSpeakerWithEnable:FALSE];
- [self setEnabled:FALSE];
- [waitView startAnimating];
-
- LinphoneCall *call = linphone_core_get_current_call(LC);
- if (call) {
- LinphoneCallParams *call_params = linphone_core_create_call_params(LC,call);
- linphone_call_params_enable_video(call_params, FALSE);
- linphone_core_update_call(LC, call, call_params);
- linphone_call_params_unref(call_params);
- } else if (self.inVideoConf) {
- LinphoneConferenceParams *cp = linphone_core_create_conference_params(LC);
- linphone_conference_params_set_video_enabled(cp, false);
- linphone_conference_update_params(linphone_core_get_conference(LC), cp);
- } else {
- LOGW(@"Cannot toggle video button, because no current call or no video conference");
- }
-}
-
-- (bool)onUpdate {
- bool video_enabled = false;
- LinphoneCall *currentCall = linphone_core_get_current_call(LC);
- if (linphone_core_video_supported(LC)) {
- if (self.inAudioConf || self.inVideoConf || (linphone_core_video_display_enabled(LC) && currentCall && !linphone_core_sound_resources_locked(LC) &&
- linphone_call_get_state(currentCall) == LinphoneCallStreamsRunning)){
- video_enabled = TRUE;
- }
- }
-
- [self setEnabled:video_enabled];
- if (last_update_state != video_enabled)
- [waitView stopAnimating];
- if (video_enabled) {
- video_enabled = self.inVideoConf || (currentCall && linphone_call_params_video_enabled(linphone_call_get_current_params(currentCall)));
- }
- last_update_state = video_enabled;
-
- return video_enabled;
-}
-
--(BOOL) inVideoConf {
- return (linphone_core_is_in_conference(LC) && linphone_core_get_conference(LC) != nil && linphone_conference_params_is_video_enabled(linphone_conference_get_current_params(linphone_core_get_conference(LC))));
-}
-
--(BOOL) inAudioConf {
- return (linphone_core_is_in_conference(LC) && linphone_core_get_conference(LC) != nil && !linphone_conference_params_is_video_enabled(linphone_conference_get_current_params(linphone_core_get_conference(LC))));
-}
-
-@end
diff --git a/Classes/LinphoneUI/fr.lproj/UICallConferenceCell.strings b/Classes/LinphoneUI/fr.lproj/UICallConferenceCell.strings
deleted file mode 100644
index 629130fa2..000000000
Binary files a/Classes/LinphoneUI/fr.lproj/UICallConferenceCell.strings and /dev/null differ
diff --git a/Classes/LinphoneUI/fr.lproj/UICallPausedCell.strings b/Classes/LinphoneUI/fr.lproj/UICallPausedCell.strings
deleted file mode 100644
index 4d0eb9bb9..000000000
Binary files a/Classes/LinphoneUI/fr.lproj/UICallPausedCell.strings and /dev/null differ
diff --git a/Classes/LinphoneUI/hu.lproj/UICallConferenceCell.strings b/Classes/LinphoneUI/hu.lproj/UICallConferenceCell.strings
deleted file mode 100644
index c90ef96ac..000000000
Binary files a/Classes/LinphoneUI/hu.lproj/UICallConferenceCell.strings and /dev/null differ
diff --git a/Classes/LinphoneUI/hu.lproj/UICallPausedCell.strings b/Classes/LinphoneUI/hu.lproj/UICallPausedCell.strings
deleted file mode 100644
index 7e626165f..000000000
Binary files a/Classes/LinphoneUI/hu.lproj/UICallPausedCell.strings and /dev/null differ
diff --git a/Classes/Log.h b/Classes/Log.h
index c2ee3d897..c838afa12 100644
--- a/Classes/Log.h
+++ b/Classes/Log.h
@@ -32,6 +32,11 @@
+ (void)log:(OrtpLogLevel)severity file:(const char *)file line:(int)line format:(NSString *)format, ...;
+ (void)enableLogs:(OrtpLogLevel)level;
+ (void)directLog:(OrtpLogLevel)level text:(NSString *)text;
++ (void)d:(NSString *)text;
++ (void)i:(NSString *)text;
++ (void)w:(NSString *)text;
++ (void)e:(NSString *)text;
++ (void)f:(NSString *)text;
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args);
@end
diff --git a/Classes/PhoneMainView.h b/Classes/PhoneMainView.h
index adfe87b5a..672030668 100644
--- a/Classes/PhoneMainView.h
+++ b/Classes/PhoneMainView.h
@@ -27,10 +27,6 @@
#import "AboutView.h"
#import "AssistantLinkView.h"
#import "AssistantView.h"
-#import "CallIncomingView.h"
-#import "CallOutgoingView.h"
-#import "CallSideMenuView.h"
-#import "CallView.h"
#import "ChatConversationCreateView.h"
#import "ChatConversationInfoView.h"
#import "ChatConversationImdnView.h"
@@ -78,7 +74,7 @@
@end
-@interface PhoneMainView : UIViewController {
+@interface PhoneMainView : UIViewController {
@private
NSMutableArray *inhibitedEvents;
}
@@ -96,6 +92,7 @@
- (void)changeCurrentView:(UICompositeViewDescription *)view;
- (UIViewController*)popCurrentView;
+- (UIViewController *)popView:(UICompositeViewDescription *)view;
- (UIViewController *)popToView:(UICompositeViewDescription *)currentView;
- (void) setPreviousViewName:(NSString*)previous;
- (NSString*) getPreviousViewName;
diff --git a/Classes/PhoneMainView.m b/Classes/PhoneMainView.m
index 3d0ad4962..a45b51376 100644
--- a/Classes/PhoneMainView.m
+++ b/Classes/PhoneMainView.m
@@ -22,6 +22,8 @@
#import "LinphoneAppDelegate.h"
#import "Log.h"
#import "PhoneMainView.h"
+#import "linphoneapp-Swift.h"
+
static RootViewManager *rootViewManagerInstance = nil;
@@ -373,7 +375,9 @@ static RootViewManager *rootViewManagerInstance = nil;
break;
}
case LinphoneCallOutgoingInit: {
- [self changeCurrentView:CallOutgoingView.compositeViewDescription];
+ OutgoingCallView *v = VIEW(OutgoingCallView);
+ [self changeCurrentView:OutgoingCallView.compositeViewDescription];
+ [v setCallWithCall:call];
break;
}
case LinphoneCallPausedByRemote:
@@ -381,39 +385,12 @@ static RootViewManager *rootViewManagerInstance = nil;
if (![LinphoneManager.instance isCTCallCenterExist]) {
/*only register CT call center CB for connected call*/
[LinphoneManager.instance setupGSMInteraction];
- [[UIDevice currentDevice] setProximityMonitoringEnabled:!([CallManager.instance isSpeakerEnabled] || [CallManager.instance isBluetoothEnabled])];
- }
- break;
- }
- case LinphoneCallStreamsRunning: {
- [self changeCurrentView:CallView.compositeViewDescription];
- break;
- }
- case LinphoneCallUpdatedByRemote: {
- const LinphoneCallParams *current = linphone_call_get_current_params(call);
- const LinphoneCallParams *remote = linphone_call_get_remote_params(call);
-
- if (linphone_call_params_video_enabled(current) && !linphone_call_params_video_enabled(remote)) {
- [self changeCurrentView:CallView.compositeViewDescription];
}
break;
}
case LinphoneCallError: {
[self displayCallError:call message:message];
}
- case LinphoneCallEnd: {
- const MSList *calls = linphone_core_get_calls(LC);
- if (!calls || calls->data == call) {
- while ((currentView == CallView.compositeViewDescription) ||
- (currentView == CallIncomingView.compositeViewDescription) ||
- (currentView == CallOutgoingView.compositeViewDescription)) {
- [self popCurrentView];
- }
- } else {
- [self changeCurrentView:CallView.compositeViewDescription];
- }
- break;
- }
case LinphoneCallEarlyUpdatedByRemote:
case LinphoneCallEarlyUpdating:
case LinphoneCallIdle:
@@ -633,6 +610,15 @@ static RootViewManager *rootViewManagerInstance = nil;
return [mainViewController getCurrentViewController];
}
+- (UIViewController *)popView:(UICompositeViewDescription *)view {
+ NSMutableArray *viewStack = [RootViewManager instance].viewDescriptionStack;
+ while (viewStack.count > 0 && [[viewStack lastObject] equal:view]) {
+ [viewStack removeLastObject];
+ }
+ return [self popToView:viewStack.lastObject ?: DialerView.compositeViewDescription];
+}
+
+
- (void)changeCurrentView:(UICompositeViewDescription *)view {
[self _changeCurrentView:view transition:nil animated:ANIMATED];
}
@@ -764,10 +750,10 @@ static RootViewManager *rootViewManagerInstance = nil;
[CallManager.instance acceptCallWithCall:call hasVideo:YES];
} else {
AudioServicesPlaySystemSound(lm.sounds.vibrate);
- CallIncomingView *view = VIEW(CallIncomingView);
+ IncomingCallView *view = VIEW(IncomingCallView);
[self changeCurrentView:view.compositeViewDescription];
- [view setCall:call];
- [view setDelegate:self];
+ [view setCallWithCall:call];
+ //CDFIX [view setDelegate:self];
}
}
}
diff --git a/Classes/ProviderDelegate.swift b/Classes/ProviderDelegate.swift
index 54113dafd..a6f324058 100644
--- a/Classes/ProviderDelegate.swift
+++ b/Classes/ProviderDelegate.swift
@@ -92,7 +92,7 @@ class ProviderDelegate: NSObject {
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
if CallManager.instance().endCallkit {
- let call = CallManager.instance().lc?.getCallByCallid(callId: callId!)
+ let call = CallManager.instance().core?.getCallByCallid(callId: callId!)
if (call?.state == .PushIncomingReceived) {
try? call?.terminate()
}
@@ -184,7 +184,7 @@ extension ProviderDelegate: CXProviderDelegate {
CallManager.instance().backgroundContextCameraIsEnabled = call!.params?.videoEnabled ?? false
call?.cameraEnabled = false // Disable camera while app is not on foreground
}
- CallManager.instance().lc?.configureAudioSession()
+ CallManager.instance().core?.configureAudioSession()
CallManager.instance().acceptCall(call: call!, hasVideo: call!.params?.videoEnabled ?? false)
action.fulfill()
}
@@ -199,8 +199,8 @@ extension ProviderDelegate: CXProviderDelegate {
}
do {
- if (CallManager.instance().lc?.isInConference ?? false && action.isOnHold) {
- try CallManager.instance().lc?.leaveConference()
+ if (CallManager.instance().core?.isInConference ?? false && action.isOnHold) {
+ try CallManager.instance().core?.leaveConference()
Log.directLog(BCTBX_LOG_DEBUG, text: "CallKit: call-id: [\(String(describing: callId))] leaving conference")
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
return
@@ -212,11 +212,10 @@ extension ProviderDelegate: CXProviderDelegate {
if (call!.params?.localConferenceMode ?? false) {
return
}
- CallManager.instance().speakerBeforePause = CallManager.instance().isSpeakerEnabled()
try call!.pause()
} else {
- if (CallManager.instance().lc?.conference != nil && CallManager.instance().lc?.callsNb ?? 0 > 1) {
- try CallManager.instance().lc?.enterConference()
+ if (CallManager.instance().core?.conference != nil && CallManager.instance().core?.callsNb ?? 0 > 1) {
+ try CallManager.instance().core?.enterConference()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
} else {
try call!.resume()
@@ -245,7 +244,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fail()
}
- CallManager.instance().lc?.configureAudioSession()
+ CallManager.instance().core?.configureAudioSession()
try CallManager.instance().doCall(addr: addr!, isSas: callInfo?.sasEnabled ?? false)
} catch {
Log.directLog(BCTBX_LOG_ERROR, text: "CallKit: Call started failed because \(error)")
@@ -256,7 +255,7 @@ extension ProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).")
- CallManager.instance().addAllToConference()
+ CallManager.instance().addAllToLocalConference()
action.fulfill()
}
@@ -264,7 +263,7 @@ extension ProviderDelegate: CXProviderDelegate {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: Call muted with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
- CallManager.instance().lc!.micEnabled = !CallManager.instance().lc!.micEnabled
+ CallManager.instance().core!.micEnabled = !CallManager.instance().core!.micEnabled
action.fulfill()
}
@@ -297,12 +296,12 @@ extension ProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session activated.")
- CallManager.instance().lc?.activateAudioSession(actived: true)
+ CallManager.instance().core?.activateAudioSession(actived: true)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: audio session deactivated.")
- CallManager.instance().lc?.activateAudioSession(actived: false)
+ CallManager.instance().core?.activateAudioSession(actived: false)
}
}
diff --git a/Classes/CallConferenceTableView.h b/Classes/SwiftUtil/Extensions/IOS/OptionalExtensions.swift
similarity index 79%
rename from Classes/CallConferenceTableView.h
rename to Classes/SwiftUtil/Extensions/IOS/OptionalExtensions.swift
index 41698c257..58f10f627 100644
--- a/Classes/CallConferenceTableView.h
+++ b/Classes/SwiftUtil/Extensions/IOS/OptionalExtensions.swift
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
- * This file is part of linphone-iphone
+ * This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,10 +17,16 @@
* along with this program. If not, see .
*/
-#import
-@interface CallConferenceTableView : UITableViewController
-
-- (void)update;
-
-@end
+extension Optional {
+ var logable: Any {
+ switch self {
+ case .none:
+ return "|⭕️"
+ case let .some(value):
+ return value
+ }
+ }
+
+
+}
diff --git a/Classes/SwiftUtil/Extensions/IOS/UIApplication+Extension.swift b/Classes/SwiftUtil/Extensions/IOS/UIApplication+Extension.swift
new file mode 100644
index 000000000..50f5e9353
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/IOS/UIApplication+Extension.swift
@@ -0,0 +1,39 @@
+/*
+* Copyright (c) 2010-2020 Belledonne Communications SARL.
+*
+* This file is part of linhome
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+
+
+import Foundation
+import UIKit
+
+
+extension UIApplication {
+
+ class func getTopMostViewController() -> UIViewController? {
+ let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
+ if var topController = keyWindow?.rootViewController {
+ while let presentedViewController = topController.presentedViewController {
+ topController = presentedViewController
+ }
+ return topController
+ } else {
+ return nil
+ }
+ }
+}
diff --git a/Classes/SwiftUtil/Extensions/IOS/UIColorExtensions.swift b/Classes/SwiftUtil/Extensions/IOS/UIColorExtensions.swift
new file mode 100644
index 000000000..2d4b8efc9
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/IOS/UIColorExtensions.swift
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+
+extension UIColor {
+ public convenience init(hex: String) {
+ let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
+ var int = UInt64()
+ Scanner(string: hex).scanHexInt64(&int)
+ let a, r, g, b: UInt64
+ switch hex.count {
+ case 3: // RGB (12-bit)
+ (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
+ case 6: // RGB (24-bit)
+ (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
+ case 8: // ARGB (32-bit)
+ (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
+ default:
+ (a, r, g, b) = (255, 0, 0, 0)
+ }
+ self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
+ }
+}
diff --git a/Classes/SwiftUtil/Extensions/IOS/UIDeviceExtensions.swift b/Classes/SwiftUtil/Extensions/IOS/UIDeviceExtensions.swift
new file mode 100644
index 000000000..fa00d6236
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/IOS/UIDeviceExtensions.swift
@@ -0,0 +1,57 @@
+/*
+* Copyright (c) 2010-2020 Belledonne Communications SARL.
+*
+* This file is part of linhome
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+
+
+import Foundation
+import UIKit
+import AVFoundation
+
+extension UIDevice {
+ static func ipad() -> Bool {
+ return UIDevice.current.userInterfaceIdiom == .pad
+ }
+ static func vibrate() {
+ if (!ipad()) {
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
+ }
+ }
+ static func is5SorSEGen1() -> Bool {
+ return UIScreen.main.nativeBounds.height == 1136
+ }
+
+ static func hasNotch() -> Bool {
+ if (UserDefaults.standard.bool(forKey: "hasNotch")) {
+ return true
+ }
+ guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top, topPadding > 24 else {
+ return false
+ }
+ UserDefaults.standard.setValue(true, forKey: "hasNotch")
+ return true
+ }
+
+ static func notchHeight() -> CGFloat {
+ guard #available(iOS 11.0, *), let topPadding = UIApplication.shared.keyWindow?.safeAreaInsets.top else {
+ return 0
+ }
+ return topPadding
+ }
+
+}
diff --git a/Classes/SwiftUtil/Extensions/IOS/UIImageExtensions.swift b/Classes/SwiftUtil/Extensions/IOS/UIImageExtensions.swift
new file mode 100644
index 000000000..a409d782a
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/IOS/UIImageExtensions.swift
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+
+extension UIImage {
+ func tinted(with color: UIColor?) -> UIImage? {
+ if (color == nil) {
+ return self
+ }
+ defer { UIGraphicsEndImageContext() }
+ UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale)
+ color!.set()
+ self.withRenderingMode(.alwaysTemplate).draw(in: CGRect(origin: .zero, size: self.size))
+ return UIGraphicsGetImageFromCurrentImageContext()
+ }
+
+ func withInsets(insets: UIEdgeInsets) -> UIImage? {
+ UIGraphicsBeginImageContextWithOptions(
+ CGSize(width: self.size.width + insets.left + insets.right,
+ height: self.size.height + insets.top + insets.bottom), false, self.scale)
+ let _ = UIGraphicsGetCurrentContext()
+ let origin = CGPoint(x: insets.left, y: insets.top)
+ self.draw(at: origin)
+ let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
+ UIGraphicsEndImageContext()
+ return imageWithInsets
+ }
+
+ func withPadding(padding: CGFloat) -> UIImage? {
+ let insets = UIEdgeInsets(top: padding, left: padding, bottom: padding, right: padding)
+ return withInsets(insets: insets)
+ }
+}
diff --git a/Classes/CallPausedTableView.h b/Classes/SwiftUtil/Extensions/IOS/UIImageViewExtensions.swift
similarity index 78%
rename from Classes/CallPausedTableView.h
rename to Classes/SwiftUtil/Extensions/IOS/UIImageViewExtensions.swift
index 622f19084..9f926344a 100644
--- a/Classes/CallPausedTableView.h
+++ b/Classes/SwiftUtil/Extensions/IOS/UIImageViewExtensions.swift
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
- * This file is part of linphone-iphone
+ * This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,10 +17,11 @@
* along with this program. If not, see .
*/
-#import
+import Foundation
-@interface CallPausedTableView : UITableViewController
-
-- (void)update;
-
-@end
+extension UIImageView {
+ func tint(_ color:UIColor) {
+ self.image = self.image?.withRenderingMode(.alwaysTemplate)
+ tintColor = color
+ }
+}
diff --git a/Classes/LinphoneUI/UIVideoButton.h b/Classes/SwiftUtil/Extensions/IOS/UIVIewControllerExtensions.swift
similarity index 72%
rename from Classes/LinphoneUI/UIVideoButton.h
rename to Classes/SwiftUtil/Extensions/IOS/UIVIewControllerExtensions.swift
index 79def1378..8c27c7dec 100644
--- a/Classes/LinphoneUI/UIVideoButton.h
+++ b/Classes/SwiftUtil/Extensions/IOS/UIVIewControllerExtensions.swift
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
- * This file is part of linphone-iphone
+ * This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,13 +17,13 @@
* along with this program. If not, see .
*/
-#import
+import Foundation
+import SnapKit
+import UIKit
-#import "UIToggleButton.h"
-
-@interface UIVideoButton : UIToggleButton {
+extension UIViewController {
+ func VIEW( _ desc: UICompositeViewDescription) -> T{
+ return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T
+ }
+
}
-
-@property(nonatomic, strong) IBOutlet UIActivityIndicatorView *waitView;
-
-@end
diff --git a/Classes/SwiftUtil/Extensions/IOS/UIVIewExtensions.swift b/Classes/SwiftUtil/Extensions/IOS/UIVIewExtensions.swift
new file mode 100644
index 000000000..7e99f532f
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/IOS/UIVIewExtensions.swift
@@ -0,0 +1,326 @@
+/*
+ * 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 SnapKit
+import UIKit
+
+extension UIView {
+
+ // Few constraints wrapper to abstract SnapKit functions
+
+ func removeConstraints() -> UIView {
+ snp.removeConstraints()
+ return self
+ }
+
+ func square(_ size:Int) -> UIView {
+ snp.makeConstraints { (make) in
+ make.width.equalTo(size)
+ make.height.equalTo(size)
+ }
+ return self
+ }
+
+ func size(w:CGFloat,h:CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.width.equalTo(w)
+ make.height.equalTo(h)
+ }
+ return self
+ }
+
+ func height(_ h:CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.height.equalTo(h)
+ }
+ return self
+ }
+
+ func height(_ h:Int) -> UIView {
+ return height(CGFloat(h))
+ }
+
+ func width(_ h:CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.width.equalTo(h)
+ }
+ return self
+ }
+
+ func width(_ h:Int) -> UIView {
+ return width(CGFloat(h))
+ }
+
+ func maxHeight(_ h:CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.height.lessThanOrEqualTo(h)
+ }
+ return self
+ }
+
+ func minWidth(_ h:CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.width.greaterThanOrEqualTo(h)
+ }
+ return self
+ }
+
+
+ func matchParentSideBorders(insetedByDx:CGFloat = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.left.equalToSuperview().offset(insetedByDx)
+ make.right.equalToSuperview().offset(-insetedByDx)
+ }
+ return self
+ }
+
+ func matchParentDimmensions() -> UIView {
+ snp.makeConstraints { (make) in
+ make.left.right.top.bottom.equalToSuperview()
+ }
+ return self
+ }
+
+ func matchDimentionsOf(view:UIView) -> UIView {
+ snp.makeConstraints { (make) in
+ make.left.right.top.bottom.equalTo(view)
+ }
+ return self
+ }
+
+ func matchParentHeight() -> UIView {
+ snp.makeConstraints { (make) in
+ make.top.bottom.equalToSuperview()
+ }
+ return self
+ }
+
+ func matchParentHeightDividedBy(_ divider : CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.height.equalToSuperview().dividedBy(divider)
+ }
+ return self
+ }
+
+ func matchParentWidthDividedBy(_ divider : CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.width.equalToSuperview().dividedBy(divider)
+ }
+ return self
+ }
+
+ func center() -> UIView {
+ snp.makeConstraints { (make) in
+ make.center.equalToSuperview()
+ }
+ return self
+ }
+
+ func alignParentTop(withMargin:CGFloat = 0.0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.top.equalToSuperview().offset(withMargin)
+ }
+ return self
+ }
+
+ func alignParentTop(withMargin:Int ) -> UIView {
+ return alignParentTop(withMargin:CGFloat(withMargin))
+ }
+
+
+ func alignUnder(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.top.equalTo(view.snp.bottom).offset(withMargin)
+ }
+ return self
+ }
+ func alignUnder(view:UIView, withMargin:Int) -> UIView {
+ return alignUnder(view: view,withMargin:CGFloat(withMargin))
+ }
+
+ func matchRightOf(view:UIView, withMargin:CGFloat = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.right.equalTo(view).offset(withMargin)
+ }
+ return self
+ }
+
+ func updateAlignUnder(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
+ snp.updateConstraints { (make) in
+ make.top.equalTo(view.snp.bottom).offset(withMargin)
+ }
+ return self
+ }
+
+ func alignParentBottom(withMargin:CGFloat = 0.0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.bottom.equalToSuperview().offset(-withMargin)
+ }
+ return self
+ }
+
+ func alignParentBottom(withMargin:Int) -> UIView {
+ return alignParentBottom(withMargin:CGFloat(withMargin))
+ }
+
+ func alignAbove(view:UIView, withMargin:CGFloat = 0.0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.bottom.equalTo(view.snp.top).offset(-withMargin)
+ }
+ return self
+ }
+
+ func alignAbove(view:UIView, withMargin:Int) -> UIView {
+ return alignAbove(view: view,withMargin:CGFloat(withMargin))
+ }
+
+ func alignBottomWith(otherView:UIView) -> UIView {
+ snp.makeConstraints { (make) in
+ make.bottom.equalTo(otherView)
+ }
+ return self
+ }
+
+ func marginLeft(_ m:CGFloat) -> UIView {
+ snp.makeConstraints { (make) in
+ make.left.equalToSuperview().offset(m)
+ }
+ return self
+ }
+
+ func alignParentLeft(withMargin:CGFloat = 0.0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.left.equalToSuperview().offset(withMargin)
+ }
+ return self
+ }
+
+ func alignParentLeft(withMargin:Int) -> UIView {
+ return alignParentLeft(withMargin:CGFloat(withMargin))
+ }
+
+ func alignParentRight(withMargin:Int = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.right.equalToSuperview().offset(-withMargin)
+ }
+ return self
+ }
+
+ func alignParentRight(withMargin:CGFloat) -> UIView {
+ return alignParentRight(withMargin:Int(withMargin))
+ }
+
+
+ func toRightOf(_ view:UIView, withLeftMargin:Int = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.left.equalTo(view.snp.right).offset(withLeftMargin)
+ }
+ return self
+ }
+
+ func toRightOf(_ view:UIView, withLeftMargin:CGFloat) -> UIView {
+ return toRightOf(view,withLeftMargin: Int(withLeftMargin))
+ }
+
+ func toLeftOf(_ view:UIView) -> UIView {
+ snp.makeConstraints { (make) in
+ make.right.equalTo(view.snp.left)
+ }
+ return self
+ }
+
+
+ func centerX(withDx:Int = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.centerX.equalToSuperview().offset(withDx)
+ }
+ return self
+ }
+
+ func centerY(withDy:Int = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.centerY.equalToSuperview().offset(withDy)
+ }
+ return self
+ }
+
+ func matchCenterXOf(view:UIView, withDx:Int = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.centerX.equalTo(view).offset(withDx)
+ }
+ return self
+ }
+
+ func matchCenterYOf(view:UIView, withDy:Int = 0) -> UIView {
+ snp.makeConstraints { (make) in
+ make.centerY.equalTo(view).offset(withDy)
+ }
+ return self
+ }
+
+ func wrapContentY() -> UIView {
+ subviews.first?.snp.makeConstraints({ make in
+ make.top.equalToSuperview()
+ })
+ subviews.last?.snp.makeConstraints({ make in
+ make.bottom.equalToSuperview()
+ })
+ return self
+ }
+
+ func wrapContentX() -> UIView {
+ subviews.first?.snp.makeConstraints({ make in
+ make.left.equalToSuperview()
+ })
+ subviews.last?.snp.makeConstraints({ make in
+ make.right.equalToSuperview()
+ })
+ return self
+ }
+
+ func done() {
+ // to avoid the unused variable warning
+ }
+
+ // Onclick
+
+ class TapGestureRecognizer: UITapGestureRecognizer {
+ var action : (()->Void)? = nil
+ }
+
+ func onClick(action : @escaping ()->Void ){
+ let tap = TapGestureRecognizer(target: self , action: #selector(self.handleTap(_:)))
+ tap.action = action
+ tap.numberOfTapsRequired = 1
+ tap.cancelsTouchesInView = false
+
+ self.addGestureRecognizer(tap)
+ self.isUserInteractionEnabled = true
+
+ }
+ @objc func handleTap(_ sender: TapGestureRecognizer) {
+ sender.action!()
+ }
+
+ func VIEW( _ desc: UICompositeViewDescription) -> T{
+ return PhoneMainView.instance().mainViewController.getCachedController(desc.name) as! T
+ }
+
+}
diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/AddressExtensions.swift b/Classes/SwiftUtil/Extensions/LinphoneCore/AddressExtensions.swift
new file mode 100644
index 000000000..39bd741f8
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/AddressExtensions.swift
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import linphonesw
+
+extension Address {
+
+ func initials() -> String? {
+ var initials = initials(displayName: addressBookEnhancedDisplayName())
+ if (initials == nil || initials!.isEmpty) {
+ initials = String(username.prefix(1))
+ }
+ return initials
+ }
+
+ private func initials(displayName: String?) -> String? { // Basic ImproveMe
+ return displayName?.components(separatedBy: " ")
+ .reduce("") {
+ ($0.isEmpty ? "" : "\($0.first?.uppercased() ?? "")") +
+ ($1.isEmpty ? "" : "\($1.first?.uppercased() ?? "")")
+ }
+ }
+
+ func addressBookEnhancedDisplayName() -> String? {
+ if let contact = FastAddressBook.getContactWith(getCobject) {
+ return contact.displayName
+ } else if (!displayName.isEmpty) {
+ return displayName
+ } else {
+ return username
+ }
+ }
+
+ func contact() -> Contact? {
+ return FastAddressBook.getContactWith(getCobject)
+ }
+
+}
diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/CallExtensions.swift b/Classes/SwiftUtil/Extensions/LinphoneCore/CallExtensions.swift
new file mode 100644
index 000000000..197f48fbc
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/CallExtensions.swift
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import linphonesw
+
+extension Call {
+ func answerVideoUpdateRequest(accept:Bool) {
+ guard let params = try?core? .createCallParams(call: self) else {
+ Log.i("[Call] \(self) unable to answerVideoUpdateRequest : could not create params ")
+ return
+ }
+ if (accept) {
+ params.videoEnabled = true
+ core?.videoCaptureEnabled = true
+ core?.videoDisplayEnabled = true
+ } else {
+ params.videoEnabled = false
+ }
+ try?acceptUpdate(params: params)
+ }
+}
+
+extension Call : CustomStringConvertible {
+ public var description: String {
+ if let callId = callLog?.callId {
+ return ""
+ }
+ return ""
+ }
+}
+
diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/ConferenceExtensions.swift b/Classes/SwiftUtil/Extensions/LinphoneCore/ConferenceExtensions.swift
new file mode 100644
index 000000000..483f3bc16
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/ConferenceExtensions.swift
@@ -0,0 +1,33 @@
+/*
+ * 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 linphonesw
+
+
+
+extension Conference : CustomStringConvertible {
+ public var description: String {
+ if let username = conferenceAddress?.username {
+ return "<\(username)>"
+ }
+ return ""
+ }
+}
+
diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/CoreExtensions.swift b/Classes/SwiftUtil/Extensions/LinphoneCore/CoreExtensions.swift
new file mode 100644
index 000000000..ee0c99edb
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/CoreExtensions.swift
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import linphonesw
+
+extension Core {
+ static func get() -> Core {
+ return CallManager.instance().core!
+ }
+
+ func showSwitchCameraButton() -> Bool {
+ return videoDevicesList.count > 2 // Count StaticImage camera
+ }
+
+ func toggleCamera() {
+ Log.i("[Core] Current camera device is \(videoDevice)")
+
+ videoDevicesList.forEach {
+ if ($0 != videoDevice && $0 != "StaticImage: Static picture") {
+ Log.i("[Core] New camera device will be \($0)")
+ try?setVideodevice(newValue: $0)
+ return
+ }
+ }
+
+ let inConference = conference != nil && conference!.isIn
+ if !inConference, let call = currentCall {
+ try?call.update(params: nil)
+ }
+ }
+}
diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/IceState.swift b/Classes/SwiftUtil/Extensions/LinphoneCore/IceState.swift
new file mode 100644
index 000000000..7274e2f7e
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/IceState.swift
@@ -0,0 +1,35 @@
+/*
+ * 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 linphonesw
+
+extension IceState {
+ func toString()->String {
+ switch (self) {
+ case .NotActivated: return NSLocalizedString("Not activated", tableName:"ICE has not been activated for this call",comment : "")
+ case .Failed: return NSLocalizedString("Failed", tableName:"ICE processing has failed",comment :"")
+ case .InProgress: return NSLocalizedString("In progress", tableName:"ICE process is in progress",comment :"")
+ case .HostConnection: return NSLocalizedString("Direct connection", tableName:"ICE has established a direct connection to the remote host",comment :"")
+ case .ReflexiveConnection: return NSLocalizedString( "NAT(s) connection", tableName:"ICE has established a connection to the remote host through one or several NATs",comment :"")
+ case .RelayConnection: return NSLocalizedString("Relay connection", tableName:"ICE has established a connection through a relay",comment :"")
+ }
+
+ }
+}
diff --git a/Classes/SwiftUtil/Extensions/LinphoneCore/ParticipantExtensions.swift b/Classes/SwiftUtil/Extensions/LinphoneCore/ParticipantExtensions.swift
new file mode 100644
index 000000000..9b7b07652
--- /dev/null
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/ParticipantExtensions.swift
@@ -0,0 +1,33 @@
+/*
+ * 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 linphonesw
+
+
+
+extension Participant : CustomStringConvertible {
+ public var description: String {
+ if let address = address?.asStringUriOnly() {
+ return ""
+ }
+ return ""
+ }
+}
+
diff --git a/Classes/LinphoneUI/UISpeakerButton.h b/Classes/SwiftUtil/Extensions/LinphoneCore/PayloadType.swift
similarity index 85%
rename from Classes/LinphoneUI/UISpeakerButton.h
rename to Classes/SwiftUtil/Extensions/LinphoneCore/PayloadType.swift
index 34361634e..197a8d733 100644
--- a/Classes/LinphoneUI/UISpeakerButton.h
+++ b/Classes/SwiftUtil/Extensions/LinphoneCore/PayloadType.swift
@@ -17,12 +17,11 @@
* along with this program. If not, see .
*/
-#import
-
-#import "UIToggleButton.h"
-
-@interface UISpeakerButton : UIToggleButton {
+import Foundation
+import linphonesw
+import linphone
+extension linphonesw.PayloadType {
+
+
}
-
-@end
diff --git a/Classes/SwiftUtil/ViewModel/MutableLiveData.swift b/Classes/SwiftUtil/ViewModel/MutableLiveData.swift
new file mode 100644
index 000000000..125939785
--- /dev/null
+++ b/Classes/SwiftUtil/ViewModel/MutableLiveData.swift
@@ -0,0 +1,138 @@
+/*
+* Copyright (c) 2010-2020 Belledonne Communications SARL.
+*
+* This file is part of linhome
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see .
+*/
+
+
+import Foundation
+
+
+class MutableLiveDataOnChangeClosure: NSObject {
+ let value: (Type?) -> Void
+ let onlyOnce: Bool
+ init(_ function: @escaping (Type?) -> Void, onlyOnce:Bool = false) {
+ value = function
+ self.onlyOnce = onlyOnce
+ }
+}
+
+class MutableLiveData {
+
+ private var _value : T? = nil
+ private var observers = [MutableLiveDataOnChangeClosure] ()
+ private var _opposite : MutableLiveData? = nil
+
+ init(_ initial:T) {
+ self.value = initial
+ }
+
+ init() {
+ }
+
+ var value : T? {
+ get {
+ return self._value
+ }
+ set {
+ self._value = newValue
+ self.notifyAllObservers(with: newValue)
+ }
+ }
+
+
+ func addObserver(observer: MutableLiveDataOnChangeClosure, andNotifyOnce: Bool = false) {
+ observers.append(observer)
+ if (andNotifyOnce) {
+ notifyValue()
+ }
+ }
+
+ func removeObserver(observer: MutableLiveDataOnChangeClosure) {
+ observers = observers.filter({$0 !== observer})
+ }
+
+
+ func clearObservers() {
+ observers.forEach {
+ removeObserver(observer: $0)
+ }
+ }
+
+
+ func notifyAllObservers(with newValue: T?) {
+ for observer in observers {
+ observer.value(newValue)
+ if (observer.onlyOnce) {
+ removeObserver(observer: observer)
+ }
+ }
+ }
+
+ func notifyValue() {
+ for observer in observers {
+ observer.value(value)
+ if (observer.onlyOnce) {
+ removeObserver(observer: observer)
+ }
+ }
+ }
+
+ func observe(onChange : @escaping (T?)->Void) {
+ let observer = MutableLiveDataOnChangeClosure({ value in
+ onChange(value)
+ }, onlyOnce: false)
+ addObserver(observer: observer)
+ }
+
+ func readCurrentAndObserve(onChange : @escaping (T?)->Void) {
+ let observer = MutableLiveDataOnChangeClosure({ value in
+ onChange(value)
+ }, onlyOnce: false)
+ addObserver(observer: observer)
+ observer.value(value)
+ }
+
+ func observeAsUniqueObserver (onChange : @escaping (T?)->Void, unique: Bool = false) {
+ let observer = MutableLiveDataOnChangeClosure({ value in
+ onChange(value)
+ }, onlyOnce: false)
+ if (unique) {
+ clearObservers()
+ }
+ addObserver(observer: observer)
+ }
+
+ func observeOnce(onChange : @escaping (T?)->Void) {
+ let observer = MutableLiveDataOnChangeClosure({ value in
+ onChange(value)
+ }, onlyOnce: true)
+ addObserver(observer: observer)
+ }
+
+ func opposite() -> MutableLiveData? {
+ if (_opposite != nil) {
+ return _opposite
+ }
+ _opposite = MutableLiveData(!(value! as! Bool))
+ observe { (value) in
+ self._opposite!.value = !(value! as! Bool)
+ }
+ return _opposite
+ }
+
+
+}
diff --git a/Classes/Utils/AudioHelper.h b/Classes/Utils/AudioHelper.h
deleted file mode 100644
index fdf2845b1..000000000
--- a/Classes/Utils/AudioHelper.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2010-2020 Belledonne Communications SARL.
- *
- * This file is part of linphone-iphone
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#ifndef AudioHelper_h
-#define AudioHelper_h
-
-#import
-
-@import AVFoundation;
-
-@interface AudioHelper : NSObject
-
-+ (NSArray *)bluetoothRoutes;
-+ (AVAudioSessionPortDescription *)bluetoothAudioDevice;
-+ (AVAudioSessionPortDescription *)builtinAudioDevice;
-+ (AVAudioSessionPortDescription *)speakerAudioDevice;
-+ (AVAudioSessionPortDescription *)audioDeviceFromTypes:(NSArray *)types;
-@end
-
-#endif /* AudioHelper_h */
diff --git a/Classes/Utils/FileTransferDelegate.m b/Classes/Utils/FileTransferDelegate.m
index 0738c54b4..413c6c0b5 100644
--- a/Classes/Utils/FileTransferDelegate.m
+++ b/Classes/Utils/FileTransferDelegate.m
@@ -21,6 +21,8 @@
#import "LinphoneManager.h"
#import "PhoneMainView.h"
#import "Utils.h"
+#import "linphoneapp-Swift.h"
+
@interface FileTransferDelegate ()
@property(strong) NSMutableData *data;
diff --git a/Classes/Utils/Log.m b/Classes/Utils/Log.m
index c7fe474c8..9c0b29c96 100644
--- a/Classes/Utils/Log.m
+++ b/Classes/Utils/Log.m
@@ -67,6 +67,24 @@
bctbx_log(BCTBX_LOG_DOMAIN, level, "%s", [text cStringUsingEncoding:NSUTF8StringEncoding]);
}
+
++(void)d:(NSString *)text {
+ [Log directLog:BCTBX_LOG_DEBUG text:text];
+}
++(void)i:(NSString *)text {
+ [Log directLog:BCTBX_LOG_MESSAGE text:text];
+}
++(void)w:(NSString *)text {
+ [Log directLog:BCTBX_LOG_WARNING text:text];
+}
++(void)e:(NSString *)text {
+ [Log directLog:BCTBX_LOG_ERROR text:text];
+}
++(void)f:(NSString *)text {
+ [Log directLog:BCTBX_LOG_FATAL text:text];
+}
+
+
#pragma mark - Logs Functions callbacks
void linphone_iphone_log_handler(const char *domain, OrtpLogLevel lev, const char *fmt, va_list args) {
diff --git a/Classes/Voip/AudioRouteUtils.swift b/Classes/Voip/AudioRouteUtils.swift
new file mode 100644
index 000000000..94a26f71e
--- /dev/null
+++ b/Classes/Voip/AudioRouteUtils.swift
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import AVFoundation
+import linphonesw
+
+@objc class AudioRouteUtils : NSObject {
+
+ static let core = Core.get()
+
+ static private func applyAudioRouteChange( call: Call?, types: [AudioDeviceType], output: Bool = true) {
+ let typesNames = types.map { String(describing: $0) }.joined(separator: "/")
+
+ let currentCall = core.callsNb > 0 ? (call != nil) ? call : core.currentCall != nil ? core.currentCall : core.calls[0] : nil
+ if (currentCall == nil) {
+ Log.w("[Audio Route Helper] No call found, setting audio route on Core")
+ }
+ let conference = core.conference
+ let capability = output ? AudioDeviceCapabilities.CapabilityPlay : AudioDeviceCapabilities.CapabilityRecord
+
+ var found = false
+
+ core.audioDevices.forEach { (audioDevice) in
+ Log.i("[Audio Route Helper] cdes [\(audioDevice.deviceName)] [\(audioDevice.type)] [\(audioDevice.capabilities)] ")
+ }
+
+ core.audioDevices.forEach { (audioDevice) in
+ if (!found && types.contains(audioDevice.type) && audioDevice.hasCapability(capability: capability)) {
+ if (conference != nil && conference?.isIn == true) {
+ Log.i("[Audio Route Helper] Found [\(audioDevice.type)] \(output ? "playback" : "recorder") audio device [\(audioDevice.deviceName)], routing conference audio to it")
+ if (output) {
+ conference?.outputAudioDevice = audioDevice
+ } else {
+ conference?.inputAudioDevice = audioDevice
+ }
+ } else if (currentCall != nil) {
+ Log.i("[Audio Route Helper] Found [\(audioDevice.type)] \(output ? "playback" : "recorder") audio device [\(audioDevice.deviceName)], routing call audio to it")
+ if (output) {
+ currentCall?.outputAudioDevice = audioDevice
+ }
+ else {
+ currentCall?.inputAudioDevice = audioDevice
+ }
+ } else {
+ Log.i("[Audio Route Helper] Found [\(audioDevice.type)] \(output ? "playback" : "recorder") audio device [\(audioDevice.deviceName)], changing core default audio device")
+ if (output) {
+ core.outputAudioDevice = audioDevice
+ } else {
+ core.inputAudioDevice = audioDevice
+ }
+ }
+ found = true
+ }
+ }
+ if (!found) {
+ Log.e("[Audio Route Helper] Couldn't find \(typesNames) audio device")
+ }
+ }
+
+ static private func changeCaptureDeviceToMatchAudioRoute(call: Call?, types: [AudioDeviceType]) {
+ switch (types.first) {
+ case .Bluetooth :if (isBluetoothAudioRecorderAvailable()) {
+ Log.i("[Audio Route Helper] Bluetooth device is able to record audio, also change input audio device")
+ applyAudioRouteChange(call: call, types: [AudioDeviceType.Bluetooth], output: false)
+ }
+ case .Headset, .Headphones : if (isHeadsetAudioRecorderAvailable()) {
+ Log.i("[Audio Route Helper] Headphones/headset device is able to record audio, also change input audio device")
+ applyAudioRouteChange(call:call,types: [AudioDeviceType.Headphones, AudioDeviceType.Headset], output:false)
+ }
+ default: break
+ }
+ }
+
+ static private func routeAudioTo( call: Call?, types: [AudioDeviceType]) {
+ let currentCall = call != nil ? call : core.currentCall != nil ? core.currentCall : core.calls[0]
+ if (call != nil || currentCall != nil) {
+ let callToUse = call != nil ? call : currentCall
+ applyAudioRouteChange(call: callToUse, types: types)
+ changeCaptureDeviceToMatchAudioRoute(call: callToUse, types: types)
+ } else {
+ applyAudioRouteChange(call: call, types: types)
+ changeCaptureDeviceToMatchAudioRoute(call: call, types: types)
+ }
+ }
+
+ static func routeAudioToEarpiece(call: Call? = nil) {
+ routeAudioTo(call: call, types: [AudioDeviceType.Microphone]) // on iOS Earpiece = Microphone
+ }
+
+ static func routeAudioToSpeaker(call: Call? = nil) {
+ routeAudioTo(call: call, types: [AudioDeviceType.Speaker])
+ }
+
+ @objc static func routeAudioToSpeaker() {
+ routeAudioTo(call: nil, types: [AudioDeviceType.Speaker])
+ }
+
+ static func routeAudioToBluetooth(call: Call? = nil) {
+ routeAudioTo(call: call, types: [AudioDeviceType.Bluetooth])
+ }
+
+ static func routeAudioToHeadset(call: Call? = nil) {
+ routeAudioTo(call: call, types: [AudioDeviceType.Headphones, AudioDeviceType.Headset])
+ }
+
+ static func isSpeakerAudioRouteCurrentlyUsed(call: Call? = nil) -> Bool {
+
+ let currentCall = core.callsNb > 0 ? (call != nil) ? call : core.currentCall != nil ? core.currentCall : core.calls[0] : nil
+ if (currentCall == nil) {
+ Log.w("[Audio Route Helper] No call found, setting audio route on Core")
+ }
+
+ let conference = core.conference
+ let audioDevice = conference != nil && conference?.isIn == true ? conference!.outputAudioDevice : currentCall != nil ? currentCall!.outputAudioDevice : core.outputAudioDevice
+ Log.i("[Audio Route Helper] Playback audio currently in use is [\(audioDevice?.deviceName ?? "n/a")] with type (\(audioDevice?.type ?? .Unknown)")
+ return audioDevice?.type == AudioDeviceType.Speaker
+ }
+
+ static func isBluetoothAudioRouteCurrentlyUsed(call: Call? = nil) -> Bool {
+ if (core.callsNb == 0) {
+ Log.w("[Audio Route Helper] No call found, so bluetooth audio route isn't used")
+ return false
+ }
+ let currentCall = call != nil ? call : core.currentCall != nil ? core.currentCall : core.calls[0]
+ let conference = core.conference
+
+ let audioDevice = conference != nil && conference?.isIn == true ? conference!.outputAudioDevice : currentCall?.outputAudioDevice
+ Log.i("[Audio Route Helper] Playback audio device currently in use is [\(audioDevice?.deviceName ?? "n/a")] with type (\(audioDevice?.type ?? .Unknown)")
+ return audioDevice?.type == AudioDeviceType.Bluetooth
+ }
+
+ static func isBluetoothAudioRouteAvailable() -> Bool {
+ if let device = core.audioDevices.first(where: { $0.type == AudioDeviceType.Bluetooth && $0.hasCapability(capability: .CapabilityPlay) }) {
+ Log.i("[Audio Route Helper] Found bluetooth audio device [\(device.deviceName)]")
+ return true
+ }
+ return false
+ }
+
+ static private func isBluetoothAudioRecorderAvailable() -> Bool {
+ if let device = core.audioDevices.first(where: { $0.type == AudioDeviceType.Bluetooth && $0.hasCapability(capability: .CapabilityRecord) }) {
+ Log.i("[Audio Route Helper] Found bluetooth audio recorder [\(device.deviceName)]")
+ return true
+ }
+ return false
+ }
+
+ static func isHeadsetAudioRouteAvailable() -> Bool {
+ if let device = core.audioDevices.first(where: { ($0.type == AudioDeviceType.Headset||$0.type == AudioDeviceType.Headphones) && $0.hasCapability(capability: .CapabilityPlay) }) {
+ Log.i("[Audio Route Helper] Found headset/headphones audio device [\(device.deviceName)]")
+ return true
+ }
+ return false
+ }
+
+ static private func isHeadsetAudioRecorderAvailable() -> Bool {
+ if let device = core.audioDevices.first(where: { ($0.type == AudioDeviceType.Headset||$0.type == AudioDeviceType.Headphones) && $0.hasCapability(capability: .CapabilityRecord) }) {
+ Log.i("[Audio Route Helper] Found headset/headphones audio recorder [\(device.deviceName)]")
+ return true
+ }
+ return false
+ }
+
+
+
+ static func isReceiverEnabled() -> Bool {
+ if let outputDevice = core.outputAudioDevice {
+ return outputDevice.type == AudioDeviceType.Microphone
+ }
+ return false
+ }
+
+}
diff --git a/Classes/Voip/Models/CallData.swift b/Classes/Voip/Models/CallData.swift
new file mode 100644
index 000000000..49f5fa2a5
--- /dev/null
+++ b/Classes/Voip/Models/CallData.swift
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linhome
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import linphonesw
+import Foundation
+
+class CallData {
+
+ var call : Call
+ let address :String?
+
+ let isPaused = MutableLiveData()
+ let isRemotelyPaused = MutableLiveData()
+ let canBePaused = MutableLiveData()
+ let isRecording = MutableLiveData()
+ let isRemotelyRecorded = MutableLiveData()
+ let isInRemoteConference = MutableLiveData()
+ let remoteConferenceSubject = MutableLiveData()
+ let isOutgoing = MutableLiveData()
+ let isIncoming = MutableLiveData()
+ let callState = MutableLiveData()
+ let iFrameReceived = MutableLiveData(false)
+ let outgoingEarlyMedia = MutableLiveData()
+ let enteredDTMF = MutableLiveData(" ")
+
+ var chatRoom: ChatRoom? = nil
+
+ private var callDelegate : CallDelegateStub?
+
+ init (call:Call) {
+ self.call = call
+ address = call.remoteAddress?.asStringUriOnly()
+ callDelegate = CallDelegateStub(
+ onStateChanged : { (call: linphonesw.Call, state: linphonesw.Call.State, message: String) -> Void in
+ self.update()
+ },
+ onNextVideoFrameDecoded : { (call: linphonesw.Call) -> Void in
+ self.iFrameReceived.value = true
+ },
+ onRemoteRecording: { (call: linphonesw.Call, recording:Bool) -> Void in
+ self.isRemotelyRecorded.value = true
+ }
+ )
+ call.addDelegate(delegate: callDelegate!)
+ update()
+ initChatRoom()
+ }
+
+
+ private func isCallPaused() -> Bool {
+ return [Call.State.Paused, Call.State.Pausing].contains(call.state)
+ }
+
+ private func isCallRemotelyPaused() -> Bool {
+ return [Call.State.PausedByRemote].contains(call.state)
+ }
+
+ private func isOutGoing() -> Bool {
+ return [Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging].contains(call.state)
+ }
+
+ private func isInComing() -> Bool {
+ return [Call.State.IncomingReceived, Call.State.IncomingEarlyMedia].contains(call.state)
+ }
+
+ private func canCallBePaused() -> Bool {
+ return !call.mediaInProgress() && [Call.State.StreamsRunning, Call.State.PausedByRemote].contains(call.state)
+ }
+
+ private func update() {
+ isPaused.value = isCallPaused()
+ isRemotelyPaused.value = isCallRemotelyPaused()
+ canBePaused.value = canCallBePaused()
+ let conference = call.conference
+ isInRemoteConference.value = conference != nil
+ if (conference != nil) {
+ remoteConferenceSubject.value = conference?.subject != nil && (conference?.subject.count)! > 0 ? conference!.subject : VoipTexts.conference_default_title
+ }
+ isOutgoing.value = isOutGoing()
+ isIncoming.value = isInComing()
+ if (call.mediaInProgress()) {
+ DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
+ self.update()
+ }
+ }
+ outgoingEarlyMedia.value = callState.value == .OutgoingEarlyMedia
+ isRecording.value = call.params?.isRecording
+ callState.value = call.state
+ }
+
+ private func initChatRoom() {
+ let localSipUri = Core.get().defaultAccount?.params?.identityAddress?.asStringUriOnly()
+ let remoteSipUri = call.remoteAddress?.asStringUriOnly()
+
+ guard
+ let localSipUri = Core.get().defaultAccount?.params?.identityAddress?.asStringUriOnly(),
+ let remoteSipUri = call.remoteAddress?.asStringUriOnly(),
+ let localAddress = try?Factory.Instance.createAddress(addr: localSipUri),
+ let remoteSipAddress = try?Factory.Instance.createAddress(addr: remoteSipUri)
+ else {
+ Log.e("[Call] Failed to get either local \(localSipUri.logable) or remote \(remoteSipUri.logable) SIP address!")
+ return
+ }
+ do {
+ chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: [])
+ if (chatRoom == nil) {
+ chatRoom = Core.get().searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress])
+ }
+ if (chatRoom == nil) {
+ Log.w("[Call] Failed to find existing chat room for local address [$localSipUri] and remote address [$remoteSipUri]")
+ let chatRoomParams = try Core.get().createDefaultChatRoomParams()
+ // TODO: configure chat room params
+ chatRoom = try Core.get().createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress])
+ }
+
+ if (chatRoom == nil) {
+ Log.e("[Call] Failed to create a chat room for local address \(localSipUri) and remote address \(remoteSipUri)!")
+ }
+ } catch {
+ Log.e("[Call] Exception caught initiating a chat room for local address \(localSipUri) and remote address \(remoteSipUri) Error : \(error)!")
+ }
+ }
+
+ func sendDTMF(dtmf:String) {
+ enteredDTMF.value = enteredDTMF.value! + dtmf
+ Core.get().playDtmf(dtmf: dtmf.utf8CString[0], durationMs: 1000)
+ try?call.sendDtmf(dtmf: dtmf.utf8CString[0])
+ }
+
+ func destroy() {
+ call.removeDelegate(delegate: callDelegate!)
+ isPaused.clearObservers()
+ isRemotelyPaused.clearObservers()
+ canBePaused.clearObservers()
+ isRecording.clearObservers()
+ isRemotelyRecorded.clearObservers()
+ isInRemoteConference.clearObservers()
+ remoteConferenceSubject.clearObservers()
+ isOutgoing.clearObservers()
+ isIncoming.clearObservers()
+ callState.clearObservers()
+ iFrameReceived.clearObservers()
+ outgoingEarlyMedia.clearObservers()
+ enteredDTMF.clearObservers()
+ }
+
+ func toggleRecord() {
+ if (call.params?.isRecording == true) {
+ call.stopRecording()
+ } else {
+ call.startRecording()
+ }
+ isRecording.value = call.params?.isRecording
+ }
+
+ func togglePause() {
+ if (isCallPaused()) {
+ try?call.resume()
+ } else {
+ try?call.pause()
+ }
+ isPaused.value = isCallPaused()
+ }
+
+}
diff --git a/Classes/Voip/Models/CallStatisticsData.swift b/Classes/Voip/Models/CallStatisticsData.swift
new file mode 100644
index 000000000..5d075d063
--- /dev/null
+++ b/Classes/Voip/Models/CallStatisticsData.swift
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2010-2021 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-android
+ * (see https://www.linphone.org).
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import linphonesw
+
+class CallStatisticsData {
+
+ var call : Call
+ var audioStats:[StatItemData] = []
+ var videoStats:[StatItemData] = []
+ let isVideoEnabled = MutableLiveData()
+ let statsUpdated = MutableLiveData(true)
+
+ private var callDelegate : CallDelegateStub?
+
+ init (call:Call) {
+ self.call = call
+ callDelegate = CallDelegateStub(
+ onStatsUpdated : { (call: Call, stats: CallStats) -> Void in
+ self.isVideoEnabled.value = call.currentParams?.videoEnabled
+ self.updateCallStats(stats: stats)
+ self.statsUpdated.value = true
+ }
+
+ )
+ call.addDelegate(delegate: callDelegate!)
+ initCallStats()
+ isVideoEnabled.value = call.currentParams?.videoEnabled
+ call.audioStats.map { updateCallStats(stats: $0) }
+ call.videoStats.map { updateCallStats(stats: $0) }
+ }
+
+ private func initCallStats() {
+
+ audioStats.append(StatItemData(type: StatType.CAPTURE))
+ audioStats.append(StatItemData(type: StatType.PLAYBACK))
+ audioStats.append(StatItemData(type: StatType.PAYLOAD))
+ audioStats.append(StatItemData(type: StatType.ENCODER))
+ audioStats.append(StatItemData(type: StatType.DECODER))
+ audioStats.append(StatItemData(type: StatType.DOWNLOAD_BW))
+ audioStats.append(StatItemData(type: StatType.UPLOAD_BW))
+ audioStats.append(StatItemData(type: StatType.ICE))
+ audioStats.append(StatItemData(type: StatType.IP_FAM))
+ audioStats.append(StatItemData(type: StatType.SENDER_LOSS))
+ audioStats.append(StatItemData(type: StatType.RECEIVER_LOSS))
+ audioStats.append(StatItemData(type: StatType.JITTER))
+
+ videoStats.append(StatItemData(type: StatType.CAPTURE))
+ videoStats.append(StatItemData(type: StatType.PLAYBACK))
+ videoStats.append(StatItemData(type: StatType.PAYLOAD))
+ videoStats.append(StatItemData(type: StatType.ENCODER))
+ videoStats.append(StatItemData(type: StatType.DECODER))
+ videoStats.append(StatItemData(type: StatType.DOWNLOAD_BW))
+ videoStats.append(StatItemData(type: StatType.UPLOAD_BW))
+ videoStats.append(StatItemData(type: StatType.ESTIMATED_AVAILABLE_DOWNLOAD_BW))
+ videoStats.append(StatItemData(type: StatType.ICE))
+ videoStats.append(StatItemData(type: StatType.IP_FAM))
+ videoStats.append(StatItemData(type: StatType.SENDER_LOSS))
+ videoStats.append(StatItemData(type: StatType.RECEIVER_LOSS))
+ videoStats.append(StatItemData(type: StatType.SENT_RESOLUTION))
+ videoStats.append(StatItemData(type: StatType.RECEIVED_RESOLUTION))
+ videoStats.append(StatItemData(type: StatType.SENT_FPS))
+ videoStats.append(StatItemData(type: StatType.RECEIVED_FPS))
+ }
+
+ private func updateCallStats(stats: CallStats) {
+ if (stats.type == StreamType.Audio) {
+ audioStats.forEach{ $0.update(call: call, stats: stats)}
+ } else if (stats.type == StreamType.Video) {
+ videoStats.forEach{ $0.update(call: call, stats: stats)}
+ }
+ }
+}
+
+
+enum StatType {
+ case CAPTURE
+ case PLAYBACK
+ case PAYLOAD
+ case ENCODER
+ case DECODER
+ case DOWNLOAD_BW
+ case UPLOAD_BW
+ case ICE
+ case IP_FAM
+ case SENDER_LOSS
+ case RECEIVER_LOSS
+ case JITTER
+ case SENT_RESOLUTION
+ case RECEIVED_RESOLUTION
+ case SENT_FPS
+ case RECEIVED_FPS
+ case ESTIMATED_AVAILABLE_DOWNLOAD_BW
+}
+
+struct StatItemData {
+ var type:StatType
+
+ let value = MutableLiveData()
+
+ func update(call: Call, stats: CallStats) {
+ guard let payloadType = stats.type == StreamType.Audio ? call.currentParams?.usedAudioPayloadType : call.currentParams?.usedVideoPayloadType, let core = call.core else {
+ value.value = "n/a"
+ return
+ }
+ switch(type) {
+ case StatType.CAPTURE: value.value = stats.type == StreamType.Audio ? core.captureDevice : core.videoDevice
+ case StatType.PLAYBACK: value.value = stats.type == StreamType.Audio ? core.playbackDevice : core.videoDisplayFilter
+ case StatType.PAYLOAD: value.value = "\(payloadType.mimeType)/\(payloadType.clockRate / 1000) kHz"
+ case StatType.ENCODER: value.value = payloadType.description
+ case StatType.DECODER: value.value = payloadType.description
+ case StatType.DOWNLOAD_BW: value.value = "\(stats.downloadBandwidth) kbits/s"
+ case StatType.UPLOAD_BW: value.value = "\(stats.uploadBandwidth) kbits/s"
+ case StatType.ICE: value.value = stats.iceState.toString()
+ case StatType.IP_FAM: value.value = stats.ipFamilyOfRemote == AddressFamily.Inet6 ? "IPv6" : "IPv4"
+ case StatType.SENDER_LOSS: value.value = String(format: "%.2f%",stats.senderLossRate)
+ case StatType.RECEIVER_LOSS: value.value = String(format: "%.2f%",stats.receiverLossRate)
+ case StatType.JITTER: value.value = String(format: "%.2f ms",stats.jitterBufferSizeMs)
+ case StatType.SENT_RESOLUTION: value.value = call.currentParams?.sentVideoDefinition?.name
+ case StatType.RECEIVED_RESOLUTION: value.value = call.currentParams?.receivedVideoDefinition?.name
+ case StatType.SENT_FPS: value.value = "\(call.currentParams?.sentFramerate ?? 0.0)"
+ case StatType.RECEIVED_FPS: value.value = "\(call.currentParams?.receivedFramerate ?? 0.0)"
+ case StatType.ESTIMATED_AVAILABLE_DOWNLOAD_BW: value.value = "\(stats.estimatedDownloadBandwidth) kbit/s"
+ }
+ }
+
+
+ func getTypeTitle() -> String {
+ switch (type) {
+ case .CAPTURE: return VoipTexts.call_stats_capture_filter
+ case .PLAYBACK: return VoipTexts.call_stats_player_filter
+ case .PAYLOAD: return VoipTexts.call_stats_codec
+ case .ENCODER: return VoipTexts.call_stats_encoder_name
+ case .DECODER: return VoipTexts.call_stats_decoder_name
+ case .DOWNLOAD_BW: return VoipTexts.call_stats_download
+ case .UPLOAD_BW: return VoipTexts.call_stats_upload
+ case .ICE: return VoipTexts.call_stats_ice
+ case .IP_FAM: return VoipTexts.call_stats_ip
+ case .SENDER_LOSS: return VoipTexts.call_stats_sender_loss_rate
+ case .RECEIVER_LOSS: return VoipTexts.call_stats_receiver_loss_rate
+ case .JITTER: return VoipTexts.call_stats_jitter_buffer
+ case .SENT_RESOLUTION: return VoipTexts.call_stats_video_resolution_sent
+ case .RECEIVED_RESOLUTION: return VoipTexts.call_stats_video_resolution_received
+ case .SENT_FPS: return VoipTexts.call_stats_video_fps_sent
+ case .RECEIVED_FPS: return VoipTexts.call_stats_video_fps_received
+ case .ESTIMATED_AVAILABLE_DOWNLOAD_BW: return VoipTexts.call_stats_estimated_download
+ }
+ }
+
+
+
+
+
+}
diff --git a/Classes/Voip/Models/CallsViewModel.swift b/Classes/Voip/Models/CallsViewModel.swift
new file mode 100644
index 000000000..2caa75232
--- /dev/null
+++ b/Classes/Voip/Models/CallsViewModel.swift
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linhome
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import Foundation
+import linphonesw
+import AVFoundation
+
+
+class CallsViewModel {
+
+ let currentCallData = MutableLiveData(nil)
+ let callsData = MutableLiveData<[CallData]>([])
+ let inactiveCallsCount = MutableLiveData(0)
+ let currentCallUnreadChatMessageCount = MutableLiveData(0)
+ let chatAndCallsCount = MutableLiveData(0)
+ let callConnectedEvent = MutableLiveData()
+ let callUpdateEvent = MutableLiveData()
+ let noMoreCallEvent = MutableLiveData(false)
+ let core = Core.get()
+
+ static let shared = CallsViewModel()
+
+ private var coreDelegate : CoreDelegateStub?
+
+ init () {
+ coreDelegate = CoreDelegateStub(
+ onCallStateChanged : { (core: Core, call: Call, state: Call.State, message:String) -> Void in
+ Log.i("[Calls] Call state changed: \(call) : \(state)")
+ let currentCall = core.currentCall
+ if (currentCall != nil && self.currentCallData.value??.call.getCobject != currentCall?.getCobject) {
+ self.updateCurrentCallData(currentCall: currentCall)
+ } else if (currentCall == nil && core.callsNb > 1) {
+ self.updateCurrentCallData(currentCall: currentCall)
+ }
+ if ([.End,.Released,.Error].contains(state)) {
+ self.removeCallFromList(call: call)
+ } else if ([.OutgoingInit].contains(state)) {
+ self.addCallToList(call:call)
+ } else if ([.IncomingReceived].contains(state)) {
+ self.addCallToList(call:call)
+ } else if (state == .UpdatedByRemote) {
+ let remoteVideo = call.remoteParams?.videoEnabled == true
+ let localVideo = call.currentParams?.videoEnabled == true
+ let autoAccept = call.core?.videoActivationPolicy?.automaticallyAccept == true
+ if (remoteVideo && !localVideo && !autoAccept) {
+ if (core.videoCaptureEnabled || core.videoDisplayEnabled) {
+ try?call.deferUpdate()
+ self.callUpdateEvent.value = call
+ } else {
+ call.answerVideoUpdateRequest(accept: false)
+ }
+ }
+ }else if (state == Call.State.Connected) {
+ self.callConnectedEvent.value = call
+ } else if (state == Call.State.StreamsRunning) {
+ self.callUpdateEvent.value = call
+ }
+ self.updateInactiveCallsCount()
+ self.callsData.notifyValue()
+ },
+
+ onMessageReceived : { (core: Core, room: ChatRoom, message: ChatMessage) -> Void in
+ self.updateUnreadChatCount()
+ },
+ onChatRoomRead : { (core: Core, room: ChatRoom) -> Void in
+ self.updateUnreadChatCount()
+ },
+ onLastCallEnded: { (core: Core) -> Void in
+ self.currentCallData.value??.destroy()
+ self.currentCallData.value = nil
+ self.noMoreCallEvent.value = true
+ }
+ )
+
+ Core.get().addDelegate(delegate: coreDelegate!)
+
+ if let currentCall = core.currentCall {
+ currentCallData.value??.destroy()
+ currentCallData.value = CallData(call:currentCall)
+ }
+
+ chatAndCallsCount.value = 0
+ inactiveCallsCount.readCurrentAndObserve { (_) in
+ self.updateCallsAndChatCount()
+ }
+ currentCallUnreadChatMessageCount.readCurrentAndObserve { (_) in
+ self.updateCallsAndChatCount()
+ }
+
+ initCallList()
+ updateInactiveCallsCount()
+ updateUnreadChatCount()
+
+ }
+
+ private func initCallList() {
+ core.calls.forEach { addCallToList(call: $0) }
+ }
+
+ private func removeCallFromList(call: Call) {
+ Log.i("[Calls] Removing call \(call) from calls list")
+ if let removeCandidate = callsData.value?.filter{$0.call.getCobject == call.getCobject}.first {
+ removeCandidate.destroy()
+ }
+
+ callsData.value = callsData.value?.filter(){$0.call.getCobject != call.getCobject}
+ callsData.notifyValue()
+ }
+
+ private func addCallToList(call: Call) {
+ Log.i("[Calls] Adding call \(call) to calls list")
+ callsData.value?.append(CallData(call: call))
+ callsData.notifyValue()
+ }
+
+ private func updateUnreadChatCount() {
+ guard let unread = currentCallData.value??.chatRoom?.unreadMessagesCount else {
+ currentCallUnreadChatMessageCount.value = 0
+ return
+ }
+ currentCallUnreadChatMessageCount.value = unread
+ }
+
+ private func updateInactiveCallsCount() {
+ inactiveCallsCount.value = core.callsNb - 1
+ }
+
+ private func updateCallsAndChatCount() {
+ var value = 0
+ if let calls = inactiveCallsCount.value {
+ value += calls
+ }
+ if let chats = currentCallUnreadChatMessageCount.value {
+ value += chats
+ }
+ chatAndCallsCount.value = value
+ }
+
+ func mergeCallsIntoLocalConference() {
+ CallManager.instance().startLocalConference()
+ }
+
+ private func updateCurrentCallData(currentCall: Call?) {
+ var callToUse = currentCall
+ if (currentCall == nil) {
+ Log.w("[Calls] Current call is now null")
+
+ let firstCall = core.calls.first
+ if (firstCall != nil && currentCallData.value??.call.getCobject != firstCall?.getCobject) {
+ Log.i("[Calls] Using first call as \"current\" call")
+ callToUse = firstCall
+ }
+ }
+
+ guard let callToUse = callToUse else {
+ Log.w("[Calls] No call found to be used as \"current\"")
+ return
+ }
+
+ let firstToUse = callsData.value?.filter{$0.call.getCobject != callToUse.getCobject}.first
+ if (firstToUse != nil) {
+ currentCallData.value = firstToUse
+ } else {
+ Log.w("[Calls] Call not found in calls data list, shouldn't happen!")
+ currentCallData.value = CallData(call: callToUse)
+ }
+
+ updateUnreadChatCount()
+ }
+
+
+}
diff --git a/Classes/Voip/Models/ConferenceParticipantData.swift b/Classes/Voip/Models/ConferenceParticipantData.swift
new file mode 100644
index 000000000..2ccdf95d9
--- /dev/null
+++ b/Classes/Voip/Models/ConferenceParticipantData.swift
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linhome
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import linphonesw
+import Foundation
+
+class ConferenceParticipantData {
+
+ var conference:Conference
+ var participant:Participant
+
+ let isAdmin = MutableLiveData()
+ let isMeAdmin = MutableLiveData()
+
+ private var callDelegate : CallDelegateStub?
+
+ init (conference:Conference, participant:Participant) {
+ self.conference = conference
+ self.participant = participant
+ isAdmin.value = participant.isAdmin
+ isMeAdmin.value = conference.me?.isAdmin
+ Log.i("[Conference Participant] Participant \(sipUri!) is admin=\(isAdmin.value!)")
+
+ }
+
+ var sipUri:String? {
+ get {
+ return self.participant.address?.asString()
+ }
+ }
+
+ func destroy() {
+ isAdmin.clearObservers()
+ isMeAdmin.clearObservers()
+ }
+}
diff --git a/Classes/Voip/Models/ConferenceParticipantDeviceData.swift b/Classes/Voip/Models/ConferenceParticipantDeviceData.swift
new file mode 100644
index 000000000..b10dd635f
--- /dev/null
+++ b/Classes/Voip/Models/ConferenceParticipantDeviceData.swift
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linhome
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import linphonesw
+import Foundation
+
+class ConferenceParticipantDeviceData {
+
+ var participantDevice:ParticipantDevice
+ let isMe:Bool
+
+ let videoEnabled = MutableLiveData()
+ let activeSpeaker = MutableLiveData()
+ let isInConference = MutableLiveData()
+ let core = Core.get()
+
+ private var participantDeviceDelegate : ParticipantDeviceDelegate?
+
+ init (participantDevice:ParticipantDevice, isMe:Bool) {
+ self.isMe = isMe
+ self.participantDevice = participantDevice
+ participantDeviceDelegate = ParticipantDeviceDelegateStub(
+ onIsSpeakingChanged: { (participantDevice, isSpeaking) in
+ Log.i("[Conference Participant Device] Participant \(participantDevice ) isspeaking = \(isSpeaking)")
+ self.activeSpeaker.value = isSpeaking
+ }, onConferenceJoined: { (participantDevice) in
+ Log.i("[Conference Participant Device] Participant \(participantDevice) has joined the conference")
+ self.isInConference.value = true
+ }, onConferenceLeft: { (participantDevice) in
+ Log.i("[Conference Participant Device] Participant \(participantDevice) has left the conference")
+ self.isInConference.value = false
+ }, onAudioDirectionChanged: { (participantDevice, direction) in
+ Log.i("[Conference Participant Device] Participant \(participantDevice) audio stream direction changed: \(direction)")
+ }, onVideoDirectionChanged: { (participantDevice, direction) in
+ Log.i("[Conference Participant Device] Participant \(participantDevice) video stream direction changed: \(direction)")
+ self.videoEnabled.value = direction == MediaDirection.SendOnly || direction == MediaDirection.SendRecv
+ }, onTextDirectionChanged: { (participantDevice, direction) in
+ Log.i("[Conference Participant Device] Participant \(participantDevice) text stream direction changed: \(direction)")
+ })
+
+ participantDevice.addDelegate(delegate: participantDeviceDelegate!)
+ activeSpeaker.value = false
+
+ // TODO: What happens if we have disabled video locally?
+ videoEnabled.value = participantDevice.videoDirection == MediaDirection.SendOnly || participantDevice.videoDirection == MediaDirection.SendRecv
+ isInConference.value = participantDevice.isInConference
+
+ }
+
+ func destroy() {
+ participantDevice.removeDelegate(delegate: participantDeviceDelegate!)
+ }
+
+ func switchCamera() {
+ Core.get().toggleCamera()
+ }
+
+ func isSwitchCameraAvailable() -> Bool {
+ return isMe && Core.get().showSwitchCameraButton()
+ }
+
+ func setVideoView(view:UIView) {
+ if (!isMe && participantDevice.videoDirection != MediaDirection.SendRecv) {
+ Log.e("[Conference Participant Device] Participant \(participantDevice) device video direction is \(participantDevice.videoDirection), don't set video window!")
+ return
+ }
+ Log.i("[Conference Participant Device] Setting textureView \(view) for participant \(participantDevice)")
+ if (isMe) { // TODO: remove
+ core.nativePreviewWindow = view
+ } else {
+ participantDevice.nativeVideoWindowId = UnsafeMutableRawPointer(Unmanaged.passRetained(view).toOpaque())
+ }
+ }
+}
diff --git a/Classes/Voip/Models/ConferenceViewModel.swift b/Classes/Voip/Models/ConferenceViewModel.swift
new file mode 100644
index 000000000..26206c306
--- /dev/null
+++ b/Classes/Voip/Models/ConferenceViewModel.swift
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linhome
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import Foundation
+import linphonesw
+import AVFoundation
+
+class ConferenceViewModel {
+
+ let core = Core.get()
+ static let shared = ConferenceViewModel()
+
+
+ let isConferencePaused = MutableLiveData()
+ let canResumeConference = MutableLiveData()
+
+ let isMeConferenceFocus = MutableLiveData()
+ let isMeAdmin = MutableLiveData()
+
+ let conferenceAddress = MutableLiveData()
+
+ let conferenceParticipants = MutableLiveData<[ConferenceParticipantData]>()
+ let conferenceParticipantDevices = MutableLiveData<[ConferenceParticipantDeviceData]>()
+ let conferenceDisplayMode = MutableLiveData()
+
+ let isInConference = MutableLiveData()
+
+ let isVideoConference = MutableLiveData()
+
+ let isRecording = MutableLiveData()
+ let isRemotelyRecorded = MutableLiveData()
+
+ let subject = MutableLiveData()
+
+ let conference = MutableLiveData()
+
+ let maxParticipantsForMosaicLayout = ConfigManager.instance().lpConfigIntForKey(key: "max_conf_part_mosaic_layout",defaultValue: 6)
+
+ private var conferenceDelegate : ConferenceDelegateStub?
+ private var coreDelegate : CoreDelegateStub?
+
+ init () {
+ conferenceDelegate = ConferenceDelegateStub(onParticipantAdded: { (conference: Conference, participant: Participant)in
+ if (conference.isMe(uri: participant.address!)) {
+ Log.i("[Conference] \(conference) Entered conference")
+ self.isConferencePaused.value = false
+ } else {
+ Log.i("[Conference] \(conference) Participant \(participant) added")
+ }
+ self.updateParticipantsList(conference)
+ self.updateParticipantsDevicesList(conference)
+
+ let count = self.conferenceParticipantDevices.value!.count
+ if (count > self.maxParticipantsForMosaicLayout) {
+ Log.w("[Conference] \(conference) More than \(self.maxParticipantsForMosaicLayout) participants \(count), forcing active speaker layout")
+ self.conferenceDisplayMode.value = .ActiveSpeaker
+ }
+ }, onParticipantRemoved: {(conference: Conference, participant: Participant) in
+ if (conference.isMe(uri: participant.address!)) {
+ Log.i("[Conference] \(conference) \(participant) Left conference")
+ self.isConferencePaused.value = true
+ } else {
+ Log.i("[Conference] \(conference) \(participant) Participant removed")
+ }
+ self.updateParticipantsList(conference)
+ self.updateParticipantsDevicesList(conference)
+ }, onParticipantDeviceAdded: {(conference: Conference, participantDevice: ParticipantDevice) in
+ Log.i("[Conference] \(conference) Participant device \(participantDevice) added")
+ self.updateParticipantsDevicesList(conference)
+ }, onParticipantDeviceRemoved: { (conference: Conference, participantDevice: ParticipantDevice) in
+ Log.i("[Conference] \(conference) Participant device \(participantDevice) removed")
+ self.updateParticipantsDevicesList(conference)
+ }, onParticipantAdminStatusChanged: { (conference: Conference, participant: Participant) in
+ Log.i("[Conference] \(conference) Participant admin status changed")
+ self.isMeAdmin.value = conference.me?.isAdmin
+ self.updateParticipantsList(conference)
+ }
+ )
+
+ coreDelegate = CoreDelegateStub(
+ onConferenceStateChanged: { (core, conference, state) in
+ Log.i("[Conference] \(conference) Conference state changed: \(state)")
+ self.isConferencePaused.value = !conference.isIn
+ self.canResumeConference.value = true // TODO: How can this value be false?
+ self.isVideoConference.value = conference.currentParams?.isVideoEnabled == true
+
+ if (state == Conference.State.Instantiated) {
+ self.conference.value = conference
+ self.isInConference.value = true
+ conference.addDelegate(delegate: self.conferenceDelegate!)
+ } else if (state == Conference.State.Created) {
+ self.updateParticipantsList(conference)
+ self.updateParticipantsDevicesList(conference)
+ self.isMeConferenceFocus.value = conference.me?.isFocus == true
+ self.isMeAdmin.value = conference.me?.isAdmin == true
+ self.conferenceAddress.value = conference.conferenceAddress
+ self.subject.value = conference.subject.isEmpty ? (
+ conference.me?.isFocus == true ? (
+ VoipTexts.conference_local_title
+ ) : (
+ VoipTexts.conference_default_title
+ )
+ ) : (
+ conference.subject
+ )
+ } else if (state == Conference.State.Terminated || state == Conference.State.TerminationFailed) {
+ self.isInConference.value = false
+ self.isVideoConference.value = false
+ conference.removeDelegate(delegate: self.conferenceDelegate!)
+ self.conferenceParticipants.value?.forEach{ $0.destroy()}
+ self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
+ self.conferenceParticipants.value = []
+ self.conferenceParticipantDevices.value = []
+ }
+
+ let layout = conference.layout == .None ? .Grid : conference.layout
+ self.conferenceDisplayMode.value = layout
+ Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
+ }
+ )
+
+ Core.get().addDelegate(delegate: coreDelegate!)
+
+
+ conferenceParticipants.value = []
+ conferenceParticipantDevices.value = []
+ conferenceDisplayMode.value = .Grid
+
+ subject.value = VoipTexts.conference_default_title
+
+ if let conference = core.conference != nil ? core.conference : core.currentCall?.conference {
+ Log.i("[Conference] Found an existing conference: \(conference)")
+ self.conference.value = conference
+ conference.addDelegate(delegate: self.conferenceDelegate!)
+
+
+ isInConference.value = true
+ isConferencePaused.value = !conference.isIn
+ isMeConferenceFocus.value = conference.me?.isFocus == true
+ isMeAdmin.value = conference.me?.isAdmin == true
+ isVideoConference.value = conference.currentParams?.isVideoEnabled == true
+ conferenceAddress.value = conference.conferenceAddress
+ if (!conference.subject.isEmpty) {
+ subject.value = conference.subject
+ }
+
+ let layout = conference.layout == .None ? .Grid : conference.layout
+ conferenceDisplayMode.value = layout
+ Log.i("[Conference] \(conference) Conference current layout is: \(layout)")
+
+ updateParticipantsList(conference)
+ updateParticipantsDevicesList(conference)
+ }
+
+
+ }
+
+
+ func destroy() {
+ core.removeDelegate(delegate: self.coreDelegate!)
+ self.conferenceParticipants.value?.forEach{ $0.destroy()}
+ self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
+ }
+
+
+ func pauseConference() {
+ let defaultProxyConfig = core.defaultProxyConfig
+ let localAddress = defaultProxyConfig?.identityAddress
+ let participants : [Address] = []
+ let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants)
+ let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants)
+ let conference = remoteConference != nil ? remoteConference : localConference
+
+ if (conference != nil) {
+ Log.i("[Conference] Leaving conference with address \(conference) temporarily")
+ conference!.leave()
+ } else {
+ Log.w("[Conference] Unable to find conference with address \(conference)")
+ }
+ }
+
+ func resumeConference() {
+ let defaultProxyConfig = core.defaultProxyConfig
+ let localAddress = defaultProxyConfig?.identityAddress
+ let participants : [Address] = []
+ let remoteConference = core.searchConference(params: nil, localAddr: localAddress, remoteAddr: conferenceAddress.value, participants: participants)
+ let localConference = core.searchConference(params: nil, localAddr: conferenceAddress.value, remoteAddr: conferenceAddress.value, participants: participants)
+
+ if let conference = remoteConference != nil ? remoteConference : localConference {
+ Log.i("[Conference] Entering again conference with address \(conference)")
+ conference.enter()
+ } else {
+ Log.w("[Conference] Unable to find conference with address \(conference)")
+ }
+ }
+
+ func togglePlayPause () {
+ if (isConferencePaused.value == true) {
+ resumeConference()
+ isConferencePaused.value = false
+ } else {
+ pauseConference()
+ isConferencePaused.value = true
+ }
+ }
+
+ func toggleRecording() {
+ guard let conference = core.conference else {
+ Log.e("[Conference] Failed to find conference!")
+ return
+ }
+
+ if (conference.isRecording == true) {
+ conference.stopRecording()
+ } else {
+ let path = AppManager.recordingFilePathFromCall(address: conference.conferenceAddress?.asStringUriOnly() ?? "")
+ Log.i("[Conference] Starting recording \(conference) in file \(path)")
+ conference.startRecording(path: path)
+ }
+ isRecording.value = conference.isRecording
+ }
+
+ private func updateParticipantsList(_ conference: Conference) {
+ self.conferenceParticipants.value?.forEach{ $0.destroy()}
+ var participants :[ConferenceParticipantData] = []
+
+ let participantsList = conference.participantList
+ Log.i("[Conference] \(conference) Conference has \(participantsList.count) participants")
+
+ participantsList.forEach { (participant) in
+ let participantDevices = participant.devices
+ Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)")
+ let participantData = ConferenceParticipantData(conference: conference, participant: participant)
+ participants.append(participantData)
+ }
+
+ conferenceParticipants.value = participants
+ }
+
+ private func updateParticipantsDevicesList(_ conference: Conference) {
+ self.conferenceParticipantDevices.value?.forEach{ $0.destroy()}
+ var devices :[ConferenceParticipantDeviceData] = []
+
+ let participantsList = conference.participantList
+ Log.i("[Conference] \(conference) Conference has \(participantsList.count) participants")
+
+ participantsList.forEach { (participant) in
+ let participantDevices = participant.devices
+ Log.i("[Conference] \(conference) Participant found: \(participant) with \(participantDevices.count) device(s)")
+
+ participantDevices.forEach { (device) in
+ Log.i("[Conference] \(conference) Participant device found: \(device.name) (\(device.address!.asStringUriOnly()))")
+ let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: false)
+ devices.append(deviceData)
+ }
+
+ }
+ conference.me?.devices.forEach { (device) in
+ Log.i("[Conference] \(conference) Participant device for myself found: \(device.name) (\(device.address!.asStringUriOnly()))")
+ let deviceData = ConferenceParticipantDeviceData(participantDevice: device, isMe: true)
+ devices.append(deviceData)
+ }
+
+
+ conferenceParticipantDevices.value = devices
+ }
+
+}
+
+enum FlexDirection {
+ case ROW
+ case ROW_REVERSE
+ case COLUMN
+ case COLUMN_REVERSE
+}
diff --git a/Classes/Voip/Models/ControlsViewModel.swift b/Classes/Voip/Models/ControlsViewModel.swift
new file mode 100644
index 000000000..51109541e
--- /dev/null
+++ b/Classes/Voip/Models/ControlsViewModel.swift
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linhome
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import Foundation
+import linphonesw
+import AVFoundation
+
+
+class ControlsViewModel {
+ let core = Core.get()
+
+ let isSpeakerSelected = MutableLiveData()
+ let isMicrophoneMuted = MutableLiveData()
+ let isMuteMicrophoneEnabled = MutableLiveData()
+ let isBluetoothHeadsetSelected = MutableLiveData()
+ let nonEarpieceOutputAudioDevice = MutableLiveData()
+ let audioRoutesSelected = MutableLiveData()
+ let audioRoutesEnabled = MutableLiveData()
+
+ let isVideoUpdateInProgress = MutableLiveData()
+ let isVideoEnabled = MutableLiveData()
+ let isVideoAvailable = MutableLiveData()
+
+ let fullScreenMode = MutableLiveData(false)
+ let numpadVisible = MutableLiveData(false)
+ let callStatsVisible = MutableLiveData(false)
+ let goToConferenceLayoutSettings = MutableLiveData(false)
+ let goToConferenceParticipantsListEvent = MutableLiveData(false)
+ let goToChatEvent = MutableLiveData(false)
+ let goToCallsListEvent = MutableLiveData(false)
+ let hideExtraButtons = MutableLiveData(true)
+
+ let proximitySensorEnabled = MutableLiveData()
+
+
+ static let shared = ControlsViewModel()
+ private var coreDelegate : CoreDelegateStub?
+ private var previousCallState = Call.State.Idle
+
+
+ init () {
+ coreDelegate = CoreDelegateStub(
+ onCallStateChanged : { (core: Core, call: Call, state: Call.State, message:String) -> Void in
+ Log.i("[Call Controls] Call state changed: \(call) : \(state)")
+ if (state == Call.State.StreamsRunning) {
+ self.isVideoUpdateInProgress.value = false
+ }
+ self.updateUI()
+ self.setAudioRoutes(call,state)
+ self.previousCallState = state
+ },
+ onAudioDeviceChanged : { (core: Core, audioDevice: AudioDevice) -> Void in
+ Log.i("[Call Controls] Audio device changed: \(audioDevice.deviceName)")
+ self.nonEarpieceOutputAudioDevice.value = audioDevice.type != AudioDeviceType.Microphone // on iOS Earpiece = Microphone
+ self.updateSpeakerState()
+ self.updateBluetoothHeadsetState()
+ }
+ )
+ Core.get().addDelegate(delegate: coreDelegate!)
+ proximitySensorEnabled.value = shouldProximitySensorBeEnabled()
+ isVideoEnabled.readCurrentAndObserve { _ in
+ self.proximitySensorEnabled.value = self.shouldProximitySensorBeEnabled()
+ }
+ nonEarpieceOutputAudioDevice.readCurrentAndObserve { _ in
+ self.proximitySensorEnabled.value = self.shouldProximitySensorBeEnabled()
+ }
+ proximitySensorEnabled.readCurrentAndObserve { (enabled) in
+ UIDevice.current.isProximityMonitoringEnabled = enabled == true
+ }
+ updateUI()
+ }
+
+ private func setAudioRoutes(_ call:Call,_ state:Call.State) {
+ if (state == .OutgoingProgress) {
+ if (core.callsNb == 1 && ConfigManager.instance().lpConfigBoolForKey(key: "route_audio_to_bluetooth_if_available",defaultValue:true)) {
+ AudioRouteUtils.routeAudioToBluetooth(call: call)
+ }
+ }
+ if (state == .StreamsRunning) {
+ if (core.callsNb == 1) {
+ // Only try to route bluetooth / headphone / headset when the call is in StreamsRunning for the first time
+ if (previousCallState == Call.State.Connected) {
+ Log.i("[Context] First call going into StreamsRunning state for the first time, trying to route audio to headset or bluetooth if available")
+ if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) {
+ AudioRouteUtils.routeAudioToHeadset(call: call)
+ } else if (ConfigManager.instance().lpConfigBoolForKey(key: "route_audio_to_bluetooth_if_available",defaultValue:true) &&
+ AudioRouteUtils.isBluetoothAudioRouteAvailable()) {
+ AudioRouteUtils.routeAudioToBluetooth(call: call)
+ }
+ }
+ }
+
+ if (ConfigManager.instance().lpConfigBoolForKey(key: "route_audio_to_speaker_when_video_enabled",defaultValue:true) && call.currentParams?.videoEnabled == true) {
+ // Do not turn speaker on when video is enabled if headset or bluetooth is used
+ if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() &&
+ !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed(call: call)
+ ) {
+ Log.i("[Context] Video enabled and no wired headset not bluetooth in use, routing audio to speaker")
+ AudioRouteUtils.routeAudioToSpeaker(call: call)
+ }
+ }
+ }
+ }
+
+
+ private func shouldProximitySensorBeEnabled() -> Bool {
+ return isVideoEnabled.value != true && nonEarpieceOutputAudioDevice.value != true
+ }
+
+
+ func hangUp() {
+ if (core.currentCall != nil) {
+ try?core.currentCall?.terminate()
+ } else if (core.conference?.isIn == true) {
+ try?core.terminateConference()
+ } else {
+ try?core.terminateAllCalls()
+ }
+ }
+
+ func toggleVideo() {
+ if let conference = core.conference, conference.isIn {
+ if let params = try?core.createConferenceParams() {
+ let videoEnabled = conference.currentParams?.isVideoEnabled == true
+ params.videoEnabled = !videoEnabled
+ _ = conference.updateParams(params: params)
+ }
+ } else if let currentCall = core.currentCall {
+ let state = currentCall.state
+ if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) {
+ return
+ }
+ isVideoUpdateInProgress.value = true
+ if let params = try?core.createCallParams(call: currentCall) {
+ params.videoEnabled = !(currentCall.currentParams?.videoEnabled == true)
+ try?currentCall.update(params: params)
+ if (params.videoEnabled) {
+ currentCall.requestNotifyNextVideoFrameDecoded()
+ }
+ }
+ }
+ }
+
+
+ private func updateUI() {
+ updateVideoAvailable()
+ updateVideoEnabled()
+ updateMicState()
+ updateSpeakerState()
+ updateAudioRoutesState()
+ proximitySensorEnabled.value = shouldProximitySensorBeEnabled()
+ }
+
+ private func updateAudioRoutesState() {
+ let bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable()
+ audioRoutesEnabled.value = bluetoothDeviceAvailable
+
+ if (!bluetoothDeviceAvailable) {
+ audioRoutesSelected.value = false
+ audioRoutesEnabled.value = false
+ }
+ }
+
+ private func updateSpeakerState() {
+ isSpeakerSelected.value = AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()
+ }
+
+ private func updateBluetoothHeadsetState() {
+ isBluetoothHeadsetSelected.value = AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed()
+ }
+
+ private func updateVideoAvailable() {
+ let currentCall = core.currentCall
+ isVideoAvailable.value = (core.videoCaptureEnabled || core.videoPreviewEnabled) &&
+ ((currentCall != nil && currentCall?.mediaInProgress() != true) || core.conference?.isIn == true)
+ }
+
+ private func updateVideoEnabled() {
+ let enabled = isVideoCallOrConferenceActive()
+ isVideoEnabled.value = enabled
+ }
+
+ func updateMicState() {
+ isMicrophoneMuted.value = !micAuthorized() || !core.micEnabled
+ isMuteMicrophoneEnabled.value = core.currentCall != nil || core.conference?.isIn == true
+ }
+
+ func micAuthorized() -> Bool {
+ return AVCaptureDevice.authorizationStatus(for: .audio) == .authorized
+ }
+
+ func isVideoCallOrConferenceActive() -> Bool {
+ if let conference = core.conference, conference.isIn {
+ return conference.currentParams?.videoEnabled == true
+ } else {
+ return core.currentCall?.currentParams?.videoEnabled == true
+ }
+ }
+
+ func toggleFullScreen() {
+ if (isVideoEnabled.value == true) {
+ fullScreenMode.value = fullScreenMode.value != true
+ }
+ }
+
+ func toggleMuteMicrophone() {
+ if (!micAuthorized()) {
+ AVAudioSession.sharedInstance().requestRecordPermission { granted in
+ if granted {
+ self.core.micEnabled = !self.core.micEnabled
+ self.updateMicState()
+ }
+ }
+ }
+ core.micEnabled = !core.micEnabled
+ updateMicState()
+ }
+
+ func forceEarpieceAudioRoute() {
+ if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) {
+ Log.i("[Call Controls] Headset found, route audio to it instead of earpiece")
+ AudioRouteUtils.routeAudioToHeadset()
+ } else {
+ AudioRouteUtils.routeAudioToEarpiece()
+ }
+ }
+
+ func forceSpeakerAudioRoute() {
+ AudioRouteUtils.routeAudioToSpeaker()
+ }
+
+ func forceBluetoothAudioRoute() {
+ AudioRouteUtils.routeAudioToBluetooth()
+ }
+
+ func toggleSpeaker() {
+ if (AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()) {
+ forceEarpieceAudioRoute()
+ } else {
+ forceSpeakerAudioRoute()
+ }
+ }
+
+ func toggleRoutesMenu() {
+ audioRoutesSelected.value = audioRoutesSelected.value != true
+ }
+
+}
diff --git a/Classes/CallSideMenuView.h b/Classes/Voip/Theme/ButtonTheme.swift
similarity index 70%
rename from Classes/CallSideMenuView.h
rename to Classes/Voip/Theme/ButtonTheme.swift
index 2a97f78d5..88b8306bb 100644
--- a/Classes/CallSideMenuView.h
+++ b/Classes/Voip/Theme/ButtonTheme.swift
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
- * This file is part of linphone-iphone
+ * This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,15 +17,17 @@
* along with this program. If not, see .
*/
-#import
-#import "SideMenuTableView.h"
-#import "PhoneMainView.h"
+import Foundation
+import UIKit
-@interface CallSideMenuView : UIViewController
-@property(weak, nonatomic) IBOutlet UILabel *statsLabel;
+struct ButtonTheme {
+ var tintableStateIcons: [UInt: TintableIcon] // State indexed
+ var backgroundStateColors: [UInt: LightDarkColor] // State indexed
+}
-- (IBAction)onLateralSwipe:(id)sender;
-
-@end
+struct TintableIcon {
+ var name:String
+ var tintColor: LightDarkColor? = nil
+}
diff --git a/Classes/Voip/Theme/LightDarkColor.swift b/Classes/Voip/Theme/LightDarkColor.swift
new file mode 100644
index 000000000..a01e375b2
--- /dev/null
+++ b/Classes/Voip/Theme/LightDarkColor.swift
@@ -0,0 +1,42 @@
+/*
+ * 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
+
+class LightDarkColor {
+ var light: UIColor
+ var dark : UIColor
+ init(_ l:UIColor,_ d:UIColor){
+ light = l
+ dark = d
+ }
+
+ func get() -> UIColor {
+ if #available(iOS 13.0, *) {
+ if UITraitCollection.current.userInterfaceStyle == .light {
+ return light
+ } else {
+ return dark
+ }
+ } else {
+ return light
+ }
+ }
+
+}
diff --git a/Classes/Voip/Theme/TextStyle.swift b/Classes/Voip/Theme/TextStyle.swift
new file mode 100644
index 000000000..ceff5a5eb
--- /dev/null
+++ b/Classes/Voip/Theme/TextStyle.swift
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import UIKit
+
+struct TextStyle {
+ var fgColor:LightDarkColor
+ var bgColor:LightDarkColor
+ var allCaps:Bool
+ var align:NSTextAlignment
+ var font:String
+ var size:Float
+
+ func boldEd() -> TextStyle {
+ return self.font.contains("Bold") ? self : TextStyle(fgColor: self.fgColor,bgColor: self.bgColor,allCaps: self.allCaps,align: self.align,font: self.font.replacingOccurrences(of: "Regular", with: "Bold"), size: self.size)
+ }
+}
+
+
+extension UILabel {
+ func applyStyle(_ style:TextStyle) {
+ textColor = style.fgColor.get()
+ backgroundColor = style.bgColor.get()
+ if (style.allCaps) {
+ text = self.text?.uppercased()
+ tag = 1
+ }
+ textAlignment = style.align
+ let fontSizeMultiplier: Float = (UIDevice.ipad() ? 1.25 : UIDevice.is5SorSEGen1() ? 0.9 : 1.0)
+ font = UIFont.init(name: style.font, size: CGFloat(style.size*fontSizeMultiplier))
+ }
+}
+
+extension UIButton {
+ func applyTitleStyle(_ style:TextStyle) {
+ titleLabel?.applyStyle(style)
+ if (style.allCaps) {
+ setTitle(self.title(for: .normal)?.uppercased(), for: .normal)
+ tag = 1
+ }
+ setTitleColor(style.fgColor.get(), for: .normal)
+ contentHorizontalAlignment = style.align == .left ? .left : style.align == .center ? .center : style.align == .right ? .right : .left
+ }
+}
diff --git a/Classes/Voip/Theme/VoipTexts.swift b/Classes/Voip/Theme/VoipTexts.swift
new file mode 100644
index 000000000..4df37ec4d
--- /dev/null
+++ b/Classes/Voip/Theme/VoipTexts.swift
@@ -0,0 +1,137 @@
+/*
+ * 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 VoipTexts { // From android key names. Added intentionnally with NSLocalizedString calls for each key, so it can be picked up by translation system (Weblate or Xcode).
+
+ static let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as! String
+
+ // Calls
+ static let call_incoming_title = NSLocalizedString("Incoming Call",comment:"")
+ static let call_outgoing_title = NSLocalizedString("Outgoing Call",comment:"")
+ static let call_notification_paused = NSLocalizedString("Paused call",comment:"")
+ static let call_notification_outgoing = NSLocalizedString("Outgoing call",comment:"")
+ static let call_notification_active = NSLocalizedString("Call running",comment:"")
+ static let call_error_declined = NSLocalizedString("Call has been declined",comment:"")
+ static let call_error_user_busy = NSLocalizedString("User is busy",comment:"")
+ static let call_error_user_not_found = NSLocalizedString("User hasn't been found",comment:"")
+ static let call_error_incompatible_media_params = NSLocalizedString("Incompatible media parameters",comment:"")
+ static let call_error_network_unreachable = NSLocalizedString("Network is unreachable",comment:"")
+ static let call_error_io_error = NSLocalizedString("Service unavailable or network error",comment:"")
+ static let call_error_server_timeout = NSLocalizedString("Server timeout",comment:"")
+ static let call_error_temporarily_unavailable = NSLocalizedString("Temporarily unavailable",comment:"")
+ static let call_error_generic = NSLocalizedString("Error: %s",comment:"")
+ static let call_video_update_requested_dialog = NSLocalizedString("Correspondent would like to turn the video on",comment:"")
+ static let call_action_participants_list = NSLocalizedString("Participants list",comment:"")
+ static let call_action_chat = NSLocalizedString("Chat",comment:"")
+ static let call_action_calls_list = NSLocalizedString("Calls list",comment:"")
+ static let call_action_numpad = NSLocalizedString("Numpad",comment:"")
+ static let call_action_change_conf_layout = NSLocalizedString("Change layout",comment:"")
+ static let call_action_transfer_call = NSLocalizedString("Transfer call",comment:"")
+ static let call_action_add_call = NSLocalizedString("Start new call",comment:"")
+
+ static let call_action_statistics = NSLocalizedString("Call statistics",comment:"")
+ static let call_context_action_resume = NSLocalizedString("Resume call",comment:"")
+ static let call_context_action_pause = NSLocalizedString("Pause call",comment:"")
+ static let call_context_action_transfer = NSLocalizedString("Transfer call",comment:"")
+ static let call_context_action_answer = NSLocalizedString("Answer call",comment:"")
+ static let call_context_action_hangup = NSLocalizedString("Terminate call",comment:"")
+ static let call_remote_recording = NSLocalizedString("This call is being recorded.",comment:"")
+ static let call_remotely_paused_title = NSLocalizedString("Call has been paused by remote.",comment:"")
+
+ // Conference
+ static let conference_schedule_title = NSLocalizedString("Start a conference",comment:"")
+ static let conference_schedule_later = NSLocalizedString("Do you want to schedule this conference for later?",comment:"")
+ static let conference_schedule_mandatory_field = NSLocalizedString("Mandatory",comment:"")
+ static let conference_schedule_subject_title = NSLocalizedString("Subject",comment:"")
+ static let conference_schedule_subject_hint = NSLocalizedString("Conference subject",comment:"")
+ static let conference_schedule_address_title = NSLocalizedString("Conference address",comment:"")
+ static let conference_schedule_description_title = NSLocalizedString("Add a description",comment:"")
+ static let conference_schedule_description_hint = NSLocalizedString("Description",comment:"")
+ static let conference_schedule_date = NSLocalizedString("Date",comment:"")
+ static let conference_schedule_time = NSLocalizedString("Time",comment:"")
+ static let conference_schedule_duration = NSLocalizedString("Duration",comment:"")
+ static let conference_schedule_timezone = NSLocalizedString("Timezone",comment:"")
+ static let conference_schedule_send_invite_chat = NSLocalizedString("Send invite via \(appName)",comment:"")
+ static let conference_schedule_send_invite_email = NSLocalizedString("Send invite via email",comment:"")
+ static let conference_schedule_encryption = NSLocalizedString("Would you like to encrypt the conference?",comment:"")
+ static let conference_schedule_send_invite_chat_summary = NSLocalizedString("Invite will be sent out from my \(appName) account",comment:"")
+ static let conference_schedule_participants_list = NSLocalizedString("Participants list",comment:"")
+ static let conference_schedule_summary = NSLocalizedString("Conference info",comment:"")
+ static let conference_schedule_create = NSLocalizedString("Create conference",comment:"")
+ static let conference_schedule = NSLocalizedString("Schedule conference",comment:"")
+ static let conference_schedule_address_copied_to_clipboard = NSLocalizedString("Conference address copied into clipboard",comment:"")
+ static let conference_schedule_creation_failure = NSLocalizedString("Failed to create conference!",comment:"")
+ static let conference_schedule_info_not_sent_to_participant = NSLocalizedString("Failed to send conference info to a participant",comment:"")
+ static let conference_paused_title = NSLocalizedString("You are currently out of the conference.",comment:"")
+ static let conference_paused_subtitle = NSLocalizedString("Click on play button to join it back.",comment:"")
+ static let conference_default_title = NSLocalizedString("Remote conference",comment:"")
+ static let conference_local_title = NSLocalizedString("Local conference",comment:"")
+ static let conference_invite_title = NSLocalizedString("Conference invite:",comment:"")
+ static let conference_description_title = NSLocalizedString("Description:",comment:"")
+ static let conference_invite_join = NSLocalizedString("Join",comment:"")
+ static let conference_invite_participants_count = NSLocalizedString("%d participants",comment:"")
+ static let conference_display_mode_mosaic = NSLocalizedString("Mosaic mode",comment:"")
+ static let conference_display_mode_active_speaker = NSLocalizedString("Active speaker mode",comment:"")
+ static let conference_display_no_active_speaker = NSLocalizedString("No active speaker",comment:"")
+ static let conference_waiting_room_start_call = NSLocalizedString("Start",comment:"")
+ static let conference_waiting_room_cancel_call = NSLocalizedString("Cancel",comment:"")
+ static let conference_scheduled = NSLocalizedString("Conferences",comment:"")
+ static let conference_too_many_participants_for_mosaic_layout = NSLocalizedString("You can't change conference layout as there is too many participants",comment:"")
+ static let conference_participant_paused = NSLocalizedString("(paused)",comment:"")
+
+
+ // Call Stats
+
+ static let call_stats_audio = "Audio"
+ static let call_stats_video = "Video"
+ static let call_stats_codec = "Codec:"
+ static let call_stats_ip = "IP Family:"
+ static let call_stats_upload = "Upload bandwidth:"
+ static let call_stats_download = "Download bandwidth:"
+ static let call_stats_estimated_download = "Estimated download bandwidth:"
+ static let call_stats_ice = "ICE connectivity:"
+ static let call_stats_video_resolution_sent = "Sent video resolution:"
+ static let call_stats_video_resolution_received = "Received video resolution:"
+ static let call_stats_video_fps_sent = "Sent video fps:"
+ static let call_stats_video_fps_received = "Received video fps:"
+ static let call_stats_sender_loss_rate = "Sender loss rate:"
+ static let call_stats_receiver_loss_rate = "Receiver loss rate:"
+ static let call_stats_jitter_buffer = "Jitter buffer:"
+ static let call_stats_encoder_name = "Encoder:"
+ static let call_stats_decoder_name = "Decoder:"
+ static let call_stats_player_filter = "Player filter:"
+ static let call_stats_capture_filter = "Capture filter:"
+
+
+ // Added in iOS
+ static let camera_required_for_video = NSLocalizedString("Camera use is not Authorized for \(appName). This permission is required to activate Video.",comment:"")
+ static let ok = NSLocalizedString("ok",comment:"")
+ static let cancel = NSLocalizedString("cancel",comment:"")
+
+ static let dialog_accept = NSLocalizedString("Accept",comment:"")
+ static let dialog_decline = NSLocalizedString("Decline",comment:"")
+
+ // Participants list :
+ static let chat_room_group_info_admin = NSLocalizedString("Admin",comment:"")
+
+
+}
diff --git a/Classes/Voip/Theme/VoipTheme.swift b/Classes/Voip/Theme/VoipTheme.swift
new file mode 100644
index 000000000..1a5d77d9d
--- /dev/null
+++ b/Classes/Voip/Theme/VoipTheme.swift
@@ -0,0 +1,342 @@
+/*
+ * 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 VoipTheme { // Names & values replicated from Android
+
+ // Voip Colors
+ static let voip_gray_blue_color = UIColor(hex:"#798791")
+ static let voip_light_gray = UIColor(hex:"#D0D8DE")
+ static let voip_dark_gray = UIColor(hex:"#4B5964")
+ static let voip_gray = UIColor(hex:"#96A5B1")
+ static let voip_gray_background = UIColor(hex:"#D8D8D8")
+ static let voip_call_record_background = UIColor(hex:"#EBEBEB")
+ static let voip_calls_list_inactive_background = UIColor(hex:"#F0F1F2")
+ static let voip_translucent_popup_background = UIColor(hex:"#A64B5964")
+ static let voip_translucent_popup_alt_background = UIColor(hex:"#E64B5964")
+ static let voip_numpad_background = UIColor(hex:"#E4E4E4")
+ static let voip_contact_avatar_background_alt = UIColor(hex:"#AFAFAF")
+ static let voip_contact_avatar_calls_list = UIColor(hex:"#A1A1A1")
+ static let voip_conference_participant_paused_background = UIColor(hex:"#303030")
+ static let voip_drawable_color = UIColor(hex:"#A6B2BC")
+ static let voip_dark_color = UIColor(hex:"#252E35")
+ static let voip_dark_color2 = UIColor(hex:"#3F464B")
+ static let voip_dark_color3 = UIColor(hex:"#475663")
+ static let voip_dark_color4 = UIColor(hex:"#2D3841")
+
+ // General colors (used by VoIP)
+
+ static let primary_color = UIColor(hex:"#ff5e00")
+ static let primary_dark_color = UIColor(hex:"#e65000")
+ static let green_color = UIColor(hex:"#96c11f")
+ static let dark_green_color = UIColor(hex:"#7d9f21")
+ static let toolbar_color = UIColor(hex:"#e1e1e1")
+ static let form_field_gray_background = UIColor(hex:"#F7F7F7")
+ static let light_grey_color = UIColor(hex:"#c4c4c4")
+ static let header_background_color = UIColor(hex:"#f3f3f3")
+ static let dark_grey_color = UIColor(hex:"#444444")
+
+ // Light / Dark variations
+ static let voipBackgroundColor = LightDarkColor(voip_gray_blue_color,voip_dark_color)
+ static let voipBackgroundBWColor = LightDarkColor(UIColor.white,voip_dark_color)
+ static let voipParticipantBackgroundColor = LightDarkColor(voip_gray_background,voip_dark_color2)
+ static let voipExtraButtonsBackgroundColor = LightDarkColor(voip_gray,voip_dark_color3)
+ static let voipToolbarBackgroundColor = LightDarkColor(toolbar_color,voip_dark_color4)
+ static let voipDrawableColor = LightDarkColor(voip_dark_gray,.white)
+ static let voipDrawableColorHighlighted = LightDarkColor(voip_gray,voip_gray)
+ static let voipTextColor = LightDarkColor(voip_dark_gray,UIColor.white)
+ static let voipFormBackgroundColor = LightDarkColor(form_field_gray_background,voip_dark_color4)
+ static let voipFormFieldBackgroundColor = LightDarkColor(light_grey_color,voip_dark_color4)
+ static let voipFormDisabledFieldBackgroundColor = LightDarkColor(header_background_color,voip_dark_color4)
+ static let primarySubtextLightColor = LightDarkColor(light_grey_color,toolbar_color)
+ static let primaryTextColor = LightDarkColor(dark_grey_color,.white)
+
+
+
+
+ // Text styles
+ static let fontName = "Roboto"
+ static let call_header_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 18.0)
+ static let call_header_subtitle = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 14.0)
+ static let call_generated_avatar_large = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 53.0)
+ static let call_generated_avatar_medium = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 27.0)
+ static let call_generated_avatar_small = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Regular", size: 16.0)
+
+ static let dtmf_label = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 30.0)
+ static let call_remote_name = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 18.0)
+ static let call_remote_recording = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 16.0)
+ static let call_or_conference_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 30.0)
+ static let call_or_conference_subtitle = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Bold", size: 20.0)
+ static let basic_popup_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 21.0)
+ static let big_button = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .center, font: fontName+"-Bold", size: 17.0)
+
+ static let call_display_name_duration = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
+ static let call_sip_address = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 14.0)
+ static let voip_extra_button = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
+ static let unread_count_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 11.0)
+ static let call_stats_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
+ static let call_stats_font_title = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
+ static let calls_list_header_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 20.0)
+
+ static let call_list_active_name_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
+ static let call_list_active_sip_uri_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
+
+ static let call_list_name_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
+ static let call_list_sip_uri_font = TextStyle(fgColor: voipTextColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
+
+ static let call_context_menu_item_font = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: true, align: .left, font: fontName+"-Bold", size: 16.0)
+
+ static let conference_participant_admin_label = TextStyle(fgColor: primarySubtextLightColor, bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 13.0)
+ static let conference_participant_name_font = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 18.0)
+ static let conference_participant_sip_uri_font = TextStyle(fgColor: LightDarkColor(primary_color,primary_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .center, font: fontName+"-Regular", size: 12.0)
+ static let conference_participant_name_font_grid = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 15.0)
+ static let conference_participant_name_font_as = TextStyle(fgColor: LightDarkColor(.white,.white), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Bold", size: 12.0)
+
+
+ static let conference_mode_title = TextStyle(fgColor: LightDarkColor(dark_grey_color,dark_grey_color), bgColor: LightDarkColor(.clear,.clear), allCaps: false, align: .left, font: fontName+"-Regular", size: 17.0)
+ static let conference_mode_title_selected = conference_mode_title.boldEd()
+
+
+
+ // Buttons Background (State colors)
+
+ static let button_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
+ UIButton.State.selected.union(.highlighted).rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
+ UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
+ ]
+
+ static let button_background_reverse = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
+ UIButton.State.highlighted.rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.selected.union(.highlighted).rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
+ ]
+
+ static let button_call_recording_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_call_record_background,voip_call_record_background),
+ UIButton.State.selected.rawValue : LightDarkColor(primary_color,primary_color),
+ UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
+ ]
+
+ static let button_toggle_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.selected.rawValue : LightDarkColor(primary_color,primary_color),
+ UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
+ UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
+ ]
+
+ static let button_toggle_background_reverse = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
+ UIButton.State.selected.rawValue : LightDarkColor(primary_color,primary_color),
+ UIButton.State.highlighted.rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
+ ]
+
+
+
+ static let primary_colors_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color),
+ UIButton.State.highlighted.rawValue : LightDarkColor(primary_dark_color,primary_dark_color),
+ ]
+
+ static let primary_colors_background_gray = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.highlighted.rawValue : LightDarkColor(voip_dark_gray,voip_dark_gray),
+ ]
+
+ static let numpad_digit_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_numpad_background,voip_numpad_background),
+ UIButton.State.highlighted.rawValue : LightDarkColor(voip_gray_blue_color,voip_gray_blue_color)
+ ]
+
+ static let button_round_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color),
+ UIButton.State.highlighted.rawValue : LightDarkColor(dark_grey_color,dark_grey_color),
+ UIButton.State.disabled.rawValue : LightDarkColor(voip_light_gray,voip_light_gray)
+ ]
+
+ static let button_call_context_menu_background = [
+ UIButton.State.normal.rawValue : LightDarkColor(voip_gray,voip_gray),
+ UIButton.State.highlighted.rawValue : LightDarkColor(primary_color,primary_color),
+ ]
+
+ // Buttons Icons (State colors) + Background colors
+
+
+ static let call_terminate = ButtonTheme(
+ tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_hangup",tintColor: LightDarkColor(.white,.white))],
+ backgroundStateColors: [
+ UIButton.State.normal.rawValue : LightDarkColor(primary_color,primary_color),
+ UIButton.State.highlighted.rawValue : LightDarkColor(primary_dark_color,primary_dark_color)
+ ])
+
+ static let call_record = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_record",tintColor: LightDarkColor(voip_gray_blue_color,voip_gray_blue_color)),
+ UIButton.State.selected.rawValue : TintableIcon(name: "voip_call_record",tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_record",tintColor: LightDarkColor(primary_color,primary_color)),
+ ],
+ backgroundStateColors: button_call_recording_background)
+
+ static let call_pause = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_pause",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_toggle_background)
+
+ static let call_accept = ButtonTheme(
+ tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_call",tintColor: LightDarkColor(.white,.white))],
+ backgroundStateColors: [
+ UIButton.State.normal.rawValue : LightDarkColor(green_color,green_color),
+ UIButton.State.highlighted.rawValue : LightDarkColor(dark_green_color,dark_green_color)
+ ])
+
+ static let call_mute = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_micro_on",tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.selected.rawValue : TintableIcon(name: "voip_micro_off",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_background_reverse)
+
+ static let call_speaker = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_speaker_off",tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.selected.rawValue : TintableIcon(name: "voip_speaker_on",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_background_reverse)
+
+ static let call_audio_route = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_audio_routes",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_toggle_background_reverse)
+
+
+ static let call_video = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_camera_off",tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.selected.rawValue : TintableIcon(name: "voip_camera_on",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_background_reverse)
+
+ static let call_numpad = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_numpad",tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_numpad",tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray)),
+ UIButton.State.disabled.rawValue : TintableIcon(name: "voip_call_numpad",tintColor: LightDarkColor(voip_light_gray,voip_light_gray)),
+ ],
+ backgroundStateColors: button_background)
+
+ // AUdio routes
+ static let route_bluetooth = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_bluetooth",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_toggle_background_reverse)
+
+ static let route_earpiece = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_earpiece",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_toggle_background_reverse)
+
+ static let route_speaker = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_speaker_on",tintColor: LightDarkColor(.white,.white)),
+ ],
+ backgroundStateColors: button_toggle_background_reverse)
+
+
+
+ static let call_more = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_more",tintColor: LightDarkColor(.white,.white))
+ ],
+ backgroundStateColors: button_background)
+
+
+ static let voip_cancel = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_cancel",tintColor: voipDrawableColor),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_cancel",tintColor: voipDrawableColorHighlighted)
+ ],
+ backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
+
+
+ static let voip_cancel_light = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_cancel",tintColor: LightDarkColor(voip_gray,voip_gray)),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_cancel",tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray))
+ ],
+ backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
+
+ static let voip_edit = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_edit",tintColor: LightDarkColor(dark_grey_color,dark_grey_color)),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_edit",tintColor: voipDrawableColorHighlighted)
+ ],
+ backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
+
+ static let radio_button = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_radio_off",tintColor: LightDarkColor(dark_grey_color,dark_grey_color)),
+ UIButton.State.selected.rawValue : TintableIcon(name: "voip_radio_on",tintColor: LightDarkColor(primary_color,primary_color))
+ ],
+ backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
+
+ static let voip_call_list_active_menu = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: voipDrawableColorHighlighted)
+ ],
+ backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
+
+ static let voip_call_list_menu = ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: voipTextColor),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: "voip_call_list_menu",tintColor: voipDrawableColorHighlighted)
+ ],
+ backgroundStateColors: [UIButton.State.normal.rawValue : LightDarkColor(.clear,.clear)])
+
+
+ static func call_action(_ iconName:String) -> ButtonTheme {
+ return ButtonTheme(
+ tintableStateIcons:[
+ UIButton.State.normal.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(.white,.white)),
+ UIButton.State.highlighted.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(voip_dark_gray,voip_dark_gray)),
+ UIButton.State.disabled.rawValue : TintableIcon(name: iconName,tintColor: LightDarkColor(voip_light_gray,voip_light_gray)),
+ ],
+ backgroundStateColors: [:])
+ }
+
+ static let call_add = ButtonTheme(
+ tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_call_add",tintColor: LightDarkColor(.white,.white))],
+ backgroundStateColors: button_round_background)
+
+ static let call_merge = ButtonTheme(
+ tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_merge_calls",tintColor: LightDarkColor(.white,.white))],
+ backgroundStateColors: button_round_background)
+
+}
+
+
diff --git a/Classes/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift b/Classes/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift
new file mode 100644
index 000000000..f85be7062
--- /dev/null
+++ b/Classes/Voip/Views/CompositeViewControllers/ActiveCallOrConferenceView.swift
@@ -0,0 +1,348 @@
+/*
+ * 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 UIKit
+import linphonesw
+
+
+class ActiveCallOrConferenceView: UIViewController, UICompositeViewDelegate { // Replaces CallView
+
+ // Layout constants
+ let content_inset = 12.0
+
+ var callPausedByRemoteView : PausedCallOrConferenceView? = nil
+ var conferencePausedView : PausedCallOrConferenceView? = nil
+
+ var currentCallView : ActiveCallView? = nil
+ var conferenceGridView: VoipConferenceGridView? = nil
+ var conferenceActiveSpeakerView: VoipConferenceActiveSpeakerView? = nil
+
+ let extraButtonsView = VoipExtraButtonsView()
+ var numpadView : NumpadView? = nil
+ var currentCallStatsVew : CallStatsView? = nil
+ var shadingMask = UIView()
+ var videoAcceptDialog : VoipDialog? = nil
+ var dismissableView : DismissableView? = nil
+ var participantsListView : ParticipantsListView? = nil
+
+ var audioRoutesView : AudioRoutesView? = nil
+
+
+ static let compositeDescription = UICompositeViewDescription(ActiveCallOrConferenceView.self, statusBar: StatusBarView.self, tabBar: nil, sideMenu: nil, fullscreen: false, isLeftFragment: false,fragmentWith: nil)
+ static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
+ func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.backgroundColor = VoipTheme.voipBackgroundColor.get()
+
+ // Hangup
+ let hangup = CallControlButton(width: 65, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
+ ControlsViewModel.shared.hangUp()
+ })
+ view.addSubview(hangup)
+ hangup.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+
+ // Controls
+ let controlsView = ControlsView(showVideo: true)
+ view.addSubview(controlsView)
+ controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
+
+
+ // Container fiew
+ let fullScreenMutableContainerView = UIView()
+ fullScreenMutableContainerView.backgroundColor = .clear
+ self.view.addSubview(fullScreenMutableContainerView)
+ fullScreenMutableContainerView.matchParentSideBorders(insetedByDx: content_inset).matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+ // Current (Single) Call (VoipCallView)
+ currentCallView = ActiveCallView()
+ currentCallView!.isHidden = true
+ fullScreenMutableContainerView.addSubview(currentCallView!)
+ CallsViewModel.shared.currentCallData.readCurrentAndObserve { (currentCallData) in
+ self.updateNavigation()
+ self.currentCallView!.isHidden = currentCallData == nil || ConferenceViewModel.shared.isInConference.value == true
+ self.currentCallView!.callData = currentCallData != nil ? currentCallData! : nil
+ currentCallData??.isRemotelyPaused.readCurrentAndObserve { remotelyPaused in
+ self.callPausedByRemoteView?.isHidden = remotelyPaused != true
+ }
+ if (currentCallData == nil) {
+ self.callPausedByRemoteView?.isHidden = true
+ } else {
+ currentCallData??.isIncoming.readCurrentAndObserve { _ in self.updateNavigation() }
+ currentCallData??.isOutgoing.readCurrentAndObserve { _ in self.updateNavigation() }
+ }
+ self.extraButtonsView.isHidden = true
+ self.conferencePausedView?.isHidden = true
+ }
+
+ currentCallView!.matchParentDimmensions().done()
+
+ // Paused by remote (Call)
+ callPausedByRemoteView = PausedCallOrConferenceView(iconName: "voip_conference_paused_big",titleText: VoipTexts.call_remotely_paused_title,subTitleText: nil)
+ view.addSubview(callPausedByRemoteView!)
+ callPausedByRemoteView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ callPausedByRemoteView?.isHidden = true
+
+ // Conference paused
+ conferencePausedView = PausedCallOrConferenceView(iconName: "voip_conference_paused_big",titleText: VoipTexts.conference_paused_title,subTitleText: VoipTexts.conference_paused_subtitle)
+ view.addSubview(conferencePausedView!)
+ conferencePausedView?.matchParentSideBorders().matchParentHeight().alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ conferencePausedView?.isHidden = true
+
+ // Conference grid
+ conferenceGridView = VoipConferenceGridView()
+ fullScreenMutableContainerView.addSubview(conferenceGridView!)
+ conferenceGridView?.matchParentDimmensions().done()
+ conferenceGridView?.isHidden = true
+ ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isInConference) in
+ self.updateNavigation()
+ if (isInConference == true) {
+ self.currentCallView!.isHidden = true
+ self.extraButtonsView.isHidden = true
+ self.conferencePausedView?.isHidden = true
+ self.conferenceGridView!.isHidden = false
+ self.conferenceActiveSpeakerView!.isHidden = true
+ self.conferenceGridView?.conferenceViewModel = ConferenceViewModel.shared
+ } else {
+ self.conferenceGridView?.isHidden = true
+ }
+ }
+
+ // Conference active speaker
+ conferenceActiveSpeakerView = VoipConferenceActiveSpeakerView()
+ fullScreenMutableContainerView.addSubview(conferenceActiveSpeakerView!)
+ conferenceActiveSpeakerView?.matchParentDimmensions().done()
+ conferenceActiveSpeakerView?.isHidden = true
+
+ // Conference mode switching
+
+ ConferenceViewModel.shared.conferenceDisplayMode.readCurrentAndObserve { (conferenceMode) in
+ if (ConferenceViewModel.shared.isInConference.value == true) {
+ self.conferenceGridView!.isHidden = conferenceMode != .Grid
+ self.conferenceActiveSpeakerView!.isHidden = conferenceMode != .ActiveSpeaker
+ self.conferenceActiveSpeakerView?.conferenceViewModel = ConferenceViewModel.shared
+ } else {
+ self.conferenceActiveSpeakerView?.isHidden = true
+ }
+ }
+
+ ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isInConference) in
+ self.updateNavigation()
+ }
+
+ // Calls List
+ ControlsViewModel.shared.goToCallsListEvent.observe { (_) in
+ self.dismissableView = CallsListView()
+ self.view.addSubview(self.dismissableView!)
+ self.dismissableView?.matchParentDimmensions().done()
+ }
+
+ // Conference Participants List
+ ControlsViewModel.shared.goToConferenceParticipantsListEvent.observe { (_) in
+ self.participantsListView = ParticipantsListView()
+ self.view.addSubview(self.participantsListView!)
+ self.participantsListView?.matchParentDimmensions().done()
+ }
+
+ // Goto chat
+ ControlsViewModel.shared.goToChatEvent.observe { (_) in
+ self.goToChat()
+ }
+
+ // Conference mode selection
+ ControlsViewModel.shared.goToConferenceLayoutSettings.observe { (_) in
+ self.dismissableView = VoipConferenceDisplayModeSelectionView()
+ self.view.addSubview(self.dismissableView!)
+ self.dismissableView?.matchParentDimmensions().done()
+ let activeDisplayMode = ConferenceViewModel.shared.conferenceDisplayMode.value!
+ let indexPath = IndexPath(row: activeDisplayMode == .Grid ? 0 : 1, section: 0)
+ (self.dismissableView as! VoipConferenceDisplayModeSelectionView).optionsListView.selectRow(at:indexPath, animated: true, scrollPosition: .bottom)
+
+ }
+
+ // Shading mask, everything before will be shaded upon displaying of the mask
+ shadingMask.backgroundColor = VoipTheme.voip_translucent_popup_background
+ shadingMask.isHidden = true
+ self.view.addSubview(shadingMask)
+ shadingMask.matchParentDimmensions().done()
+
+ // Extra Buttons
+ let showextraButtons = CallControlButton(imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_more, onClickAction: {
+ self.showModalSubview(view: self.extraButtonsView)
+ ControlsViewModel.shared.audioRoutesSelected.value = false
+ })
+ view.addSubview(showextraButtons)
+ showextraButtons.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+ let boucingCounter = BouncingCounter(inButton:showextraButtons)
+ view.addSubview(boucingCounter)
+ boucingCounter.dataSource = CallsViewModel.shared.chatAndCallsCount
+
+ view.addSubview(extraButtonsView)
+ extraButtonsView.matchParentSideBorders(insetedByDx: content_inset).alignParentBottom().done()
+ ControlsViewModel.shared.hideExtraButtons.readCurrentAndObserve { (_) in
+ self.hideModalSubview(view: self.extraButtonsView)
+ }
+ self.view.onClick {
+ if (!self.extraButtonsView.isHidden) {
+ self.hideModalSubview(view: self.extraButtonsView)
+ }
+ ControlsViewModel.shared.audioRoutesSelected.value = false
+ }
+
+ // Numpad
+ ControlsViewModel.shared.numpadVisible.readCurrentAndObserve { (visible) in
+ if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) {
+ self.numpadView?.removeFromSuperview()
+ self.shadingMask.isHidden = false
+ self.numpadView = NumpadView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!, onDismissAction: {
+ self.numpadView?.removeFromSuperview()
+ self.shadingMask.isHidden = true
+ })
+ }
+ }
+
+ // Call stats
+ ControlsViewModel.shared.callStatsVisible.readCurrentAndObserve { (visible) in
+ if (visible == true && CallsViewModel.shared.currentCallData.value != nil ) {
+ self.currentCallStatsVew?.removeFromSuperview()
+ self.shadingMask.isHidden = false
+ self.currentCallStatsVew = CallStatsView(superView: self.view,callData: CallsViewModel.shared.currentCallData.value!!, onDismissAction: {
+ self.currentCallStatsVew?.removeFromSuperview()
+ self.shadingMask.isHidden = true
+ })
+ }
+ }
+
+ // Video activation dialog request
+ CallsViewModel.shared.callUpdateEvent.observe { (call) in
+ let core = Core.get()
+ if (call?.state == .StreamsRunning) {
+ self.videoAcceptDialog?.removeFromSuperview()
+ self.videoAcceptDialog = nil
+ } else if (call?.state == .UpdatedByRemote) {
+ if (core.videoCaptureEnabled || core.videoDisplayEnabled) {
+ if (call?.currentParams?.videoEnabled != call?.remoteParams?.videoEnabled) {
+ let accept = ButtonAttributes(text:VoipTexts.dialog_accept, action: {call?.answerVideoUpdateRequest(accept: true)}, isDestructive:false)
+ let cancel = ButtonAttributes(text:VoipTexts.dialog_decline, action: {call?.answerVideoUpdateRequest(accept: false)}, isDestructive:true)
+ self.videoAcceptDialog = VoipDialog(message:VoipTexts.call_video_update_requested_dialog, givenButtons: [cancel,accept])
+ self.videoAcceptDialog?.show()
+ }
+ } else {
+ Log.w("[Call] Video display & capture are disabled, don't show video dialog")
+ }
+ }
+ }
+
+ // Audio Routes
+ audioRoutesView = AudioRoutesView()
+ view.addSubview(audioRoutesView!)
+ audioRoutesView!.alignBottomWith(otherView: controlsView).done()
+ ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in
+ self.audioRoutesView!.isHidden = audioRoutesSelected != true
+ }
+ audioRoutesView!.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
+
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(true)
+ extraButtonsView.refresh()
+ ControlsViewModel.shared.callStatsVisible.notifyValue()
+ CallsViewModel.shared.currentCallData.notifyValue()
+ ControlsViewModel.shared.audioRoutesSelected.value = false
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ dismissableView?.removeFromSuperview()
+ dismissableView = nil
+
+ participantsListView?.removeFromSuperview()
+ participantsListView = nil
+
+ ControlsViewModel.shared.fullScreenMode.value = false
+ super.viewWillDisappear(animated)
+ }
+
+ func showModalSubview(view:UIView) {
+ view.isHidden = false
+ shadingMask.isHidden = false
+ }
+ func hideModalSubview(view:UIView) {
+ view.isHidden = true
+ shadingMask.isHidden = true
+ }
+
+ func updateNavigation() {
+ if (Core.get().callsNb == 0) {
+ PhoneMainView.instance().popView(self.compositeViewDescription())
+ } else {
+ if let data = CallsViewModel.shared.currentCallData.value {
+ if (data?.isOutgoing.value == true || data?.isIncoming.value == true) {
+ PhoneMainView.instance().popView(self.compositeViewDescription())
+ } else {
+ PhoneMainView.instance().changeCurrentView(self.compositeViewDescription())
+ }
+ } else {
+ PhoneMainView.instance().changeCurrentView(self.compositeViewDescription())
+ }
+ }
+ }
+
+ func goToChat() {
+ let core = Core.get()
+ guard
+ let localSipUri = core.defaultAccount?.params?.identityAddress?.asStringUriOnly(),
+ let remoteSipUri = ConferenceViewModel.shared.isInConference.value == true ? ConferenceViewModel.shared.conferenceAddress.value?.asStringUriOnly() : core.currentCall?.remoteAddress?.asStringUriOnly(),
+ let localAddress = try?Factory.Instance.createAddress(addr: localSipUri),
+ let remoteSipAddress = try?Factory.Instance.createAddress(addr: remoteSipUri),
+ let chatRoomParams = try?core.createDefaultChatRoomParams()
+ else {
+ return
+ }
+
+ var chatRoom = core.searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: remoteSipAddress, participants: [])
+ if (chatRoom == nil) {
+ chatRoom = core.searchChatRoom(params: nil, localAddr: localAddress, remoteAddr: nil, participants: [remoteSipAddress])
+ }
+ if (chatRoom == nil) {
+ Log.w("[Call] Failed to find existing chat room for local address \(localSipUri) and remote address \(remoteSipUri)")
+
+ // TODO: configure chat room params
+ if (ConferenceViewModel.shared.isInConference.value == true) {
+ // TODO: compute conference participants addresses list
+ } else {
+ chatRoom = try?core.createChatRoom(params: chatRoomParams, localAddr: localAddress, participants: [remoteSipAddress])
+ }
+ }
+
+ if (chatRoom != nil) {
+ PhoneMainView.instance().go(to: chatRoom?.getCobject)
+ } else {
+ Log.w("[Call] Failed to create chat room for local address \(localSipUri) and remote address \(remoteSipUri)")
+ }
+
+ }
+
+
+}
diff --git a/Classes/Voip/Views/CompositeViewControllers/IncomingCallView.swift b/Classes/Voip/Views/CompositeViewControllers/IncomingCallView.swift
new file mode 100644
index 000000000..6009bf0a5
--- /dev/null
+++ b/Classes/Voip/Views/CompositeViewControllers/IncomingCallView.swift
@@ -0,0 +1,81 @@
+/*
+ * 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 UIKit
+import Foundation
+import linphonesw
+
+@objc class IncomingCallView: IncomingOutgoingCommonView, UICompositeViewDelegate {
+
+ // Layout constants
+ let buttons_distance_from_center_x = 38
+
+ static let compositeDescription = UICompositeViewDescription(IncomingCallView.self, statusBar: nil, tabBar: nil, sideMenu: nil, fullscreen: true, isLeftFragment: false,fragmentWith: nil)
+ static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
+ func compositeViewDescription() -> UICompositeViewDescription! { return type(of: self).compositeDescription }
+
+ var earlyMediaView : UIView? = nil
+
+ override func viewDidLoad() {
+
+ super.viewDidLoad(forCallType: VoipTexts.call_incoming_title)
+
+ // Accept
+ let accept = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_accept, onClickAction: {
+ self.callData.map { CallManager.instance().acceptCall(call: $0.call.getCobject, hasVideo: false)}
+ })
+ view.addSubview(accept)
+ accept.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+ // Decline
+ let decline = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
+ self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)}
+ })
+ view.addSubview(decline)
+ decline.centerX(withDx: -buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ }
+
+ @objc override func setCall(call:OpaquePointer) {
+ super.setCall(call: call)
+ callData?.iFrameReceived.observe(onChange: { (video) in
+ if (video == true) {
+ Core.get().nativeVideoWindow = self.earlyMediaView
+ self.earlyMediaView?.isHidden = false
+ }
+ })
+ callData?.callState.readCurrentAndObserve(onChange: { (state) in
+ if (ConfigManager.instance().lpConfigBoolForKey(key: "pref_accept_early_media") && state == .IncomingReceived) {
+ try?self.callData?.call.acceptEarlyMedia()
+ self.callData?.call.requestNotifyNextVideoFrameDecoded()
+ }
+ })
+ callData?.isIncoming.readCurrentAndObserve { (incoming) in
+ if (incoming != true) {
+ PhoneMainView.instance().popView(self.compositeViewDescription())
+ }
+ }
+
+ if (ConfigManager.instance().lpConfigBoolForKey(key: "auto_answer")) {
+ CallManager.instance().acceptCall(call: call, hasVideo: false) // TODO check with old version for Video accept separate button - Not implemented in Android
+ }
+ }
+
+
+}
diff --git a/Classes/Voip/Views/CompositeViewControllers/OutgoingCallView.swift b/Classes/Voip/Views/CompositeViewControllers/OutgoingCallView.swift
new file mode 100644
index 000000000..d2898a148
--- /dev/null
+++ b/Classes/Voip/Views/CompositeViewControllers/OutgoingCallView.swift
@@ -0,0 +1,102 @@
+/*
+ * 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 UIKit
+import Foundation
+import linphonesw
+
+@objc class OutgoingCallView: IncomingOutgoingCommonView, UICompositeViewDelegate {
+
+ // Layout constants
+ let numpad_icon_padding = 10.0
+
+ var numpadView : NumpadView? = nil
+ var showNumPad : CallControlButton? = nil
+ var shadingMask = UIView()
+
+
+ static let compositeDescription = UICompositeViewDescription(OutgoingCallView.self, statusBar: nil, tabBar: nil, sideMenu: nil, fullscreen: true, isLeftFragment: false,fragmentWith: nil)
+ static func compositeViewDescription() -> UICompositeViewDescription! { return compositeDescription }
+ func compositeViewDescription() -> UICompositeViewDescription! { return OutgoingCallView.compositeDescription }
+
+ override func viewDidLoad() {
+ super.viewDidLoad(forCallType: VoipTexts.call_outgoing_title)
+
+ // Cancel
+ let cancelCall = CallControlButton(width: CallControlButton.hungup_width, imageInset:IncomingOutgoingCommonView.answer_decline_inset, buttonTheme: VoipTheme.call_terminate, onClickAction: {
+ self.callData.map { CallManager.instance().terminateCall(call: $0.call.getCobject)}
+ })
+ view.addSubview(cancelCall)
+ cancelCall.alignParentLeft(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+ // Controls
+ let controlsView = ControlsView(showVideo: false)
+ view.addSubview(controlsView)
+ controlsView.alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).centerX().done()
+
+ // Shading mask, everything after will be shaded upon displayed
+ shadingMask.backgroundColor = VoipTheme.voip_translucent_popup_background
+ shadingMask.isHidden = true
+ self.view.addSubview(shadingMask)
+ shadingMask.matchParentDimmensions().done()
+
+ // Numpad
+ showNumPad = CallControlButton(imageInset:UIEdgeInsets(top: numpad_icon_padding, left: numpad_icon_padding, bottom: numpad_icon_padding, right: numpad_icon_padding), buttonTheme: VoipTheme.call_numpad, onClickAction: {
+ self.numpadView?.removeFromSuperview()
+ self.shadingMask.isHidden = false
+ self.numpadView = NumpadView(superView: self.view,callData: self.callData!, onDismissAction: {
+ self.numpadView?.removeFromSuperview()
+ self.shadingMask.isHidden = true
+ })
+ })
+ view.addSubview(showNumPad!)
+ showNumPad?.alignParentRight(withMargin:SharedLayoutConstants.margin_call_view_side_controls_buttons).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+ showNumPad!.isHidden = true
+
+ // Audio Routes
+ let audioRoutesView = AudioRoutesView()
+ view.addSubview(audioRoutesView)
+ audioRoutesView.alignBottomWith(otherView: controlsView).done()
+ ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (audioRoutesSelected) in
+ audioRoutesView.isHidden = audioRoutesSelected != true
+ }
+ audioRoutesView.alignAbove(view:controlsView,withMargin:SharedLayoutConstants.buttons_bottom_margin).matchRightOf(view: controlsView, withMargin:+ControlsView.controls_button_spacing).done()
+ }
+
+
+ override func viewWillAppear(_ animated: Bool) {
+ ControlsViewModel.shared.audioRoutesSelected.value = false
+ super.viewWillAppear(animated)
+ }
+
+ @objc override func setCall(call:OpaquePointer) {
+ super.setCall(call: call)
+ self.callData?.outgoingEarlyMedia.readCurrentAndObserve(onChange: { (outgoingEM) in
+ self.showNumPad!.isHidden = outgoingEM != true
+ })
+ callData?.isOutgoing.readCurrentAndObserve { (outgoing) in
+ if (outgoing != true) {
+ PhoneMainView.instance().popView(self.compositeViewDescription())
+ }
+ }
+ }
+
+
+}
diff --git a/Classes/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift b/Classes/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift
new file mode 100644
index 000000000..2b8194b70
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/ActiveCall/ActiveCallView.swift
@@ -0,0 +1,231 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class ActiveCallView: UIView { // = currentCall
+
+ // Layout constants :
+ static let top_displayname_margin_top = 20.0
+ let sip_address_margin_top = 4.0
+ static let remote_recording_margin_top = 10.0
+ static let remote_recording_height = 30
+ static let bottom_displayname_margin_bottom = 10.0
+ static let bottom_displayname_margin_left = 12.0
+ static let center_view_margin_top = 15.0
+ static let center_view_corner_radius = 20.0
+ let record_pause_button_size = 40
+ let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
+ let record_pause_button_margin = 10.0
+ static let local_video_width = 150.0
+ static let local_video_margins = 15.0
+
+
+ let displayNameTop = StyledLabel(VoipTheme.call_display_name_duration)
+ let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
+ let sipAddress = StyledLabel(VoipTheme.call_sip_address)
+ let remotelyRecordedIndicator = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
+ let avatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
+ let displayNameBottom = StyledLabel(VoipTheme.call_remote_name)
+ var recordCallButtons : [CallControlButton] = []
+ var pauseCallButtons : [CallControlButton] = []
+ let remoteVideo = UIView()
+ let localVideo = LocalVideoView(width: local_video_width)
+
+ var callData: CallData? = nil {
+ didSet {
+ duration.call = callData?.call
+ callData?.call.remoteAddress.map {
+ avatar.fillFromAddress(address: $0)
+ if let displayName = $0.addressBookEnhancedDisplayName() {
+ displayNameTop.text = displayName+" - "
+ displayNameBottom.text = displayName
+ }
+ sipAddress.text = $0.asStringUriOnly()
+ }
+ self.remotelyRecordedIndicator.isRemotelyRecorded = callData?.isRemotelyRecorded
+ callData?.isRecording.readCurrentAndObserve { (selected) in
+ self.recordCallButtons.forEach {
+ $0.isSelected = selected == true
+ }
+ }
+ callData?.isPaused.readCurrentAndObserve { (paused) in
+ self.pauseCallButtons.forEach {
+ $0.isSelected = paused == true
+ }
+ if (paused == true) {
+ self.localVideo.isHidden = true
+ }
+ }
+
+ Core.get().nativeVideoWindow = remoteVideo
+ Core.get().nativePreviewWindow = localVideo
+
+ ControlsViewModel.shared.isVideoEnabled.readCurrentAndObserve{ (video) in
+ self.remoteVideo.isHidden = video != true
+ self.localVideo.isHidden = video != true
+ self.recordCallButtons.first?.isHidden = video != true
+ self.pauseCallButtons.first?.isHidden = video != true
+ self.recordCallButtons.last?.isHidden = video == true
+ self.pauseCallButtons.last?.isHidden = video == true
+ }
+
+ }
+ }
+
+ init() {
+ super.init(frame: .zero)
+ let stack = UIStackView()
+ stack.distribution = .equalSpacing
+ stack.alignment = .bottom
+ stack.spacing = record_pause_button_margin
+ stack.axis = .vertical
+
+ let displayNameDurationSipAddress = UIView()
+
+ displayNameDurationSipAddress.addSubview(displayNameTop)
+ displayNameTop.alignParentLeft().done()
+
+ displayNameDurationSipAddress.addSubview(duration)
+ duration.toRightOf(displayNameTop).alignParentRight().done()
+
+ displayNameDurationSipAddress.addSubview(sipAddress)
+ sipAddress.matchParentSideBorders().alignUnder(view: displayNameTop,withMargin:sip_address_margin_top).done()
+
+ let upperSection = UIStackView()
+ upperSection.distribution = .equalSpacing
+ upperSection.alignment = .center
+ upperSection.spacing = record_pause_button_margin
+ upperSection.axis = .horizontal
+
+ upperSection.addArrangedSubview(displayNameDurationSipAddress)
+ displayNameDurationSipAddress.wrapContentY().done()
+
+ let recordPauseView = UIStackView()
+ recordPauseView.spacing = record_pause_button_margin
+
+ // Record (with video)
+ var recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
+ self.callData.map { $0.toggleRecord() }
+ })
+ recordCallButtons.append(recordCall)
+ recordPauseView.addArrangedSubview(recordCall)
+
+ // Pause (with video)
+ var pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
+ self.callData.map { $0.togglePause() }
+ })
+ pauseCallButtons.append(pauseCall)
+ recordPauseView.addArrangedSubview(pauseCall)
+ upperSection.addArrangedSubview(recordPauseView)
+
+
+ stack.addArrangedSubview(upperSection)
+ upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
+
+
+ stack.addArrangedSubview(remotelyRecordedIndicator)
+ remotelyRecordedIndicator.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
+
+ // Center Section : Avatar + video + record/pause buttons + videos
+ let centerSection = UIView()
+ centerSection.layer.cornerRadius = ActiveCallView.center_view_corner_radius
+ centerSection.clipsToBounds = true
+ centerSection.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
+
+ // Record (w/o video)
+ recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
+ self.callData.map { $0.toggleRecord() }
+ })
+ recordCallButtons.append(recordCall)
+ centerSection.addSubview(recordCall)
+ recordCall.alignParentLeft(withMargin:record_pause_button_margin).alignParentTop(withMargin:record_pause_button_margin).done()
+
+ // Pause (w/o video)
+ pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
+ self.callData.map { $0.togglePause() }
+ })
+ pauseCallButtons.append(pauseCall)
+ centerSection.addSubview(pauseCall)
+ pauseCall.alignParentRight(withMargin:record_pause_button_margin).alignParentTop(withMargin:record_pause_button_margin).done()
+
+ // Avatar
+ centerSection.addSubview(avatar)
+ avatar.square(Avatar.diameter_for_call_views).center().done()
+
+ // Remote Video Display
+ centerSection.addSubview(remoteVideo)
+ remoteVideo.isHidden = true
+ remoteVideo.matchParentDimmensions().done()
+
+ // Local Video Display
+ centerSection.addSubview(localVideo)
+ localVideo.backgroundColor = .black
+ localVideo.alignParentBottom(withMargin: ActiveCallView.local_video_margins).alignParentRight(withMargin: ActiveCallView.local_video_margins).done()
+ localVideo.isHidden = true
+ localVideo.dragZone = centerSection
+
+ // Full screen video togggle
+ remoteVideo.onClick {
+ ControlsViewModel.shared.toggleFullScreen()
+ }
+ ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in
+ if (self.isHidden) {
+ return
+ }
+ self.remoteVideo.removeConstraints().done()
+ self.localVideo.removeConstraints().done()
+ if (fullScreen == true) {
+ self.remoteVideo.removeFromSuperview()
+ self.localVideo.removeFromSuperview()
+ PhoneMainView.instance().mainViewController.view?.addSubview(self.remoteVideo)
+ PhoneMainView.instance().mainViewController.view?.addSubview(self.localVideo)
+ } else {
+ self.remoteVideo.removeFromSuperview()
+ self.localVideo.removeFromSuperview()
+ centerSection.addSubview(self.remoteVideo)
+ centerSection.addSubview(self.localVideo)
+ }
+ self.remoteVideo.matchParentDimmensions().done()
+ self.localVideo.alignParentBottom(withMargin: ActiveCallView.local_video_margins).alignParentRight(withMargin: ActiveCallView.local_video_margins).done()
+ self.localVideo.setSizeConstraint()
+ }
+
+
+ // Bottom display name
+ centerSection.addSubview(displayNameBottom)
+ displayNameBottom.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentRight().alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
+
+ stack.addArrangedSubview(centerSection)
+ centerSection.matchParentSideBorders().alignUnder(view:upperSection,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
+
+ addSubview(stack)
+ stack.matchParentDimmensions().done()
+
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/AudioRoutesView.swift b/Classes/Voip/Views/Fragments/AudioRoutesView.swift
new file mode 100644
index 000000000..1618e3727
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/AudioRoutesView.swift
@@ -0,0 +1,80 @@
+/*
+ * 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 AudioRoutesView: UIStackView {
+
+ // Layout constants
+ let corner_radius = 6.7
+ let margin = 10.0
+
+ init () {
+ super.init(frame: .zero)
+ axis = .vertical
+ distribution = .equalCentering
+ alignment = .center
+ spacing = ControlsView.controls_button_spacing
+ backgroundColor = VoipTheme.voip_gray
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+
+ // bluetooth
+ let blueTooth = CallControlButton(buttonTheme: VoipTheme.route_bluetooth, onClickAction: {
+ ControlsViewModel.shared.forceBluetoothAudioRoute()
+ })
+ addArrangedSubview(blueTooth)
+
+ ControlsViewModel.shared.isBluetoothHeadsetSelected.readCurrentAndObserve { (selected) in
+ blueTooth.isSelected = selected == true
+ }
+
+ // Earpiece
+ let earpiece = CallControlButton(buttonTheme: VoipTheme.route_earpiece, onClickAction: {
+ ControlsViewModel.shared.forceEarpieceAudioRoute()
+ })
+ addArrangedSubview(earpiece)
+ ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (isSpeakerSelected) in
+ earpiece.isSelected = isSpeakerSelected != true && ControlsViewModel.shared.isBluetoothHeadsetSelected.value != true
+ }
+ ControlsViewModel.shared.isBluetoothHeadsetSelected.readCurrentAndObserve { (isBluetoothHeadsetSelected) in
+ earpiece.isSelected = isBluetoothHeadsetSelected != true && ControlsViewModel.shared.isSpeakerSelected.value != true
+ }
+
+ // Speaker
+ let speaker = CallControlButton(buttonTheme: VoipTheme.route_speaker, onClickAction: {
+ ControlsViewModel.shared.forceSpeakerAudioRoute()
+ })
+ addArrangedSubview(speaker)
+ ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (selected) in
+ speaker.isSelected = selected == true
+ }
+
+ size(w:CGFloat(CallControlButton.default_size)+margin, h : 3*CGFloat(CallControlButton.default_size)+2*CGFloat(ControlsView.controls_button_spacing)+margin).done()
+
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
+
+
diff --git a/Classes/Voip/Views/Fragments/CallStatsView.swift b/Classes/Voip/Views/Fragments/CallStatsView.swift
new file mode 100644
index 000000000..142cc03d6
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/CallStatsView.swift
@@ -0,0 +1,105 @@
+/*
+ * 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 linphonesw
+
+@objc class CallStatsView: UIView {
+
+ // Layout constants
+ let side_margins = 10.0
+ let margin_top = 50
+ let corner_radius = 20.0
+ let view_height = 600
+ let audio_video_margin = 20
+
+ init(superView:UIView, callData:CallData, onDismissAction : @escaping ()->Void) {
+ super.init(frame:.zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+ superView.addSubview(self)
+ snp.makeConstraints { make in
+ make.left.equalToSuperview().offset(side_margins)
+ make.right.equalToSuperview().offset(-side_margins)
+ make.height.equalTo(view_height)
+ make.bottom.equalToSuperview().offset(-side_margins)
+ }
+ callData.callState.observe { state in
+ if (state == Call.State.End) {
+ onDismissAction()
+ }
+ }
+
+ let hide = CallControlButton(buttonTheme: VoipTheme.voip_cancel_light, onClickAction: {
+ onDismissAction()
+ })
+ addSubview(hide)
+ hide.alignParentRight(withMargin: side_margins).alignParentTop(withMargin: side_margins).done()
+
+
+ let model = CallStatisticsData(call: callData.call)
+ let audioTitle = StyledLabel(VoipTheme.call_stats_font_title,NSLocalizedString("Audio", comment: ""))
+ addSubview(audioTitle)
+ audioTitle.matchParentSideBorders().alignParentTop(withMargin: margin_top).done()
+
+ let audioStats = StyledLabel(VoipTheme.call_stats_font)
+
+ audioStats.numberOfLines = 0
+ addSubview(audioStats)
+ audioStats.matchParentSideBorders().alignUnder(view: audioTitle).done()
+
+ let videoTitle = StyledLabel(VoipTheme.call_stats_font_title,NSLocalizedString("Video", comment: ""))
+ addSubview(videoTitle)
+ videoTitle.alignUnder(view: audioStats, withMargin:audio_video_margin).matchParentSideBorders().done()
+
+ let videoStats = StyledLabel(VoipTheme.call_stats_font)
+
+ videoStats.numberOfLines = 0
+ addSubview(videoStats)
+ videoStats.matchParentSideBorders().alignUnder(view: videoTitle).done()
+
+ model.isVideoEnabled.readCurrentAndObserve { (video) in
+ videoTitle.isHidden = video != true
+ videoStats.isHidden = video != true
+ }
+
+ model.statsUpdated.readCurrentAndObserve { (updated) in
+ var stats = ""
+ model.audioStats.forEach {
+ stats += "\n\($0.getTypeTitle())\($0.value.value ?? "n/a")"
+ }
+ audioStats.text = stats
+ stats = ""
+ model.videoStats.forEach {
+ stats += "\n\($0.getTypeTitle())\($0.value.value ?? "n/a")"
+ }
+ videoStats.text = stats
+ }
+
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+}
+
+
+
diff --git a/Classes/Voip/Views/Fragments/CallsList/CallsListView.swift b/Classes/Voip/Views/Fragments/CallsList/CallsListView.swift
new file mode 100644
index 000000000..6fad29f47
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/CallsList/CallsListView.swift
@@ -0,0 +1,139 @@
+/*
+ * 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 UIKit
+import Foundation
+import linphonesw
+
+@objc class CallsListView: DismissableView, UITableViewDataSource {
+
+ // Layout constants
+ let buttons_distance_from_center_x = 38
+ let buttons_size = 60
+
+ let callsListTableView = UITableView()
+ let menuView = VoipCallContextMenu()
+
+ var callsDataObserver : MutableLiveDataOnChangeClosure<[CallData]>? = nil
+
+ init() {
+ super.init(title: VoipTexts.call_action_calls_list)
+
+ // New Call
+ let newCall = CallControlButton(width: buttons_size,height: buttons_size, buttonTheme: VoipTheme.call_add, onClickAction: {
+ let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
+ view.setAddress("")
+ CallManager.instance().nextCallIsTransfer = false
+ PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
+ })
+ addSubview(newCall)
+ newCall.centerX(withDx: -buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+ // Merge Calls
+ let mergeIntoLocalConference = CallControlButton(width: buttons_size,height: buttons_size, buttonTheme: VoipTheme.call_merge, onClickAction: {
+ self.removeFromSuperview()
+ CallsViewModel.shared.mergeCallsIntoLocalConference()
+ })
+ addSubview(mergeIntoLocalConference)
+ mergeIntoLocalConference.centerX(withDx: buttons_distance_from_center_x).alignParentBottom(withMargin:SharedLayoutConstants.buttons_bottom_margin).done()
+
+
+ CallsViewModel.shared.callsData.readCurrentAndObserve{ (callsData) in
+ if let callsData = callsData {
+ mergeIntoLocalConference.isEnabled = callsData.count >= 2 && Core.get().conference?.isIn != true
+ } else {
+ mergeIntoLocalConference.isEnabled = false
+ }
+ self.callsListTableView.reloadData()
+ }
+
+
+ // CallsList
+ super.contentView.addSubview(callsListTableView)
+ callsListTableView.matchParentDimmensions().done()
+ callsListTableView.dataSource = self
+ callsListTableView.register(VoipCallCell.self, forCellReuseIdentifier: "VoipCallCell")
+ callsListTableView.allowsSelection = false
+ if #available(iOS 15.0, *) {
+ callsListTableView.allowsFocus = false
+ }
+ callsListTableView.separatorStyle = .singleLine
+ callsListTableView.separatorColor = .white
+ callsListTableView.onClick {
+ self.hideMenu()
+ }
+
+ // Floating menu
+ super.contentView.addSubview(menuView)
+
+ menuView.isHidden = true
+
+ }
+
+
+ func toggleMenu(forCell:VoipCallCell) {
+ if (menuView.isHidden) {
+ showMenu(forCell: forCell)
+ } else if (menuView.callData?.call.callLog?.callId != forCell.callData?.call.callLog?.callId) {
+ hideMenu()
+ showMenu(forCell: forCell)
+ } else {
+ hideMenu()
+ }
+ }
+
+ func showMenu(forCell:VoipCallCell) {
+ menuView.removeConstraints().alignUnder(view: forCell).alignParentRight().done()
+ menuView.callData = forCell.callData
+ menuView.isHidden = false
+ }
+
+ func hideMenu() {
+ menuView.isHidden = true
+ }
+
+ // TableView datasource delegate
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ guard let callsData = CallsViewModel.shared.callsData.value else {
+ return 0
+ }
+ return callsData.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell:VoipCallCell = tableView.dequeueReusableCell(withIdentifier: "VoipCallCell") as! VoipCallCell
+ guard let callData = CallsViewModel.shared.callsData.value?[indexPath.row] else {
+ return cell
+ }
+ cell.selectionStyle = .none
+ cell.callData = callData
+ cell.owningCallsListView = self
+ return cell
+ }
+
+ // View controller
+
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/CallsList/VoipCallCell.swift b/Classes/Voip/Views/Fragments/CallsList/VoipCallCell.swift
new file mode 100644
index 000000000..22c92def4
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/CallsList/VoipCallCell.swift
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipCallCell: UITableViewCell {
+
+ // Layout Constants
+ let cell_height = 80.0
+ let call_status_icon_size = 65.0
+ static let avatar_size = 45.0
+ let avatar_left_margin = 40.0
+ let texts_left_margin = 20.0
+ let side_menu_icon_size = 80.0
+
+
+ var onMenuClickAction : (()->Void) = {}
+ let callStatusIcon = UIImageView()
+ let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:LightDarkColor(VoipTheme.voip_contact_avatar_calls_list,VoipTheme.voip_contact_avatar_calls_list), textStyle: VoipTheme.call_generated_avatar_small)
+ let conferenceAvatar = UIImageView(image:UIImage(named:"voip_multiple_contacts_avatar"))
+ let displayName = StyledLabel(VoipTheme.call_list_active_name_font)
+ let sipAddress = StyledLabel(VoipTheme.call_list_active_sip_uri_font)
+ var menuButton : CallControlButton? = nil
+ var owningCallsListView : CallsListView? = nil
+
+ var callData: CallData? = nil {
+ didSet {
+ if let data = callData {
+ contentView.backgroundColor = data.isPaused.value == true ? VoipTheme.voip_calls_list_inactive_background : VoipTheme.voip_dark_gray
+ callStatusIcon.image =
+ data.isIncoming.value == true ? UIImage(named:"voip_call_header_incoming") :
+ data.isOutgoing.value == true ? UIImage(named:"voip_call_header_outgoing") :
+ data.isPaused.value == true ? UIImage(named:"voip_call_header_paused") :
+ UIImage(named:"voip_call_header_active")
+ if (data.isInRemoteConference.value == true) {
+ avatar.isHidden = true
+ conferenceAvatar.isHidden = false
+ displayName.text = data.remoteConferenceSubject.value
+ } else {
+ displayName.text = data.call.remoteAddress?.addressBookEnhancedDisplayName()
+ avatar.fillFromAddress(address: data.call.remoteAddress!)
+ avatar.isHidden = false
+ conferenceAvatar.isHidden = true
+ }
+ sipAddress.text = data.call.remoteAddress?.asStringUriOnly()
+ displayName.applyStyle(data.isPaused.value == true ? VoipTheme.call_list_name_font : VoipTheme.call_list_active_name_font)
+ sipAddress.applyStyle(data.isPaused.value == true ? VoipTheme.call_list_sip_uri_font : VoipTheme.call_list_active_sip_uri_font)
+ menuButton?.applyTintedIcons(tintedIcons: data.isPaused.value == true ? VoipTheme.voip_call_list_menu.tintableStateIcons : VoipTheme.voip_call_list_active_menu.tintableStateIcons)
+ }
+ }
+ }
+
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ contentView.height(cell_height).matchParentSideBorders().done()
+
+ contentView.addSubview(callStatusIcon)
+ callStatusIcon.size(w: call_status_icon_size, h: call_status_icon_size).done()
+
+ contentView.addSubview(avatar)
+ avatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done()
+
+ contentView.addSubview(conferenceAvatar)
+ conferenceAvatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done()
+
+ let nameAddress = UIView()
+ nameAddress.addSubview(displayName)
+ nameAddress.addSubview(sipAddress)
+ displayName.alignParentTop().done()
+ sipAddress.alignUnder(view: displayName).done()
+ contentView.addSubview(nameAddress)
+ nameAddress.toRightOf(avatar,withLeftMargin:texts_left_margin).toRightOf(conferenceAvatar,withLeftMargin:texts_left_margin).wrapContentY().centerY().done()
+
+ menuButton = CallControlButton(buttonTheme: VoipTheme.voip_call_list_active_menu, onClickAction: {
+ self.owningCallsListView?.toggleMenu(forCell: self)
+ })
+ contentView.addSubview(menuButton!)
+ menuButton!.size(w: side_menu_icon_size, h: side_menu_icon_size).alignParentRight().centerY().done()
+
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/Classes/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift b/Classes/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift
new file mode 100644
index 000000000..79cdaa063
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/CallsList/VoipCallContextMenu.swift
@@ -0,0 +1,160 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipCallContextMenu: UIStackView {
+
+ //Layout constants
+ static let item_height = 50.0
+ let width = 250.0
+ let margin_bw_items = 1.0
+ static let texts_margin_left = 10.0
+
+
+ let resume : ButtonWithStateBackgrounds
+ let pause : ButtonWithStateBackgrounds
+ let transfer : ButtonWithStateBackgrounds
+ let answer : ButtonWithStateBackgrounds
+ let terminate : ButtonWithStateBackgrounds
+
+ var callData: CallData? = nil {
+ didSet {
+ callData?.callState.readCurrentAndObserve(onChange: { (state) in
+ self.resume.isHidden = false
+ self.pause.isHidden = false
+ self.transfer.isHidden = false
+ self.answer.isHidden = false
+ self.terminate.isHidden = false
+ var count = 5.0
+
+ if let callData = self.callData {
+ if (callData.isPaused.value == true ||
+ callData.isIncoming.value == true ||
+ callData.isOutgoing.value == true ||
+ callData.isInRemoteConference.value == true
+ ) {
+ self.pause.isHidden = true
+ count -= 1
+ }
+
+ if (callData.isIncoming.value == true ||
+ callData.isOutgoing.value == true ||
+ callData.isInRemoteConference.value == true
+ ) {
+ self.resume.isHidden = true
+ self.transfer.isHidden = true
+ count -= 2
+ } else if (callData.isPaused.value == false) {
+ self.resume.isHidden = true
+ count -= 1
+ }
+
+ if (callData.isIncoming.value == false) {
+ count -= 1
+ self.answer.isHidden = true
+ }
+ self.size(w:self.width,h:count*VoipCallContextMenu.item_height).done()
+ }
+
+ })
+ }
+ }
+
+
+ init () {
+
+ resume = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_resume)
+ pause = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_pause)
+ transfer = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_transfer)
+ answer = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_answer)
+ terminate = VoipCallContextMenu.getButton(title: VoipTexts.call_context_action_hangup)
+
+ super.init(frame: .zero)
+ backgroundColor = .white
+ axis = .vertical
+ spacing = margin_bw_items
+
+
+ addArrangedSubview(resume)
+ addArrangedSubview(pause)
+ addArrangedSubview(transfer)
+ addArrangedSubview(answer)
+ addArrangedSubview(terminate)
+
+ resume.onClick {
+ self.isHidden = true
+ guard let call = self.callData?.call else { return }
+ if (CallManager.callKitEnabled()) {
+ CallManager.instance().setHeld(call:call,hold:false);
+ } else {
+ try?call.resume()
+ }
+ }
+ pause.onClick {
+ self.isHidden = true
+ guard let call = self.callData?.call else { return }
+ if (CallManager.callKitEnabled()) {
+ CallManager.instance().setHeld(call:call,hold:true);
+ } else {
+ try?call.pause()
+ }
+ }
+ transfer.onClick {
+ let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
+ view.setAddress("")
+ CallManager.instance().nextCallIsTransfer = true
+ PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
+ }
+ answer.onClick {
+ self.isHidden = true
+ guard let call = self.callData?.call else { return }
+ if (CallManager.callKitEnabled()) {
+ CallManager.instance().acceptCall(call: call, hasVideo: false)
+ } else {
+ try?call.accept()
+ }
+ }
+ terminate.onClick {
+ self.isHidden = true
+ guard let call = self.callData?.call else { return }
+ try?call.terminate()
+ }
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ static func getButton(title:String) -> ButtonWithStateBackgrounds {
+ let button = ButtonWithStateBackgrounds(backgroundStateColors: VoipTheme.button_call_context_menu_background)
+ button.setTitle(title, for: .normal)
+ button.applyTitleStyle(VoipTheme.call_context_menu_item_font)
+ button.titleEdgeInsets = UIEdgeInsets(top: 0, left: texts_margin_left, bottom: 0, right: 0)
+ button.height(VoipCallContextMenu.item_height).done()
+ return button
+ }
+
+
+
+}
diff --git a/Classes/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift b/Classes/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift
new file mode 100644
index 000000000..02c2f384f
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/Conference/VoipActiveSpeakerParticipantCell.swift
@@ -0,0 +1,121 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipActiveSpeakerParticipantCell: UICollectionViewCell {
+
+ // Layout Constants
+ let corner_radius = 20.0
+ static let avatar_size = 80.0
+ let switch_camera_button_margins = 8.0
+ let switch_camera_button_size = 30
+
+
+ let videoView = UIView()
+ let avatar = Avatar(diameter:VoipGridParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
+ let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
+ let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
+ let displayName = StyledLabel(VoipTheme.conference_participant_name_font_as)
+ let pauseLabel = StyledLabel(VoipTheme.conference_participant_name_font_as,VoipTexts.conference_participant_paused)
+
+ var participantData: ConferenceParticipantDeviceData? = nil {
+ didSet {
+ if let data = participantData {
+ data.isInConference.readCurrentAndObserve { (isIn) in
+ self.updateBackground()
+ self.pause.isHidden = isIn == true
+ self.pauseLabel.isHidden = self.pause.isHidden
+ }
+ data.videoEnabled.readCurrentAndObserve { (videoEnabled) in
+ self.updateBackground()
+ if (videoEnabled == true) {
+ data.setVideoView(view: self.videoView)
+ self.avatar.isHidden = true
+ } else {
+ self.avatar.isHidden = false
+ }
+ self.switchCamera.isHidden = videoEnabled != true || !data.isSwitchCameraAvailable()
+ }
+ data.participantDevice.address.map {
+ avatar.fillFromAddress(address: $0)
+ if let displayName = $0.addressBookEnhancedDisplayName() {
+ self.displayName.text = displayName
+ }
+ }
+ }
+ }
+ }
+
+ func updateBackground() {
+ if let data = participantData {
+ if (data.isInConference.value != true) {
+ self.contentView.backgroundColor = VoipTheme.voip_conference_participant_paused_background
+ } else if (data.videoEnabled.value == true) {
+ self.contentView.backgroundColor = .black
+ } else {
+ self.contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
+
+ }
+ }
+ }
+
+
+ override init(frame:CGRect) {
+ super.init(frame:.zero)
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+
+ contentView.addSubview(videoView)
+ videoView.matchParentDimmensions().done()
+
+ contentView.addSubview(avatar)
+ avatar.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
+
+ contentView.addSubview(pause)
+ pause.layer.cornerRadius = VoipGridParticipantCell.avatar_size/2
+ pause.clipsToBounds = true
+ pause.backgroundColor = VoipTheme.voip_gray
+ pause.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
+
+ contentView.addSubview(switchCamera)
+ switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done()
+ switchCamera.contentMode = .scaleAspectFit
+
+ switchCamera.onClick {
+ Core.get().toggleCamera()
+ }
+
+ contentView.addSubview(displayName)
+ displayName.matchParentSideBorders(insetedByDx:ActiveCallView.bottom_displayname_margin_left).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
+
+ contentView.addSubview(pauseLabel)
+ pauseLabel.toRightOf(displayName).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
+
+ contentView.matchParentDimmensions().done()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/Classes/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift b/Classes/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift
new file mode 100644
index 000000000..e0bc2d937
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/Conference/VoipConferenceActiveSpeakerView.swift
@@ -0,0 +1,260 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipConferenceActiveSpeakerView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
+
+ // Layout constants :
+ let inter_cell = 10.0
+ let record_pause_button_margin = 10.0
+ let duration_margin_top = 4.0
+ let record_pause_button_size = 40
+ let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
+ let grid_height = 150.0
+ let cell_width = 100.0
+
+
+ let subjectLabel = StyledLabel(VoipTheme.call_display_name_duration)
+ let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
+
+ let remotelyRecording = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
+ var recordCallButtons : [CallControlButton] = []
+ var pauseCallButtons : [CallControlButton] = []
+
+ let activeSpeakerView = UIView()
+ let activeSpeakerVideoView = UIView()
+ let activeSpeakerAvatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views), color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
+ let activeSpeakerDisplayName = StyledLabel(VoipTheme.call_remote_name)
+ var activeSpeakerMonitorTimer : Timer? = nil
+
+ var grid : UICollectionView
+
+
+ var conferenceViewModel: ConferenceViewModel? = nil {
+ didSet {
+ if let model = conferenceViewModel {
+ model.subject.readCurrentAndObserve { (subject) in
+ self.subjectLabel.text = subject
+ }
+ duration.conference = model.conference.value
+ self.remotelyRecording.isRemotelyRecorded = model.isRemotelyRecorded
+ model.conferenceParticipantDevices.readCurrentAndObserve { (_) in
+ self.grid.reloadData()
+ }
+ model.isConferencePaused.readCurrentAndObserve { (paused) in
+ self.pauseCallButtons.forEach {
+ $0.isSelected = paused == true
+ }
+ }
+ model.isRecording.readCurrentAndObserve { (selected) in
+ self.recordCallButtons.forEach {
+ $0.isSelected = selected == true
+ }
+ }
+ Core.get().nativeVideoWindow = self.activeSpeakerVideoView
+ activeSpeakerMonitorTimer?.invalidate()
+ activeSpeakerMonitorTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
+ var thereIsAnActiveSpeaker = false
+ model.conferenceParticipantDevices.value?.forEach { (data) in
+ if (data.activeSpeaker.value == true) {
+ thereIsAnActiveSpeaker = true
+ data.participantDevice.address.map {
+ self.activeSpeakerAvatar.isHidden = false
+ self.activeSpeakerAvatar.fillFromAddress(address: $0)
+ self.activeSpeakerDisplayName.text = $0.addressBookEnhancedDisplayName()
+ }
+ self.activeSpeakerVideoView.isHidden = data.videoEnabled.value != true
+ return
+ }
+ }
+ if (!thereIsAnActiveSpeaker) {
+ self.activeSpeakerAvatar.isHidden = true
+ self.activeSpeakerVideoView.isHidden = true
+ self.activeSpeakerDisplayName.text = VoipTexts.conference_display_no_active_speaker
+ }
+ }
+ } else {
+ activeSpeakerMonitorTimer?.invalidate()
+ }
+ self.grid.reloadData()
+
+ }
+ }
+
+ init() {
+
+ let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
+ layout.minimumInteritemSpacing = 0
+ layout.minimumLineSpacing = 0
+ layout.scrollDirection = .horizontal
+ layout.itemSize = CGSize(width:cell_width, height:grid_height)
+ grid = UICollectionView(frame:.zero, collectionViewLayout: layout)
+
+ super.init(frame: .zero)
+
+ let headerView = UIStackView()
+ addSubview(headerView)
+ headerView.matchParentSideBorders().alignParentTop().done()
+
+ headerView.distribution = .equalSpacing
+ headerView.alignment = .bottom
+ headerView.spacing = record_pause_button_margin
+ headerView.axis = .vertical
+
+ let subjectDuration = UIView()
+
+ subjectDuration.addSubview(subjectLabel)
+ subjectLabel.alignParentLeft().done()
+
+ subjectDuration.addSubview(duration)
+ duration.alignParentLeft().alignUnder(view: subjectLabel,withMargin:duration_margin_top).done()
+
+ let upperSection = UIStackView()
+ upperSection.distribution = .equalSpacing
+ upperSection.alignment = .center
+ upperSection.spacing = record_pause_button_margin
+ upperSection.axis = .horizontal
+
+ upperSection.addArrangedSubview(subjectDuration)
+ subjectDuration.wrapContentY().done()
+
+ // Record (with video)
+ let recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
+ self.conferenceViewModel?.toggleRecording()
+ })
+
+ let recordPauseView = UIStackView()
+ recordPauseView.spacing = record_pause_button_margin
+ recordCallButtons.append(recordCall)
+ recordPauseView.addArrangedSubview(recordCall)
+
+ // Pause (with video)
+ let pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
+ self.conferenceViewModel?.togglePlayPause()
+
+ })
+ pauseCallButtons.append(pauseCall)
+ recordPauseView.addArrangedSubview(pauseCall)
+
+ upperSection.addArrangedSubview(recordPauseView)
+
+ headerView.addArrangedSubview(upperSection)
+ upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
+
+ headerView.addArrangedSubview(remotelyRecording)
+ remotelyRecording.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
+
+
+ // Container view that can toggle full screen by ckick
+ let fullScreenMutableView = UIView()
+ addSubview(fullScreenMutableView)
+ fullScreenMutableView.backgroundColor = VoipTheme.voipBackgroundColor.get()
+ fullScreenMutableView.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
+
+
+ // Active speaker
+ fullScreenMutableView.addSubview(activeSpeakerView)
+ activeSpeakerView.layer.cornerRadius = ActiveCallView.center_view_corner_radius
+ activeSpeakerView.clipsToBounds = true
+ activeSpeakerView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
+ activeSpeakerView.matchParentSideBorders().alignParentTop().done()
+
+ activeSpeakerView.addSubview(activeSpeakerAvatar)
+ activeSpeakerAvatar.square(Avatar.diameter_for_call_views).center().done()
+
+ activeSpeakerView.addSubview(activeSpeakerVideoView)
+ activeSpeakerVideoView.matchParentDimmensions().done()
+
+ activeSpeakerView.addSubview(activeSpeakerDisplayName)
+ activeSpeakerDisplayName.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentRight().alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
+
+ // CollectionView
+ grid.dataSource = self
+ grid.delegate = self
+ grid.register(VoipActiveSpeakerParticipantCell.self, forCellWithReuseIdentifier: "VoipActiveSpeakerParticipantCell")
+ grid.backgroundColor = .clear
+ grid.isScrollEnabled = true
+ fullScreenMutableView.addSubview(grid)
+
+ grid.matchParentSideBorders().height(grid_height).alignParentBottom().alignUnder(view: activeSpeakerView, withMargin:ActiveCallView.center_view_margin_top).done()
+
+ // Full screen video togggle
+ activeSpeakerView.onClick {
+ ControlsViewModel.shared.toggleFullScreen()
+ }
+
+ ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in
+ if (self.isHidden) {
+ return
+ }
+ fullScreenMutableView.removeConstraints().done()
+ if (fullScreen == true) {
+ fullScreenMutableView.removeFromSuperview()
+ PhoneMainView.instance().mainViewController.view?.addSubview(fullScreenMutableView)
+ fullScreenMutableView.matchParentDimmensions().done()
+ } else {
+ fullScreenMutableView.removeFromSuperview()
+ self.addSubview(fullScreenMutableView)
+ fullScreenMutableView.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
+ }
+ }
+
+ }
+
+
+ // UICollectionView related delegates
+
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+ return inter_cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, layout
+ collectionViewLayout: UICollectionViewLayout,
+ minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+ return inter_cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
+ return .zero
+ }
+ return participantsCount
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let cell:VoipActiveSpeakerParticipantCell = collectionView.dequeueReusableCell(withReuseIdentifier: "VoipActiveSpeakerParticipantCell", for: indexPath) as! VoipActiveSpeakerParticipantCell
+ guard let participantData = conferenceViewModel?.conferenceParticipantDevices.value?[indexPath.row] else {
+ return cell
+ }
+ cell.participantData = participantData
+ return cell
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+
+
+}
diff --git a/Classes/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift b/Classes/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift
new file mode 100644
index 000000000..42a694bbd
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/Conference/VoipConferenceDisplayModeSelectionView.swift
@@ -0,0 +1,140 @@
+/*
+ * 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 UIKit
+import Foundation
+import linphonesw
+
+@objc class VoipConferenceDisplayModeSelectionView: DismissableView, UITableViewDataSource, UITableViewDelegate{
+
+ // Layout constants
+ let buttons_distance_from_center_x = 38
+ let buttons_size = 60
+
+ let optionsListView = UITableView()
+
+ init() {
+ super.init(title: VoipTexts.call_action_change_conf_layout)
+
+ super.contentView.addSubview(optionsListView)
+ optionsListView.matchParentDimmensions().done()
+ optionsListView.dataSource = self
+ optionsListView.delegate = self
+ optionsListView.register(ConferenceDisplayModeSelectionCell.self, forCellReuseIdentifier: "ConferenceDisplayModeSelectionCell")
+ optionsListView.separatorStyle = .singleLine
+ optionsListView.separatorColor = VoipTheme.light_grey_color
+ optionsListView.isScrollEnabled = false
+ }
+
+ // TableView datasource delegate
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return 2
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell:ConferenceDisplayModeSelectionCell = tableView.dequeueReusableCell(withIdentifier: "ConferenceDisplayModeSelectionCell") as! ConferenceDisplayModeSelectionCell
+ cell.selectionStyle = .none
+ if (indexPath.row == 0) {
+ cell.setOption(title: VoipTexts.conference_display_mode_mosaic, onSelectAction: {
+ ConferenceViewModel.shared.conference.value?.layout = .Grid
+ ConferenceViewModel.shared.conferenceDisplayMode.value = .Grid
+ }, image:(UIImage(named: "voip_conference_mosaic")?.tinted(with: VoipTheme.voipDrawableColor.get())!)!)
+ cell.isUserInteractionEnabled = ConferenceViewModel.shared.conferenceParticipantDevices.value!.count <= ConferenceViewModel.shared.maxParticipantsForMosaicLayout
+ }
+ if (indexPath.row == 1) {
+ cell.setOption(title: VoipTexts.conference_display_mode_active_speaker, onSelectAction: {
+ ConferenceViewModel.shared.conference.value?.layout = .ActiveSpeaker
+ ConferenceViewModel.shared.conferenceDisplayMode.value = .ActiveSpeaker
+ }, image:(UIImage(named: "voip_conference_active_speaker")?.tinted(with: VoipTheme.voipDrawableColor.get())!)!)
+ cell.isUserInteractionEnabled = true
+ }
+
+ cell.separatorInset = .zero
+ cell.selectionStyle = .none
+ return cell
+ }
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let cell = tableView.cellForRow(at: indexPath) as! ConferenceDisplayModeSelectionCell
+ cell.onSelectAction?()
+ cell.isSelected = true
+ if (indexPath.row == 0) {
+ tableView.deselectRow(at: IndexPath(row: 1, section: 0), animated: false)
+ }
+ if (indexPath.row == 1) {
+ tableView.deselectRow(at: IndexPath(row: 0, section: 0), animated: false)
+ }
+ }
+
+ func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
+ let cell = tableView.cellForRow(at: indexPath) as! ConferenceDisplayModeSelectionCell
+ cell.isSelected = false
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
+
+class ConferenceDisplayModeSelectionCell : UITableViewCell {
+
+ let cell_height = 60.0
+ let icon_size = 40.0
+ let side_margins = 20.0
+
+ let radio = CallControlButton(buttonTheme: VoipTheme.radio_button)
+ let label = StyledLabel(VoipTheme.conference_mode_title)
+ let icon = UIImageView()
+
+ var onSelectAction : (()->Void)? = nil
+
+ override var isSelected: Bool {
+ didSet {
+ radio.isSelected = isSelected
+ label.applyStyle(isSelected ? VoipTheme.conference_mode_title_selected : VoipTheme.conference_mode_title)
+ }
+ }
+
+
+ func setOption(title:String, onSelectAction:@escaping ()->Void, image:UIImage) {
+ self.onSelectAction = onSelectAction
+ label.text = title
+ icon.image = image
+ }
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ contentView.height(cell_height).matchParentSideBorders().done()
+ contentView.addSubview(radio)
+ radio.alignParentLeft(withMargin: side_margins).centerY().done()
+ contentView.addSubview(label)
+ label.toRightOf(radio).centerY().done()
+ contentView.addSubview(icon)
+ icon.size(w: icon_size, h: icon_size).alignParentRight(withMargin: side_margins).centerY().done()
+ radio.isUserInteractionEnabled = false
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift b/Classes/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift
new file mode 100644
index 000000000..1b6509c03
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/Conference/VoipConferenceGridView.swift
@@ -0,0 +1,242 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipConferenceGridView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
+
+ // Layout constants :
+ let inter_cell = 10.0
+ let record_pause_button_margin = 10.0
+ let duration_margin_top = 4.0
+ let record_pause_button_size = 40
+ let record_pause_button_inset = UIEdgeInsets(top: 7, left: 7, bottom: 7, right: 7)
+
+
+ let subjectLabel = StyledLabel(VoipTheme.call_display_name_duration)
+ let duration = CallTimer(nil, VoipTheme.call_display_name_duration)
+
+ let remotelyRecording = RemotelyRecordingView(height: ActiveCallView.remote_recording_height,text: VoipTexts.call_remote_recording)
+ var recordCallButtons : [CallControlButton] = []
+ var pauseCallButtons : [CallControlButton] = []
+ var grid : UICollectionView
+
+
+ var conferenceViewModel: ConferenceViewModel? = nil {
+ didSet {
+ if let model = conferenceViewModel {
+ model.subject.readCurrentAndObserve { (subject) in
+ self.subjectLabel.text = subject
+ }
+ duration.conference = model.conference.value
+ self.remotelyRecording.isRemotelyRecorded = model.isRemotelyRecorded
+ model.conferenceParticipantDevices.readCurrentAndObserve { (_) in
+ self.grid.reloadData()
+ }
+ model.isConferencePaused.readCurrentAndObserve { (paused) in
+ self.pauseCallButtons.forEach {
+ $0.isSelected = paused == true
+ }
+ }
+ model.isRecording.readCurrentAndObserve { (selected) in
+ self.recordCallButtons.forEach {
+ $0.isSelected = selected == true
+ }
+ }
+ }
+ self.grid.reloadData()
+ }
+ }
+
+ init() {
+
+ let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
+ layout.minimumInteritemSpacing = 0
+ layout.minimumLineSpacing = 0
+ grid = UICollectionView(frame:.zero, collectionViewLayout: layout)
+
+ super.init(frame: .zero)
+
+ let headerView = UIStackView()
+ addSubview(headerView)
+
+ headerView.distribution = .equalSpacing
+ headerView.alignment = .bottom
+ headerView.spacing = record_pause_button_margin
+ headerView.axis = .vertical
+
+ let subjectDuration = UIView()
+
+ subjectDuration.addSubview(subjectLabel)
+ subjectLabel.alignParentLeft().done()
+
+ subjectDuration.addSubview(duration)
+ duration.alignParentLeft().alignUnder(view: subjectLabel,withMargin:duration_margin_top).done()
+
+ let upperSection = UIStackView()
+ upperSection.distribution = .equalSpacing
+ upperSection.alignment = .center
+ upperSection.spacing = record_pause_button_margin
+ upperSection.axis = .horizontal
+
+ upperSection.addArrangedSubview(subjectDuration)
+ subjectDuration.wrapContentY().done()
+
+ // Record (with video)
+ let recordCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_record, onClickAction: {
+ self.conferenceViewModel?.toggleRecording()
+ })
+
+ let recordPauseView = UIStackView()
+ recordPauseView.spacing = record_pause_button_margin
+ recordCallButtons.append(recordCall)
+ recordPauseView.addArrangedSubview(recordCall)
+
+ // Pause (with video)
+ let pauseCall = CallControlButton(width: record_pause_button_size, height: record_pause_button_size, imageInset:record_pause_button_inset, buttonTheme: VoipTheme.call_pause, onClickAction: {
+ self.conferenceViewModel?.togglePlayPause()
+
+ })
+ pauseCallButtons.append(pauseCall)
+ recordPauseView.addArrangedSubview(pauseCall)
+
+ upperSection.addArrangedSubview(recordPauseView)
+
+ headerView.addArrangedSubview(upperSection)
+ upperSection.matchParentSideBorders().alignParentTop(withMargin:ActiveCallView.top_displayname_margin_top).done()
+
+ headerView.addArrangedSubview(remotelyRecording)
+ remotelyRecording.matchParentSideBorders().alignUnder(view:upperSection, withMargin:ActiveCallView.remote_recording_margin_top).height(CGFloat(ActiveCallView.remote_recording_height)).done()
+
+ // CollectionView
+ grid.dataSource = self
+ grid.delegate = self
+ grid.register(VoipGridParticipantCell.self, forCellWithReuseIdentifier: "VoipGridParticipantCell")
+ grid.backgroundColor = VoipTheme.voipBackgroundColor.get()
+ grid.isScrollEnabled = false
+ addSubview(grid)
+ grid.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
+
+ headerView.matchParentSideBorders().alignParentTop().done()
+
+
+ // Full screen video togggle
+ grid.onClick {
+ ControlsViewModel.shared.toggleFullScreen()
+ }
+
+ ControlsViewModel.shared.fullScreenMode.observe { (fullScreen) in
+ if (self.isHidden) {
+ return
+ }
+ self.grid.removeConstraints().done()
+ if (fullScreen == true) {
+ self.grid.removeFromSuperview()
+ PhoneMainView.instance().mainViewController.view?.addSubview(self.grid)
+ self.grid.matchParentDimmensions().center().done()
+ self.grid.reloadData() // Cauz of the frames
+ } else {
+ self.grid.removeFromSuperview()
+ self.addSubview(self.grid)
+ self.grid.matchParentSideBorders().alignUnder(view:headerView,withMargin: ActiveCallView.center_view_margin_top).alignParentBottom().done()
+ self.grid.reloadData()
+ }
+ }
+ }
+
+
+ // UICollectionView related delegates
+
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+ return inter_cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, layout
+ collectionViewLayout: UICollectionViewLayout,
+ minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+ return inter_cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
+ return .zero
+ }
+ return participantsCount
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let cell:VoipGridParticipantCell = collectionView.dequeueReusableCell(withReuseIdentifier: "VoipGridParticipantCell", for: indexPath) as! VoipGridParticipantCell
+ guard let participantData = conferenceViewModel?.conferenceParticipantDevices.value?[indexPath.row] else {
+ return cell
+ }
+ cell.participantData = participantData
+ return cell
+ }
+
+ func collectionView(_ collectionView: UICollectionView,
+ layout collectionViewLayout: UICollectionViewLayout,
+ sizeForItemAt indexPath: IndexPath) -> CGSize {
+
+ guard let participantsCount = conferenceViewModel?.conferenceParticipantDevices.value?.count else {
+ return .zero
+ }
+
+ var cellSize : CGSize = .zero
+ let availableSize = collectionView.frame.size
+
+ if (participantsCount == 1) {
+ cellSize = availableSize
+ } else if (participantsCount == 2) {
+ cellSize = CGSize(width:availableSize.width, height:availableSize.height/2)
+ cellSize.height -= inter_cell/2
+ } else if (participantsCount == 3) {
+ cellSize = CGSize(width:availableSize.width, height:availableSize.height/3)
+ cellSize.height -= 2*inter_cell/3
+ } else if (participantsCount == 4) {
+ cellSize = CGSize(width:availableSize.width/2, height:availableSize.height/2)
+ cellSize.height -= inter_cell/2
+ cellSize.width -= inter_cell/2
+ } else if (participantsCount == 5) {
+ if (indexPath.row == 4) { // last (local) participant takes full width (under discussion)
+ cellSize = CGSize(width:availableSize.width, height:availableSize.height/3)
+ } else {
+ cellSize = CGSize(width:availableSize.width/2, height:availableSize.height/3)
+ cellSize.width -= inter_cell/2
+ }
+ cellSize.height -= 2*inter_cell/3
+ } else {
+ cellSize = CGSize(width:availableSize.width/2, height:availableSize.height/CGFloat((participantsCount/2)))
+ cellSize.height -= 2*inter_cell/3
+ cellSize.width -= inter_cell/2
+ }
+ return cellSize
+
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+
+
+}
diff --git a/Classes/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift b/Classes/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift
new file mode 100644
index 000000000..6339a0d15
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/Conference/VoipGridParticipantCell.swift
@@ -0,0 +1,129 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipGridParticipantCell: UICollectionViewCell {
+
+ // Layout Constants
+ let corner_radius = 20.0
+ static let avatar_size = 80.0
+ let switch_camera_button_margins = 8.0
+ let switch_camera_button_size = 30
+
+
+ let videoView = UIView()
+ let avatar = Avatar(diameter:VoipGridParticipantCell.avatar_size,color:VoipTheme.voipBackgroundColor, textStyle: VoipTheme.call_generated_avatar_medium)
+ let pause = UIImageView(image: UIImage(named: "voip_pause")?.tinted(with: .white))
+ let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
+ let displayName = StyledLabel(VoipTheme.conference_participant_name_font_grid)
+ let pauseLabel = StyledLabel(VoipTheme.conference_participant_name_font_grid,VoipTexts.conference_participant_paused)
+
+ var participantData: ConferenceParticipantDeviceData? = nil {
+ didSet {
+ if let data = participantData {
+ data.isInConference.readCurrentAndObserve { (isIn) in
+ self.updateBackground()
+ self.pause.isHidden = isIn == true
+ self.pauseLabel.isHidden = self.pause.isHidden
+ }
+ data.videoEnabled.readCurrentAndObserve { (videoEnabled) in
+ self.updateBackground()
+ if (videoEnabled == true) {
+ data.setVideoView(view: self.videoView)
+ self.avatar.isHidden = true
+ } else {
+ self.avatar.isHidden = false
+ }
+ self.switchCamera.isHidden = videoEnabled != true || !data.isSwitchCameraAvailable()
+ }
+ data.participantDevice.address.map {
+ avatar.fillFromAddress(address: $0)
+ if let displayName = $0.addressBookEnhancedDisplayName() {
+ self.displayName.text = displayName
+ }
+ }
+ data.activeSpeaker.readCurrentAndObserve { (active) in
+ if (active == true) {
+ self.layer.borderWidth = 2
+ } else {
+ self.layer.borderWidth = 0
+ }
+ }
+ }
+ }
+ }
+
+ func updateBackground() {
+ if let data = participantData {
+ if (data.isInConference.value != true) {
+ self.contentView.backgroundColor = VoipTheme.voip_conference_participant_paused_background
+ } else if (data.videoEnabled.value == true) {
+ self.contentView.backgroundColor = .black
+ } else {
+ self.contentView.backgroundColor = VoipTheme.voipParticipantBackgroundColor.get()
+
+ }
+ }
+ }
+
+
+ override init(frame:CGRect) {
+ super.init(frame:.zero)
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+ layer.borderColor = VoipTheme.primary_color.cgColor
+
+ contentView.addSubview(videoView)
+ videoView.matchParentDimmensions().done()
+
+ contentView.addSubview(avatar)
+ avatar.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
+
+ contentView.addSubview(pause)
+ pause.layer.cornerRadius = VoipGridParticipantCell.avatar_size/2
+ pause.clipsToBounds = true
+ pause.backgroundColor = VoipTheme.voip_gray
+ pause.size(w: VoipGridParticipantCell.avatar_size, h: VoipGridParticipantCell.avatar_size).center().done()
+
+ contentView.addSubview(switchCamera)
+ switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done()
+ switchCamera.contentMode = .scaleAspectFit
+
+ switchCamera.onClick {
+ Core.get().toggleCamera()
+ }
+
+ contentView.addSubview(displayName)
+ displayName.alignParentLeft(withMargin:ActiveCallView.bottom_displayname_margin_left).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
+
+ contentView.addSubview(pauseLabel)
+ pauseLabel.toRightOf(displayName).alignParentBottom(withMargin:ActiveCallView.bottom_displayname_margin_bottom).done()
+
+ contentView.matchParentDimmensions().done()
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/Classes/Voip/Views/Fragments/ControlsView.swift b/Classes/Voip/Views/Fragments/ControlsView.swift
new file mode 100644
index 000000000..58c285aa5
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/ControlsView.swift
@@ -0,0 +1,109 @@
+/*
+ * 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 ControlsView: UIStackView {
+
+ // Layout constants
+ static let controls_button_spacing = 5.0
+
+ init (showVideo:Bool) {
+ super.init(frame: .zero)
+ axis = .horizontal
+ distribution = .equalSpacing
+ alignment = .center
+ spacing = ControlsView.controls_button_spacing
+
+ // Mute
+ let mute = CallControlButton(buttonTheme: VoipTheme.call_mute, onClickAction: {
+ ControlsViewModel.shared.toggleMuteMicrophone()
+ })
+ addArrangedSubview(mute)
+ ControlsViewModel.shared.isMicrophoneMuted.readCurrentAndObserve { (muted) in
+ mute.isSelected = muted == true
+ }
+ ControlsViewModel.shared.isMuteMicrophoneEnabled.readCurrentAndObserve { (enabled) in
+ mute.isEnabled = enabled == true
+ }
+
+ // Speaker
+ let speaker = CallControlButton(buttonTheme: VoipTheme.call_speaker, onClickAction: {
+ ControlsViewModel.shared.toggleSpeaker()
+ })
+ addArrangedSubview(speaker)
+ ControlsViewModel.shared.isSpeakerSelected.readCurrentAndObserve { (selected) in
+ speaker.isSelected = selected == true
+ }
+
+ // Audio routes
+ let routes = CallControlButton(buttonTheme: VoipTheme.call_audio_route, onClickAction: {
+ ControlsViewModel.shared.toggleRoutesMenu()
+ })
+ addArrangedSubview(routes)
+ ControlsViewModel.shared.audioRoutesSelected.readCurrentAndObserve { (selected) in
+ routes.isSelected = selected == true
+ }
+
+ ControlsViewModel.shared.audioRoutesEnabled.readCurrentAndObserve { (routesEnabled) in
+ speaker.isHidden = routesEnabled == true
+ routes.isHidden = !speaker.isHidden
+ }
+
+ // Video
+ if (showVideo) {
+ let video = CallControlButton(buttonTheme: VoipTheme.call_video, onClickAction: {
+ if AVCaptureDevice.authorizationStatus(for: .video) == .authorized {
+ ControlsViewModel.shared.toggleVideo()
+ } else {
+ AVCaptureDevice.requestAccess(for: .video, completionHandler: { (granted: Bool) in
+ if granted {
+ ControlsViewModel.shared.toggleVideo()
+ } else {
+ VoipDialog(message:VoipTexts.camera_required_for_video).show()
+ }
+ })
+ }
+ })
+ addArrangedSubview(video)
+ video.showActivityIndicatorDataSource = ControlsViewModel.shared.isVideoUpdateInProgress
+ ControlsViewModel.shared.isVideoEnabled.readCurrentAndObserve { (selected) in
+ video.isSelected = selected == true
+ }
+ ControlsViewModel.shared.isVideoAvailable.readCurrentAndObserve { (available) in
+ video.isEnabled = available == true && ControlsViewModel.shared.isVideoUpdateInProgress.value != true
+ }
+ ControlsViewModel.shared.isVideoUpdateInProgress.readCurrentAndObserve { (updateInProgress) in
+ video.isEnabled = updateInProgress != true && ControlsViewModel.shared.isVideoAvailable.value == true
+ }
+
+ }
+
+ height(CallControlButton.default_size).done()
+
+ }
+
+ required init(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
+
+
diff --git a/Classes/Voip/Views/Fragments/DismissableView.swift b/Classes/Voip/Views/Fragments/DismissableView.swift
new file mode 100644
index 000000000..9bb1f7d9b
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/DismissableView.swift
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import UIKit
+import Foundation
+
+class DismissableView: UIView {
+
+ // Layout constants
+ let header_height = 60.0
+ let title_left_margin = 20
+ let dismiss_right_margin = 10
+ let dismiss_icon_inset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
+ let headerView = UIView()
+ let contentView = UIView()
+ var dismiss : CallControlButton? = nil
+
+ init(title:String) {
+ super.init(frame:.zero)
+
+ headerView.backgroundColor = VoipTheme.voipToolbarBackgroundColor.get()
+ self.addSubview(headerView)
+ headerView.matchParentSideBorders().alignParentTop().height(header_height).done()
+
+ dismiss = CallControlButton(imageInset:dismiss_icon_inset,buttonTheme: VoipTheme.voip_cancel, onClickAction: {
+ self.removeFromSuperview()
+ })
+ headerView.addSubview(dismiss!)
+ dismiss?.alignParentRight(withMargin: dismiss_right_margin).centerY().done()
+
+ let title = StyledLabel(VoipTheme.calls_list_header_font,title)
+ headerView.addSubview(title)
+ title.alignParentTop().alignParentLeft(withMargin: title_left_margin).centerY().done()
+
+ self.addSubview(contentView)
+ contentView.alignUnder(view: headerView).matchParentSideBorders().alignParentBottom().done()
+
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift b/Classes/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift
new file mode 100644
index 000000000..40a21da83
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/IncomingOuntgoingCommonView.swift
@@ -0,0 +1,98 @@
+/*
+ * 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 UIKit
+import Foundation
+import linphonesw
+
+@objc class IncomingOutgoingCommonView: UIViewController { // Shared between IncomingCallView and OutgoingCallVIew (all upper part except control buttons)
+
+ // Layout constants
+ static let spinner_size = 50
+ static let spinner_margin_top = 8.0
+ static let call_type_margin_top = 10.0
+ static let duration_margin_top = 10.0
+ static let display_name_height = 20.0
+ static let display_name_margin_top = 18.0
+ static let sip_address_height = 16.0
+ static let sip_address_margin_top = 6.0
+ static let answer_decline_inset = UIEdgeInsets(top: 2, left: 7, bottom: 2, right: 7)
+
+ let spinner = RotatingSpinner()
+ let duration = CallTimer(nil, VoipTheme.call_header_subtitle)
+ let avatar = Avatar(diameter: CGFloat(Avatar.diameter_for_call_views),color:VoipTheme.voipParticipantBackgroundColor, textStyle: VoipTheme.call_generated_avatar_large)
+ let displayName = StyledLabel(VoipTheme.call_header_title)
+ let sipAddress = StyledLabel(VoipTheme.call_header_subtitle)
+
+ var callData: CallData? = nil {
+ didSet {
+ duration.call = callData?.call
+ callData?.call.remoteAddress.map {
+ avatar.fillFromAddress(address: $0)
+ displayName.text = $0.addressBookEnhancedDisplayName()
+ sipAddress.text = $0.asStringUriOnly()
+ }
+ }
+ }
+
+ func viewDidLoad(forCallType:String) {
+ super.viewDidLoad()
+
+ view.backgroundColor = VoipTheme.voipBackgroundColor.get()
+
+ view.addSubview(spinner)
+ spinner.square(IncomingOutgoingCommonView.spinner_size).matchParentSideBorders().alignParentTop(withMargin:IncomingOutgoingCommonView.spinner_margin_top + UIDevice.notchHeight()).done()
+
+ let callType = StyledLabel(VoipTheme.call_header_title,forCallType)
+ view.addSubview(callType)
+ callType.matchParentSideBorders().alignUnder(view:spinner,withMargin:IncomingOutgoingCommonView.call_type_margin_top).done()
+
+ self.view.addSubview(duration)
+ duration.matchParentSideBorders().alignUnder(view:callType,withMargin:IncomingOutgoingCommonView.duration_margin_top).done()
+
+ // 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()
+
+
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ spinner.startRotation()
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ spinner.stopRotation()
+ super.viewWillDisappear(animated)
+ }
+
+ @objc func setCall(call:OpaquePointer) {
+ callData = CallData(call: Call.getSwiftObject(cObject: call))
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/LocalVideoView.swift b/Classes/Voip/Views/Fragments/LocalVideoView.swift
new file mode 100644
index 000000000..dba74002d
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/LocalVideoView.swift
@@ -0,0 +1,87 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class LocalVideoView: UIView {
+
+ //Layout constants
+ let corner_radius = 15.0
+ let aspect_ratio = 4.0/3.0
+ let switch_camera_button_margins = 8.0
+ let switch_camera_button_size = 30
+
+ var width : CGFloat
+
+ var dragZone : UIView? {
+ didSet {
+ let panGesture = UIPanGestureRecognizer(target: self, action: #selector(drag))
+ isUserInteractionEnabled = true
+ addGestureRecognizer(panGesture)
+ }
+ }
+
+ let switchCamera = UIImageView(image: UIImage(named:"voip_change_camera")?.tinted(with:.white))
+
+ var callData: CallData? = nil {
+ didSet {
+ callData?.isRemotelyRecorded.readCurrentAndObserve(onChange: { (isRemotelyRecording) in
+ self.isHidden = !(isRemotelyRecording == true)
+ })
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ width = 0.0
+ super.init(coder: coder)
+ }
+
+ init (width:CGFloat) {
+ self.width = width
+ super.init(frame: .zero)
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+
+ addSubview(switchCamera)
+ switchCamera.alignParentTop(withMargin: switch_camera_button_margins).alignParentRight(withMargin: switch_camera_button_margins).square(switch_camera_button_size).done()
+ switchCamera.contentMode = .scaleAspectFit
+
+ switchCamera.onClick {
+ Core.get().toggleCamera()
+ }
+ setSizeConstraint()
+ }
+
+ func setSizeConstraint() {
+ size(w: width, h: width*aspect_ratio).done()
+ }
+
+
+ @objc func drag(_ sender:UIPanGestureRecognizer){
+ dragZone?.bringSubviewToFront(self)
+ let translation = sender.translation(in: dragZone)
+ center = CGPoint(x: center.x + translation.x, y: center.y + translation.y)
+ sender.setTranslation(CGPoint.zero, in: dragZone)
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/NumpadView.swift b/Classes/Voip/Views/Fragments/NumpadView.swift
new file mode 100644
index 000000000..e949e82f8
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/NumpadView.swift
@@ -0,0 +1,123 @@
+/*
+ * 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 linphonesw
+
+@objc class NumpadView: UIView {
+
+ // Layout constants
+ let side_margins = 10.0
+ let margin_top = 100.0
+ let button_size = 70
+ let button_vertical_space = 17.0
+ let button_horizontal_space = 14.0
+ let digit_icon_inset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
+ let corner_radius = 20.0
+ let pad_height = 550
+ let side_padding = 50.0
+
+
+ init(superView:UIView, callData:CallData, onDismissAction : @escaping ()->Void) {
+ super.init(frame:.zero)
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+ superView.addSubview(self)
+ snp.makeConstraints { make in
+ make.left.equalToSuperview().offset(side_margins)
+ make.right.equalToSuperview().offset(-side_margins)
+ make.height.equalTo(pad_height)
+ make.bottom.equalToSuperview().offset(-side_margins)
+ }
+ callData.callState.observe { state in
+ if (state == Call.State.End) {
+ onDismissAction()
+ }
+ }
+
+ // Hide numpad button
+ let hide = CallControlButton(buttonTheme: VoipTheme.voip_cancel_light, onClickAction: {
+ onDismissAction()
+ })
+ addSubview(hide)
+ hide.alignParentRight(withMargin: side_margins).alignParentTop(withMargin: side_margins).done()
+
+ // DTMF History :
+
+ let eneteredDtmf = StyledLabel(VoipTheme.dtmf_label)
+ addSubview(eneteredDtmf)
+ _ = eneteredDtmf.matchParentSideBorders().alignUnder(view:hide,withMargin:side_margins)
+ callData.enteredDTMF.readCurrentAndObserve { (dtmfs) in
+ eneteredDtmf.text = dtmfs
+ }
+
+ // Digit buttons
+
+ let allRows = UIStackView()
+ allRows.axis = .vertical
+ allRows.distribution = .equalSpacing
+ allRows.alignment = .center
+ allRows.spacing = button_vertical_space
+ allRows.layoutMargins = UIEdgeInsets(top: 0, left: side_padding, bottom: 0, right: side_padding)
+ allRows.isLayoutMarginsRelativeArrangement = true
+ addSubview(allRows)
+ _ = allRows.matchParentSideBorders().alignUnder(view:eneteredDtmf,withMargin: side_margins)
+
+
+ for key in [["1","2","3"],["4","5","6"],["7","8","9"],["*","0","#"]] {
+ let newRow = addRow(allRows: allRows)
+ for subkey in key {
+ let digit = CallControlButton(width:button_size, height:button_size, imageInset: digit_icon_inset, buttonTheme: ButtonTheme(tintableStateIcons:[UIButton.State.normal.rawValue : TintableIcon(name: "voip_numpad_\(iconNameForDigit(digit: subkey))")],backgroundStateColors:VoipTheme.numpad_digit_background), onClickAction: {
+ callData.sendDTMF(dtmf: "\(subkey)")
+ })
+ newRow.addArrangedSubview(digit)
+ }
+ }
+ }
+
+ func iconNameForDigit(digit:String) -> String {
+ if (digit == "*") {
+ return "star"
+ }
+ if (digit == "#") {
+ return "hash"
+ }
+ return digit
+ }
+
+ func addRow(allRows:UIStackView) -> UIStackView {
+ let row = UIStackView()
+ row.axis = .horizontal
+ row.distribution = .equalSpacing
+ row.alignment = .center
+ row.spacing = button_vertical_space
+ row.isLayoutMarginsRelativeArrangement = true
+ allRows.addArrangedSubview(row)
+ return row
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+}
+
+
+
diff --git a/Classes/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift b/Classes/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift
new file mode 100644
index 000000000..b81357bb7
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/ParticipantsList/ParticipantsListView.swift
@@ -0,0 +1,97 @@
+/*
+ * 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 UIKit
+import Foundation
+import linphonesw
+
+@objc class ParticipantsListView: DismissableView, UITableViewDataSource {
+
+ // Layout constants
+
+
+ let participantsListTableView = UITableView()
+
+ var callsDataObserver : MutableLiveDataOnChangeClosure<[CallData]>? = nil
+
+ init() {
+ super.init(title: VoipTexts.call_action_participants_list)
+
+
+ let edit = CallControlButton(buttonTheme: VoipTheme.voip_edit, onClickAction: {
+ // Todo (not implemented in Android yet as of 22.11.21)
+ })
+ super.headerView.addSubview(edit)
+ edit.centerY().done()
+ super.dismiss?.toRightOf(edit,withLeftMargin: dismiss_right_margin).centerY().done()
+
+
+ // ParticipantsList
+ super.contentView.addSubview(participantsListTableView)
+ participantsListTableView.matchParentDimmensions().done()
+ participantsListTableView.dataSource = self
+ participantsListTableView.register(VoipParticipantCell.self, forCellReuseIdentifier: "VoipParticipantCell")
+ participantsListTableView.allowsSelection = false
+ if #available(iOS 15.0, *) {
+ participantsListTableView.allowsFocus = false
+ }
+ participantsListTableView.separatorStyle = .singleLine
+ participantsListTableView.separatorColor = .white
+
+
+ CallsViewModel.shared.callsData.readCurrentAndObserve{ (callsData) in
+ self.participantsListTableView.reloadData()
+ }
+
+ ConferenceViewModel.shared.isMeAdmin.readCurrentAndObserve { (meAdmin) in
+ edit.isHidden = meAdmin != true
+ }
+
+ }
+
+
+ // TableView datasource delegate
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ guard let participants = ConferenceViewModel.shared.conferenceParticipants.value else {
+ return 0
+ }
+ return participants.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let cell:VoipParticipantCell = tableView.dequeueReusableCell(withIdentifier: "VoipParticipantCell") as! VoipParticipantCell
+ guard let participantData = ConferenceViewModel.shared.conferenceParticipants.value?[indexPath.row] else {
+ return cell
+ }
+ cell.selectionStyle = .none
+ cell.participantData = participantData
+ cell.owningParticpantsListView = self
+ return cell
+ }
+
+ // View controller
+
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift b/Classes/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift
new file mode 100644
index 000000000..c57a4e7a0
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/ParticipantsList/VoipParticipantCell.swift
@@ -0,0 +1,113 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipParticipantCell: UITableViewCell {
+
+ // Layout Constants
+
+ let dismiss_icon_inset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
+ let dismiss_right_margin = 10
+ let check_box_size = 20.0
+ let cell_height = 80.0
+ let avatar_left_margin = 15.0
+ let texts_left_margin = 20.0
+
+
+ let avatar = Avatar(diameter:VoipCallCell.avatar_size,color:VoipTheme.primaryTextColor, textStyle: VoipTheme.call_generated_avatar_small)
+ let displayName = StyledLabel(VoipTheme.conference_participant_name_font)
+ let sipAddress = StyledLabel(VoipTheme.conference_participant_sip_uri_font)
+ let isAdminView = UIView()
+ var removePart : CallControlButton?
+
+
+ var owningParticpantsListView : ParticipantsListView? = nil
+
+ var participantData: ConferenceParticipantData? = nil {
+ didSet {
+ if let data = participantData {
+ avatar.fillFromAddress(address: data.participant.address!)
+ displayName.text = data.participant.address?.addressBookEnhancedDisplayName()
+ sipAddress.text = data.participant.address?.asStringUriOnly()
+ data.isAdmin.readCurrentAndObserve { (isAdmin) in self.isAdminView.isHidden = isAdmin != true
+
+ }
+ data.isMeAdmin.readCurrentAndObserve { (isMeAdmin) in
+ self.removePart!.isHidden = isMeAdmin != true
+ self.isAdminView.alpha = isMeAdmin == true ? 1.0 : 0.6
+ self.isAdminView.isUserInteractionEnabled = isMeAdmin == true
+ }
+ self.isAdminView.onClick {
+ data.conference.setParticipantAdminStatus(participant: data.participant, isAdmin: data.isAdmin.value != true)
+ self.owningParticpantsListView?.participantsListTableView.reloadData()
+ }
+ self.removePart?.onClick {
+ try?data.conference.removeParticipant(participant: data.participant)
+ self.owningParticpantsListView?.participantsListTableView.reloadData()
+ }
+ }
+ }
+ }
+
+
+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+ super.init(style: style, reuseIdentifier: reuseIdentifier)
+ contentView.height(cell_height).matchParentSideBorders().done()
+
+
+ contentView.addSubview(avatar)
+ avatar.size(w: VoipCallCell.avatar_size, h: VoipCallCell.avatar_size).centerY().alignParentLeft(withMargin: avatar_left_margin).done()
+
+ let nameAddress = UIView()
+ nameAddress.addSubview(displayName)
+ nameAddress.addSubview(sipAddress)
+ displayName.alignParentTop().done()
+ sipAddress.alignUnder(view: displayName).done()
+ contentView.addSubview(nameAddress)
+ nameAddress.toRightOf(avatar,withLeftMargin:texts_left_margin).wrapContentY().centerY().done()
+
+ removePart = CallControlButton(imageInset:dismiss_icon_inset,buttonTheme: VoipTheme.voip_cancel, onClickAction: {
+ self.removeFromSuperview()
+ })
+ contentView.addSubview(removePart!)
+ removePart!.alignParentRight(withMargin: dismiss_right_margin).centerY().done()
+
+ let isAdminLabel = StyledLabel(VoipTheme.conference_participant_admin_label,VoipTexts.chat_room_group_info_admin)
+ isAdminView.addSubview(isAdminLabel)
+ isAdminLabel.alignParentRight().centerY().done()
+
+ let isAdminCheck = UIImageView(image: UIImage(named:("check_unselected")))
+ isAdminView.addSubview(isAdminCheck)
+ isAdminCheck.size(w: check_box_size, h: check_box_size).toLeftOf(isAdminLabel).done()
+
+ contentView.addSubview(isAdminView)
+ isAdminView.height(check_box_size).toLeftOf(removePart!).centerY().done()
+
+
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+}
diff --git a/Classes/Voip/Views/Fragments/PausedCallOrConferenceView.swift b/Classes/Voip/Views/Fragments/PausedCallOrConferenceView.swift
new file mode 100644
index 000000000..45a1a9a98
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/PausedCallOrConferenceView.swift
@@ -0,0 +1,70 @@
+/*
+ * 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 UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class PausedCallOrConferenceView: UIView {
+
+ // Layout constants
+ let icon_size = 200
+ let icon_padding = 80.0
+ let title_margin_top = 20
+
+ var icon : UIImageView? = nil
+ let title = StyledLabel(VoipTheme.call_or_conference_title)
+ let subtitle = StyledLabel(VoipTheme.call_or_conference_subtitle)
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init (iconName:String, titleText:String, subTitleText:String? = nil) {
+ super.init(frame: .zero)
+
+ backgroundColor = VoipTheme.voip_translucent_popup_background
+
+ let centeredView = UIView()
+ icon = UIImageView(image: UIImage(named:iconName)?.withPadding(padding: icon_padding))
+ icon!.backgroundColor = VoipTheme.primary_color
+ icon!.layer.cornerRadius = CGFloat(icon_size/2)
+ icon!.clipsToBounds = true
+ icon!.contentMode = .scaleAspectFit
+ centeredView.addSubview(icon!)
+ icon!.square(icon_size).centerX().done()
+
+ title.numberOfLines = 0
+ centeredView.addSubview(title)
+ title.alignUnder(view:icon!, withMargin:title_margin_top).matchParentSideBorders().done()
+ title.text = titleText
+
+ subtitle.numberOfLines = 0
+ centeredView.addSubview(subtitle)
+ subtitle.alignUnder(view: title).matchParentSideBorders().done()
+ subtitle.text = subTitleText
+
+ self.addSubview(centeredView)
+ centeredView.center().matchParentSideBorders().wrapContentY().done()
+
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/RemotelyRecording.swift b/Classes/Voip/Views/Fragments/RemotelyRecording.swift
new file mode 100644
index 000000000..a3e957865
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/RemotelyRecording.swift
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class RemotelyRecordingView: UIView {
+
+ let label = StyledLabel(VoipTheme.call_remote_recording)
+ let icon = UIImageView(image: UIImage(named:"voip_remote_recording"))
+
+ var isRemotelyRecorded: MutableLiveData? = nil {
+ didSet {
+ isRemotelyRecorded?.readCurrentAndObserve(onChange: { (isRemotelyRecording) in
+ self.isHidden = !(isRemotelyRecording == true)
+ })
+ }
+ }
+
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init (height:Int, text:String) {
+ super.init(frame: .zero)
+ backgroundColor = VoipTheme.dark_grey_color
+ layer.cornerRadius = CGFloat(height/2)
+ clipsToBounds = true
+
+ addSubview(label)
+ label.center().height(CGFloat(height)).done()
+ label.text = text
+
+ addSubview(icon)
+ icon.square(height).toLeftOf(label).done()
+
+ isHidden = true
+
+ }
+
+}
diff --git a/Classes/Voip/Views/Fragments/VoipExtraButtonsView.swift b/Classes/Voip/Views/Fragments/VoipExtraButtonsView.swift
new file mode 100644
index 000000000..2a7deca61
--- /dev/null
+++ b/Classes/Voip/Views/Fragments/VoipExtraButtonsView.swift
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+
+import UIKit
+import Foundation
+import SnapKit
+import linphonesw
+
+class VoipExtraButtonsView: UIStackView {
+
+ //Layout constants
+ let height = 200.0
+ let corner_radius = 20.0
+
+ required init(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ init () {
+ super.init(frame: .zero)
+
+ axis = .vertical
+ distribution = .fillEqually
+ alignment = .center
+
+ layer.cornerRadius = corner_radius
+ clipsToBounds = true
+
+ backgroundColor = VoipTheme.voipExtraButtonsBackgroundColor.get()
+ height(height).done()
+
+ let row1 = UIStackView()
+ row1.axis = .horizontal
+ row1.distribution = .fillEqually
+ row1.alignment = .center
+
+
+ // First row
+ let numpad = VoipExtraButton(text: VoipTexts.call_action_numpad, buttonTheme: VoipTheme.call_action("voip_call_numpad"),onClickAction: {
+ ControlsViewModel.shared.numpadVisible.notifyAllObservers(with: true)
+ })
+ row1.addArrangedSubview(numpad)
+
+ let stats = VoipExtraButton(text: VoipTexts.call_action_statistics, buttonTheme: VoipTheme.call_action("voip_call_stats"),onClickAction: {
+ ControlsViewModel.shared.callStatsVisible.notifyAllObservers(with: true)
+ })
+ row1.addArrangedSubview(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)
+
+ addArrangedSubview(row1)
+ row1.matchParentSideBorders().done()
+
+ // Second row
+
+ let row2 = UIStackView()
+ row2.axis = .horizontal
+ row2.distribution = .fillEqually
+ row2.alignment = .center
+
+ let transfer = VoipExtraButton(text: VoipTexts.call_action_transfer_call, buttonTheme: VoipTheme.call_action("voip_call_forward"),onClickAction: {
+ let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
+ view.setAddress("")
+ CallManager.instance().nextCallIsTransfer = true
+ PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
+ })
+ row2.addArrangedSubview(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)
+
+
+ let addcall = VoipExtraButton(text: VoipTexts.call_action_add_call, buttonTheme: VoipTheme.call_action("voip_call_add"),onClickAction: {
+ let view: DialerView = self.VIEW(DialerView.compositeViewDescription());
+ view.setAddress("")
+ CallManager.instance().nextCallIsTransfer = false
+ PhoneMainView.instance().changeCurrentView(view.compositeViewDescription())
+ })
+ row2.addArrangedSubview(addcall)
+
+
+ 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)
+
+ 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)
+
+ addArrangedSubview(row2)
+ row2.matchParentSideBorders().done()
+
+ ConferenceViewModel.shared.isInConference.readCurrentAndObserve { (isIn) in
+ participants.isHidden = isIn != true
+ layoutselect.isHidden = isIn != true
+ transfer.isHidden = isIn == true
+ addcall.isHidden = isIn == true
+ }
+
+ }
+
+ func refresh() {
+ CallsViewModel.shared.currentCallUnreadChatMessageCount.notifyValue()
+ CallsViewModel.shared.inactiveCallsCount.notifyValue()
+ }
+
+
+
+}
diff --git a/Classes/LinphoneUI/UIHangUpButton.h b/Classes/Voip/Views/SharedLayoutConstants.swift
similarity index 79%
rename from Classes/LinphoneUI/UIHangUpButton.h
rename to Classes/Voip/Views/SharedLayoutConstants.swift
index 784369828..163d8ba07 100644
--- a/Classes/LinphoneUI/UIHangUpButton.h
+++ b/Classes/Voip/Views/SharedLayoutConstants.swift
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
- * This file is part of linphone-iphone
+ * This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,13 +17,11 @@
* along with this program. If not, see .
*/
-#import
-#import "UIIconButton.h"
+import Foundation
+
+class SharedLayoutConstants {
+ static let buttons_bottom_margin = 15
+ static let margin_call_view_side_controls_buttons = 12
-@interface UIHangUpButton : UIIconButton {
}
-
-- (void)update;
-
-@end
diff --git a/Classes/Voip/VoipDialog.swift b/Classes/Voip/VoipDialog.swift
new file mode 100644
index 000000000..8488e8764
--- /dev/null
+++ b/Classes/Voip/VoipDialog.swift
@@ -0,0 +1,107 @@
+/*
+ * 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.big_button)
+ 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() {
+ rootVC()?.view.addSubview(self)
+ matchParentDimmensions().done()
+ }
+
+ private func rootVC() -> UIViewController? {
+ return UIApplication.getTopMostViewController()
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+}
+
+struct ButtonAttributes {
+ let text:String
+ let action: (()->Void)
+ let isDestructive: Bool
+}
diff --git a/Classes/Voip/Widgets/Avatar.swift b/Classes/Voip/Widgets/Avatar.swift
new file mode 100644
index 000000000..8e4084fa5
--- /dev/null
+++ b/Classes/Voip/Widgets/Avatar.swift
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+import linphonesw
+
+class Avatar : UIImageView {
+
+ static let diameter_for_call_views = 191
+
+ required init?(coder: NSCoder) {
+ initialsLabel = StyledLabel(VoipTheme.call_generated_avatar_large)
+ super.init(coder: coder)
+ }
+
+ let initialsLabel: StyledLabel
+
+ init (diameter: CGFloat, color:LightDarkColor,textStyle:TextStyle) {
+ initialsLabel = StyledLabel(textStyle)
+ super.init(frame: .zero)
+ layer.cornerRadius = diameter/2.0
+ clipsToBounds = true
+ self.backgroundColor = color.get()
+ addSubview(initialsLabel)
+ _ = initialsLabel.matchParentSideBorders().matchParentHeight()
+ }
+
+
+
+ func fillFromAddress(address:Address) {
+ if let image = address.contact()?.avatar() {
+ self.image = image
+ initialsLabel.isHidden = true
+ } else {
+ self.image = nil
+ initialsLabel.text = address.initials()
+ initialsLabel.isHidden = false
+ }
+ }
+
+
+
+}
+
diff --git a/Classes/Voip/Widgets/BouncingCounter.swift b/Classes/Voip/Widgets/BouncingCounter.swift
new file mode 100644
index 000000000..a07f5124b
--- /dev/null
+++ b/Classes/Voip/Widgets/BouncingCounter.swift
@@ -0,0 +1,75 @@
+/*
+ * 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
+import SwiftUI
+
+class BouncingCounter : UIBouncingView {
+
+ // Layout constants
+ let size = 20.0
+ let center_offset = 20
+
+ let owningButton : UIButton
+ let label : StyledLabel
+
+ var dataSource : MutableLiveData? {
+ didSet {
+ if let dataSource = dataSource {
+ self.size(w:self.size,h:self.size).matchCenterXOf(view: self.owningButton, withDx: self.center_offset).matchCenterYOf(view: self.owningButton, withDy: -self.center_offset).done()
+ dataSource.readCurrentAndObserve { (value) in
+ if (value! > 0) {
+ self.label.text = value! < 100 ? String(value!) : "99+"
+ self.isHidden = true // to force legacy startAnimating to unhide and animate
+ self.startAnimating(true)
+ } else {
+ self.isHidden = false // to force legacy startAnimating to hide and animate
+ self.stopAnimating(true)
+ }
+ }
+ } else {
+ self.isHidden = false
+ self.stopAnimating(true)
+ }
+ }
+ }
+
+
+ init (inButton:UIButton) {
+ owningButton = inButton
+ label = StyledLabel(VoipTheme.unread_count_font)
+ super.init(frame:.zero)
+ addSubview(label)
+ label.matchParentDimmensions().done()
+ backgroundColor = VoipTheme.primary_color
+ layer.masksToBounds = true
+ layer.cornerRadius = size/2
+ self.isHidden = true
+ }
+
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+
+
+}
diff --git a/Classes/Voip/Widgets/ButtonWithStateBackgrounds.swift b/Classes/Voip/Widgets/ButtonWithStateBackgrounds.swift
new file mode 100644
index 000000000..fa437dc8f
--- /dev/null
+++ b/Classes/Voip/Widgets/ButtonWithStateBackgrounds.swift
@@ -0,0 +1,49 @@
+/*
+ * 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
+import SwiftUI
+
+class ButtonWithStateBackgrounds : UIButton {
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init (backgroundStateColors: [UInt: LightDarkColor]) {
+ super.init(frame: .zero)
+ backgroundStateColors.keys.forEach { (stateRawValue) in
+ setBackgroundColor(color: backgroundStateColors[stateRawValue]!.get(), forState: UIButton.State(rawValue: stateRawValue))
+ }
+ }
+
+ func setBackgroundColor(color: UIColor, forState: UIControl.State) {
+ UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
+ UIGraphicsGetCurrentContext()!.setFillColor(color.cgColor)
+ UIGraphicsGetCurrentContext()!.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
+ let colorImage = UIGraphicsGetImageFromCurrentImageContext()
+ UIGraphicsEndImageContext()
+ self.setBackgroundImage(colorImage, for: forState)
+ }
+
+
+
+}
diff --git a/Classes/Voip/Widgets/CallControlButton.swift b/Classes/Voip/Widgets/CallControlButton.swift
new file mode 100644
index 000000000..03a8a4835
--- /dev/null
+++ b/Classes/Voip/Widgets/CallControlButton.swift
@@ -0,0 +1,92 @@
+/*
+ * 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
+import SwiftUI
+
+class CallControlButton : ButtonWithStateBackgrounds {
+
+ // Layout constants
+ static let default_size = 50
+ static let hungup_width = 65
+
+ var showActivityIndicatorDataSource : MutableLiveData? = nil {
+ didSet {
+ if let showActivityIndicatorDataSource = self.showActivityIndicatorDataSource {
+ let spinner = UIActivityIndicatorView(style: .white)
+ spinner.color = VoipTheme.primary_color
+ self.addSubview(spinner)
+ spinner.matchParentDimmensions().center().done()
+
+ showActivityIndicatorDataSource.readCurrentAndObserve { (show) in
+ if (show == true) {
+ spinner.startAnimating()
+ spinner.isHidden = false
+ self.isEnabled = false
+ } else {
+ spinner.stopAnimating()
+ spinner.isHidden = true
+ self.isEnabled = true
+ }
+ }
+ }
+ }
+ }
+
+ var onClickAction : (()->Void)? = nil
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init (width:Int = CallControlButton.default_size, height:Int = CallControlButton.default_size, imageInset:UIEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2), buttonTheme: ButtonTheme, onClickAction : (()->Void)? = nil ) {
+ super.init(backgroundStateColors: buttonTheme.backgroundStateColors)
+
+ layer.cornerRadius = CGFloat(height/2)
+ clipsToBounds = true
+ contentMode = .scaleAspectFit
+
+ applyTintedIcons(tintedIcons: buttonTheme.tintableStateIcons)
+
+ imageView?.contentMode = .scaleAspectFit
+
+ imageEdgeInsets = imageInset
+
+ size(w: CGFloat(width), h: CGFloat(height)).done()
+
+ self.onClickAction = onClickAction
+ onClick {
+ self.onClickAction?()
+ }
+
+ }
+
+ func applyTintedIcons(tintedIcons: [UInt: TintableIcon]) {
+ tintedIcons.keys.forEach { (stateRawValue) in
+ let tintedIcon = tintedIcons[stateRawValue]!
+ UIImage(named:tintedIcon.name).map {
+ setImage($0.tinted(with: tintedIcon.tintColor?.get()),for: UIButton.State(rawValue: stateRawValue))
+ }
+ }
+ }
+
+
+}
diff --git a/Classes/LinphoneUI/UIPauseButton.h b/Classes/Voip/Widgets/RotatingSpinner.swift
similarity index 50%
rename from Classes/LinphoneUI/UIPauseButton.h
rename to Classes/Voip/Widgets/RotatingSpinner.swift
index 0146c3174..a03f12671 100644
--- a/Classes/LinphoneUI/UIPauseButton.h
+++ b/Classes/Voip/Widgets/RotatingSpinner.swift
@@ -1,7 +1,7 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
- * This file is part of linphone-iphone
+ * This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,22 +17,32 @@
* along with this program. If not, see .
*/
-#import "UIToggleButton.h"
+import Foundation
-#include "linphone/linphonecore.h"
-
-typedef enum _UIPauseButtonType {
- UIPauseButtonType_CurrentCall,
- UIPauseButtonType_Call,
- UIPauseButtonType_Conference
-} UIPauseButtonType;
-
-@interface UIPauseButton : UIToggleButton {
- @private
- UIPauseButtonType type;
- LinphoneCall* call;
+class RotatingSpinner : UIImageView {
+
+ init () {
+ super.init(frame: .zero)
+ self.image = UIImage(named: "voip_spinner")
+ self.tint(UIColor.white)
+ self.contentMode = .scaleAspectFit
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ func startRotation() {
+ let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
+ rotation.toValue = NSNumber(value: Double.pi * 2)
+ rotation.duration = 2.2
+ rotation.isCumulative = true
+ rotation.repeatCount = Float.greatestFiniteMagnitude
+ self.layer.add(rotation, forKey: "rotationAnimation")
+ }
+
+ func stopRotation() {
+ self.layer.removeAllAnimations()
+ }
}
-- (void)setType:(UIPauseButtonType) type call:(LinphoneCall*)call;
-
-@end
diff --git a/Classes/Voip/Widgets/StyledLabel.swift b/Classes/Voip/Widgets/StyledLabel.swift
new file mode 100644
index 000000000..29c15da3e
--- /dev/null
+++ b/Classes/Voip/Widgets/StyledLabel.swift
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010-2020 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import Foundation
+
+class StyledLabel: UILabel {
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init (_ style:TextStyle, _ text:String? = nil) {
+ super.init(frame: .zero)
+ self.text = text
+ applyStyle(style)
+ }
+
+}
diff --git a/Classes/Voip/Widgets/UICallTimer.swift b/Classes/Voip/Widgets/UICallTimer.swift
new file mode 100644
index 000000000..1eaf13ef9
--- /dev/null
+++ b/Classes/Voip/Widgets/UICallTimer.swift
@@ -0,0 +1,70 @@
+/*
+ * 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 linphonesw
+
+class CallTimer : StyledLabel {
+
+ let min_width = 50.0
+
+ let formatter = DateComponentsFormatter()
+ var call:Call? = nil {
+ didSet {
+ if (self.call != nil) {
+ self.format()
+ }
+ }
+ }
+
+ var conference:Conference? = nil {
+ didSet {
+ if (self.call != nil) {
+ self.format()
+ }
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init (_ text:String?, _ style:TextStyle, _ call:Call? = nil) {
+ super.init(style,text)
+ self.call = call
+ formatter.unitsStyle = .positional
+ formatter.allowedUnits = [.minute, .second ]
+ formatter.zeroFormattingBehavior = [ .pad ]
+ Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
+ self.format()
+ }
+ 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
+ }
+ }
+
+}
diff --git a/Classes/Voip/Widgets/VoipExtraButton.swift b/Classes/Voip/Widgets/VoipExtraButton.swift
new file mode 100644
index 000000000..67fce96c1
--- /dev/null
+++ b/Classes/Voip/Widgets/VoipExtraButton.swift
@@ -0,0 +1,75 @@
+/*
+ * 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
+import SwiftUI
+
+class VoipExtraButton : UIButton {
+
+ // Layout constants
+ let width = 60.0
+ let image_size = 50.0
+ let bouncing_label_size = 17.0
+
+ var boucingCounter : BouncingCounter? = nil
+
+
+ required init?(coder: NSCoder) {
+ super.init(coder: coder)
+ }
+
+ init ( text:String, buttonTheme: ButtonTheme, withbBoucinCounterDataSource:MutableLiveData? = nil, onClickAction : @escaping ()->Void ) {
+ super.init(frame: .zero)
+
+
+ contentMode = .scaleToFill
+
+ buttonTheme.tintableStateIcons.keys.forEach { (stateRawValue) in
+ let tintedIcon = buttonTheme.tintableStateIcons[stateRawValue]!
+ UIImage(named:tintedIcon.name).map {
+ setImage($0.tinted(with: tintedIcon.tintColor?.get()),for: UIButton.State(rawValue: stateRawValue))
+ }
+ setTitleColor(tintedIcon.tintColor?.get(), for: UIButton.State(rawValue: stateRawValue))
+ }
+ imageView?.contentMode = .scaleAspectFit
+ imageView?.size(w: image_size,h: image_size).centerX().alignParentTop().done()
+ titleLabel?.alignUnder(view: imageView!).centerX().done()
+
+
+ size(w: width,h: image_size).done()
+ setTitle(text, for: .normal)
+ applyTitleStyle(VoipTheme.voip_extra_button)
+
+ onClick {
+ ControlsViewModel.shared.hideExtraButtons.value = true
+ onClickAction()
+ }
+
+ if (withbBoucinCounterDataSource != nil) {
+ boucingCounter = BouncingCounter(inButton:self)
+ addSubview(boucingCounter!)
+ boucingCounter?.dataSource = withbBoucinCounterDataSource
+ }
+
+ }
+
+
+}
diff --git a/Classes/ar.lproj/CallIncomingView.strings b/Classes/ar.lproj/CallIncomingView.strings
deleted file mode 100644
index c5adc5123..000000000
Binary files a/Classes/ar.lproj/CallIncomingView.strings and /dev/null differ
diff --git a/Classes/ar.lproj/CallView.strings b/Classes/ar.lproj/CallView.strings
deleted file mode 100644
index 6544e6570..000000000
Binary files a/Classes/ar.lproj/CallView.strings and /dev/null differ
diff --git a/Classes/fr.lproj/CallIncomingView.strings b/Classes/fr.lproj/CallIncomingView.strings
deleted file mode 100644
index b59db541e..000000000
Binary files a/Classes/fr.lproj/CallIncomingView.strings and /dev/null differ
diff --git a/Classes/fr.lproj/CallOutgoingView.strings b/Classes/fr.lproj/CallOutgoingView.strings
deleted file mode 100644
index f507fda06..000000000
Binary files a/Classes/fr.lproj/CallOutgoingView.strings and /dev/null differ
diff --git a/Classes/fr.lproj/CallView.strings b/Classes/fr.lproj/CallView.strings
deleted file mode 100644
index 8303fcfed..000000000
Binary files a/Classes/fr.lproj/CallView.strings and /dev/null differ
diff --git a/Classes/fr.lproj/CallView~ipad.strings b/Classes/fr.lproj/CallView~ipad.strings
deleted file mode 100644
index b4c093b13..000000000
Binary files a/Classes/fr.lproj/CallView~ipad.strings and /dev/null differ
diff --git a/Classes/hu.lproj/CallIncomingView.strings b/Classes/hu.lproj/CallIncomingView.strings
deleted file mode 100644
index de84a65e8..000000000
Binary files a/Classes/hu.lproj/CallIncomingView.strings and /dev/null differ
diff --git a/Classes/hu.lproj/CallOutgoingView.strings b/Classes/hu.lproj/CallOutgoingView.strings
deleted file mode 100644
index 912463c42..000000000
Binary files a/Classes/hu.lproj/CallOutgoingView.strings and /dev/null differ
diff --git a/Classes/hu.lproj/CallView.strings b/Classes/hu.lproj/CallView.strings
deleted file mode 100644
index 95effba3d..000000000
Binary files a/Classes/hu.lproj/CallView.strings and /dev/null differ
diff --git a/Classes/hu.lproj/CallView~ipad.strings b/Classes/hu.lproj/CallView~ipad.strings
deleted file mode 100644
index 3bd40d4d4..000000000
Binary files a/Classes/hu.lproj/CallView~ipad.strings and /dev/null differ
diff --git a/Classes/linphone-Bridging-Header.h b/Classes/linphone-Bridging-Header.h
index a520acc8e..c09603899 100644
--- a/Classes/linphone-Bridging-Header.h
+++ b/Classes/linphone-Bridging-Header.h
@@ -7,5 +7,8 @@
#import
#import "FastAddressBook.h"
#import "Log.h"
-#import "AudioHelper.h"
-
+#import "LinphoneUI/UICompositeView.h"
+#import "Contact.h"
+#import "StatusBarView.h"
+#import "LinphoneUI/UIBouncingView.h"
+#import "PhoneMainView.h"
diff --git a/Classes/ru.lproj/CallIncomingView.strings b/Classes/ru.lproj/CallIncomingView.strings
deleted file mode 100644
index f433ea682..000000000
Binary files a/Classes/ru.lproj/CallIncomingView.strings and /dev/null differ
diff --git a/Classes/ru.lproj/CallView.strings b/Classes/ru.lproj/CallView.strings
deleted file mode 100644
index c3217f4ee..000000000
Binary files a/Classes/ru.lproj/CallView.strings and /dev/null differ
diff --git a/Podfile b/Podfile
index d6a747f18..4bdf972fb 100644
--- a/Podfile
+++ b/Podfile
@@ -26,6 +26,7 @@ target 'linphone' do
# Pods for linphone
pod 'SVProgressHUD'
+ pod 'SnapKit'
all_pods
end
diff --git a/Resources/fonts/Roboto-Bold.ttf b/Resources/fonts/Roboto-Bold.ttf
new file mode 100644
index 000000000..8d6cf0551
Binary files /dev/null and b/Resources/fonts/Roboto-Bold.ttf differ
diff --git a/Resources/fonts/Roboto-Italic.ttf b/Resources/fonts/Roboto-Italic.ttf
new file mode 100644
index 000000000..737244bc0
Binary files /dev/null and b/Resources/fonts/Roboto-Italic.ttf differ
diff --git a/Resources/fonts/Roboto-Regular.ttf b/Resources/fonts/Roboto-Regular.ttf
new file mode 100644
index 000000000..3a4973c78
Binary files /dev/null and b/Resources/fonts/Roboto-Regular.ttf differ
diff --git a/Resources/images/voip_audio_routes.png b/Resources/images/voip_audio_routes.png
new file mode 100644
index 000000000..fae4e84e3
Binary files /dev/null and b/Resources/images/voip_audio_routes.png differ
diff --git a/Resources/images/voip_bluetooth.png b/Resources/images/voip_bluetooth.png
new file mode 100644
index 000000000..3010fb418
Binary files /dev/null and b/Resources/images/voip_bluetooth.png differ
diff --git a/Resources/images/voip_call.png b/Resources/images/voip_call.png
new file mode 100644
index 000000000..1a95c052e
Binary files /dev/null and b/Resources/images/voip_call.png differ
diff --git a/Resources/images/voip_call_add.png b/Resources/images/voip_call_add.png
new file mode 100644
index 000000000..8237f3305
Binary files /dev/null and b/Resources/images/voip_call_add.png differ
diff --git a/Resources/images/voip_call_chat.png b/Resources/images/voip_call_chat.png
new file mode 100644
index 000000000..4ff01de6f
Binary files /dev/null and b/Resources/images/voip_call_chat.png differ
diff --git a/Resources/images/voip_call_forward.png b/Resources/images/voip_call_forward.png
new file mode 100644
index 000000000..cc61de482
Binary files /dev/null and b/Resources/images/voip_call_forward.png differ
diff --git a/Resources/images/voip_call_header_active.png b/Resources/images/voip_call_header_active.png
new file mode 100644
index 000000000..3ef0106bc
Binary files /dev/null and b/Resources/images/voip_call_header_active.png differ
diff --git a/Resources/images/voip_call_header_incoming.png b/Resources/images/voip_call_header_incoming.png
new file mode 100644
index 000000000..7a8d458ad
Binary files /dev/null and b/Resources/images/voip_call_header_incoming.png differ
diff --git a/Resources/images/voip_call_header_outgoing.png b/Resources/images/voip_call_header_outgoing.png
new file mode 100644
index 000000000..474abe754
Binary files /dev/null and b/Resources/images/voip_call_header_outgoing.png differ
diff --git a/Resources/images/voip_call_header_paused.png b/Resources/images/voip_call_header_paused.png
new file mode 100644
index 000000000..fdcfaf5d2
Binary files /dev/null and b/Resources/images/voip_call_header_paused.png differ
diff --git a/Resources/images/voip_call_list_menu.png b/Resources/images/voip_call_list_menu.png
new file mode 100644
index 000000000..fdd82bea6
Binary files /dev/null and b/Resources/images/voip_call_list_menu.png differ
diff --git a/Resources/images/voip_call_more.png b/Resources/images/voip_call_more.png
new file mode 100644
index 000000000..73bb13b36
Binary files /dev/null and b/Resources/images/voip_call_more.png differ
diff --git a/Resources/images/voip_call_numpad.png b/Resources/images/voip_call_numpad.png
new file mode 100644
index 000000000..a0fd33835
Binary files /dev/null and b/Resources/images/voip_call_numpad.png differ
diff --git a/Resources/images/voip_call_participants.png b/Resources/images/voip_call_participants.png
new file mode 100644
index 000000000..b2b766b14
Binary files /dev/null and b/Resources/images/voip_call_participants.png differ
diff --git a/Resources/images/voip_call_record.png b/Resources/images/voip_call_record.png
new file mode 100644
index 000000000..9c392dc47
Binary files /dev/null and b/Resources/images/voip_call_record.png differ
diff --git a/Resources/images/voip_call_stats.png b/Resources/images/voip_call_stats.png
new file mode 100644
index 000000000..3dd39d43b
Binary files /dev/null and b/Resources/images/voip_call_stats.png differ
diff --git a/Resources/images/voip_calls_list.png b/Resources/images/voip_calls_list.png
new file mode 100644
index 000000000..a3d69b84e
Binary files /dev/null and b/Resources/images/voip_calls_list.png differ
diff --git a/Resources/images/voip_camera_off.png b/Resources/images/voip_camera_off.png
new file mode 100644
index 000000000..dd41c338b
Binary files /dev/null and b/Resources/images/voip_camera_off.png differ
diff --git a/Resources/images/voip_camera_on.png b/Resources/images/voip_camera_on.png
new file mode 100644
index 000000000..7109c577d
Binary files /dev/null and b/Resources/images/voip_camera_on.png differ
diff --git a/Resources/images/voip_cancel.png b/Resources/images/voip_cancel.png
new file mode 100644
index 000000000..493b35e79
Binary files /dev/null and b/Resources/images/voip_cancel.png differ
diff --git a/Resources/images/voip_change_camera.png b/Resources/images/voip_change_camera.png
new file mode 100644
index 000000000..d6dc15cb8
Binary files /dev/null and b/Resources/images/voip_change_camera.png differ
diff --git a/Resources/images/voip_chat_rooms_list.png b/Resources/images/voip_chat_rooms_list.png
new file mode 100644
index 000000000..edf722c7e
Binary files /dev/null and b/Resources/images/voip_chat_rooms_list.png differ
diff --git a/Resources/images/voip_conference_active_speaker.png b/Resources/images/voip_conference_active_speaker.png
new file mode 100644
index 000000000..18f21106a
Binary files /dev/null and b/Resources/images/voip_conference_active_speaker.png differ
diff --git a/Resources/images/voip_conference_mosaic.png b/Resources/images/voip_conference_mosaic.png
new file mode 100644
index 000000000..8fa0137b7
Binary files /dev/null and b/Resources/images/voip_conference_mosaic.png differ
diff --git a/Resources/images/voip_conference_new.png b/Resources/images/voip_conference_new.png
new file mode 100644
index 000000000..8985782ae
Binary files /dev/null and b/Resources/images/voip_conference_new.png differ
diff --git a/Resources/images/voip_conference_paused_big.png b/Resources/images/voip_conference_paused_big.png
new file mode 100644
index 000000000..745f17220
Binary files /dev/null and b/Resources/images/voip_conference_paused_big.png differ
diff --git a/Resources/images/voip_conference_play_big.png b/Resources/images/voip_conference_play_big.png
new file mode 100644
index 000000000..303d05faa
Binary files /dev/null and b/Resources/images/voip_conference_play_big.png differ
diff --git a/Resources/images/voip_copy.png b/Resources/images/voip_copy.png
new file mode 100644
index 000000000..43639693e
Binary files /dev/null and b/Resources/images/voip_copy.png differ
diff --git a/Resources/images/voip_delete.png b/Resources/images/voip_delete.png
new file mode 100644
index 000000000..3022d156d
Binary files /dev/null and b/Resources/images/voip_delete.png differ
diff --git a/Resources/images/voip_dropdown.png b/Resources/images/voip_dropdown.png
new file mode 100644
index 000000000..d9fccac91
Binary files /dev/null and b/Resources/images/voip_dropdown.png differ
diff --git a/Resources/images/voip_earpiece.png b/Resources/images/voip_earpiece.png
new file mode 100644
index 000000000..e95a30801
Binary files /dev/null and b/Resources/images/voip_earpiece.png differ
diff --git a/Resources/images/voip_edit.png b/Resources/images/voip_edit.png
new file mode 100644
index 000000000..c24930212
Binary files /dev/null and b/Resources/images/voip_edit.png differ
diff --git a/Resources/images/voip_export.png b/Resources/images/voip_export.png
new file mode 100644
index 000000000..3fdfa078a
Binary files /dev/null and b/Resources/images/voip_export.png differ
diff --git a/Resources/images/voip_hangup.png b/Resources/images/voip_hangup.png
new file mode 100644
index 000000000..a2ceab5d8
Binary files /dev/null and b/Resources/images/voip_hangup.png differ
diff --git a/Resources/images/voip_info.png b/Resources/images/voip_info.png
new file mode 100644
index 000000000..ae8dd86f8
Binary files /dev/null and b/Resources/images/voip_info.png differ
diff --git a/Resources/images/voip_mandatory.png b/Resources/images/voip_mandatory.png
new file mode 100644
index 000000000..4be37f7e4
Binary files /dev/null and b/Resources/images/voip_mandatory.png differ
diff --git a/Resources/images/voip_menu_more.png b/Resources/images/voip_menu_more.png
new file mode 100644
index 000000000..7f7f7d8fd
Binary files /dev/null and b/Resources/images/voip_menu_more.png differ
diff --git a/Resources/images/voip_merge_calls.png b/Resources/images/voip_merge_calls.png
new file mode 100644
index 000000000..6c4da5988
Binary files /dev/null and b/Resources/images/voip_merge_calls.png differ
diff --git a/Resources/images/voip_micro_off.png b/Resources/images/voip_micro_off.png
new file mode 100644
index 000000000..57569b4f2
Binary files /dev/null and b/Resources/images/voip_micro_off.png differ
diff --git a/Resources/images/voip_micro_on.png b/Resources/images/voip_micro_on.png
new file mode 100644
index 000000000..6552d35d5
Binary files /dev/null and b/Resources/images/voip_micro_on.png differ
diff --git a/Resources/images/voip_multiple_contacts_avatar.png b/Resources/images/voip_multiple_contacts_avatar.png
new file mode 100644
index 000000000..78b10f11b
Binary files /dev/null and b/Resources/images/voip_multiple_contacts_avatar.png differ
diff --git a/Resources/images/voip_numpad_0.png b/Resources/images/voip_numpad_0.png
new file mode 100644
index 000000000..115bdb17d
Binary files /dev/null and b/Resources/images/voip_numpad_0.png differ
diff --git a/Resources/images/voip_numpad_1.png b/Resources/images/voip_numpad_1.png
new file mode 100644
index 000000000..4d8b7f5cc
Binary files /dev/null and b/Resources/images/voip_numpad_1.png differ
diff --git a/Resources/images/voip_numpad_2.png b/Resources/images/voip_numpad_2.png
new file mode 100644
index 000000000..6b561c468
Binary files /dev/null and b/Resources/images/voip_numpad_2.png differ
diff --git a/Resources/images/voip_numpad_3.png b/Resources/images/voip_numpad_3.png
new file mode 100644
index 000000000..386715586
Binary files /dev/null and b/Resources/images/voip_numpad_3.png differ
diff --git a/Resources/images/voip_numpad_4.png b/Resources/images/voip_numpad_4.png
new file mode 100644
index 000000000..e3dfdcc51
Binary files /dev/null and b/Resources/images/voip_numpad_4.png differ
diff --git a/Resources/images/voip_numpad_5.png b/Resources/images/voip_numpad_5.png
new file mode 100644
index 000000000..a18af28e5
Binary files /dev/null and b/Resources/images/voip_numpad_5.png differ
diff --git a/Resources/images/voip_numpad_6.png b/Resources/images/voip_numpad_6.png
new file mode 100644
index 000000000..79279cb99
Binary files /dev/null and b/Resources/images/voip_numpad_6.png differ
diff --git a/Resources/images/voip_numpad_7.png b/Resources/images/voip_numpad_7.png
new file mode 100644
index 000000000..c68656fd3
Binary files /dev/null and b/Resources/images/voip_numpad_7.png differ
diff --git a/Resources/images/voip_numpad_8.png b/Resources/images/voip_numpad_8.png
new file mode 100644
index 000000000..8d84c96ba
Binary files /dev/null and b/Resources/images/voip_numpad_8.png differ
diff --git a/Resources/images/voip_numpad_9.png b/Resources/images/voip_numpad_9.png
new file mode 100644
index 000000000..af3e0e0bf
Binary files /dev/null and b/Resources/images/voip_numpad_9.png differ
diff --git a/Resources/images/voip_numpad_hash.png b/Resources/images/voip_numpad_hash.png
new file mode 100644
index 000000000..790e7d12e
Binary files /dev/null and b/Resources/images/voip_numpad_hash.png differ
diff --git a/Resources/images/voip_numpad_star.png b/Resources/images/voip_numpad_star.png
new file mode 100644
index 000000000..5a2649de4
Binary files /dev/null and b/Resources/images/voip_numpad_star.png differ
diff --git a/Resources/images/voip_pause.png b/Resources/images/voip_pause.png
new file mode 100644
index 000000000..e888da937
Binary files /dev/null and b/Resources/images/voip_pause.png differ
diff --git a/Resources/images/voip_radio_off.png b/Resources/images/voip_radio_off.png
new file mode 100644
index 000000000..b703dea80
Binary files /dev/null and b/Resources/images/voip_radio_off.png differ
diff --git a/Resources/images/voip_radio_on.png b/Resources/images/voip_radio_on.png
new file mode 100644
index 000000000..feaf00e11
Binary files /dev/null and b/Resources/images/voip_radio_on.png differ
diff --git a/Resources/images/voip_remote_recording.png b/Resources/images/voip_remote_recording.png
new file mode 100644
index 000000000..3e4e94009
Binary files /dev/null and b/Resources/images/voip_remote_recording.png differ
diff --git a/Resources/images/voip_single_contact_avatar.png b/Resources/images/voip_single_contact_avatar.png
new file mode 100644
index 000000000..5d158fa49
Binary files /dev/null and b/Resources/images/voip_single_contact_avatar.png differ
diff --git a/Resources/images/voip_speaker_off.png b/Resources/images/voip_speaker_off.png
new file mode 100644
index 000000000..c018c9899
Binary files /dev/null and b/Resources/images/voip_speaker_off.png differ
diff --git a/Resources/images/voip_speaker_on.png b/Resources/images/voip_speaker_on.png
new file mode 100644
index 000000000..07f13c9b6
Binary files /dev/null and b/Resources/images/voip_speaker_on.png differ
diff --git a/Resources/images/voip_spinner.png b/Resources/images/voip_spinner.png
new file mode 100644
index 000000000..8238de56b
Binary files /dev/null and b/Resources/images/voip_spinner.png differ
diff --git a/linphone-Info.plist b/linphone-Info.plist
index 8bd93f866..b9d24c1fb 100644
--- a/linphone-Info.plist
+++ b/linphone-Info.plist
@@ -102,6 +102,8 @@
Share photos with your friends and customize avatars
NSContactsUsageDescription
Make calls with your friends
+ NSLocalNetworkUsageDescription
+ Stream audio and video through the local network
NSLocationWhenInUseUsageDescription
Linphone will not use, store or communicate your location. The authorization allows us to detect Wifi connection changes
NSMicrophoneUsageDescription
@@ -110,8 +112,6 @@
Add tranfered files to your library
NSPhotoLibraryUsageDescription
Share photos with your friends and customize avatars
- NSLocalNetworkUsageDescription
- Stream audio and video through the local network
NSUbiquitousContainers
iCloud.org.linphone.phone
@@ -126,6 +126,12 @@
NSVoIPUsageDescription
Make audio/video calls
+ UIAppFonts
+
+ Roboto-Regular.ttf
+ Roboto-Bold.ttf
+ Roboto-Italic.ttf
+
UIApplicationShortcutItems
diff --git a/linphone.xcodeproj/project.pbxproj b/linphone.xcodeproj/project.pbxproj
index 3fbac849c..f0ab4df60 100644
--- a/linphone.xcodeproj/project.pbxproj
+++ b/linphone.xcodeproj/project.pbxproj
@@ -7,13 +7,13 @@
objects = {
/* Begin PBXBuildFile section */
+ 017AC1D70F142AE8EAC13BDB /* Pods_msgNotificationContent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A164BAF39B3A5B9F905917A7 /* Pods_msgNotificationContent.framework */; };
152F22361B15E889008C0621 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 152F22351B15E889008C0621 /* libxml2.dylib */; };
1D3623260D0F684500981E51 /* LinphoneAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* LinphoneAppDelegate.m */; };
1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; };
1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; };
1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
2214EB7A12F846B1002A5394 /* UICallButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2214EB7912F846B1002A5394 /* UICallButton.m */; };
- 2214EB8912F84EBB002A5394 /* UIHangUpButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2214EB8812F84EBB002A5394 /* UIHangUpButton.m */; };
2214EBF312F86360002A5394 /* UIMutedMicroButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 2214EBF212F86360002A5394 /* UIMutedMicroButton.m */; };
22276E8913C73DC000210156 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22276E8813C73DC000210156 /* CoreMedia.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
22405EEE1600B4E400B92522 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22405EED1600B4E400B92522 /* AssetsLibrary.framework */; };
@@ -25,8 +25,6 @@
2274401A106F31BD006EC466 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 22744019106F31BD006EC466 /* CoreAudio.framework */; };
2274402F106F335E006EC466 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2274402E106F335E006EC466 /* AudioToolbox.framework */; };
228697C411AC29B800E9E0CA /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 228697C311AC29B800E9E0CA /* CFNetwork.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
- 22968A5F12F875C600588287 /* UISpeakerButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 22968A5E12F875C600588287 /* UISpeakerButton.m */; };
- 22AA8B0113D83F6300B30535 /* UICamSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AA8B0013D83F6300B30535 /* UICamSwitch.m */; };
22C755601317E59C007BC101 /* UIBluetoothButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 22C7555F1317E59C007BC101 /* UIBluetoothButton.m */; };
22D1B68112A3E0BE001AE361 /* libresolv.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 22D1B68012A3E0BE001AE361 /* libresolv.dylib */; };
22E0A822111C44E100B04932 /* AboutView.m in Sources */ = {isa = PBXBuildFile; fileRef = 22E0A81C111C44E100B04932 /* AboutView.m */; };
@@ -53,10 +51,8 @@
24E1C7C01F9A235600D3F981 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1C7B91F9A235500D3F981 /* Contacts.framework */; };
288765FD0DF74451002DB57D /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 288765FC0DF74451002DB57D /* CoreGraphics.framework */; };
340751971506459A00B89C47 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 340751961506459A00B89C47 /* CoreTelephony.framework */; };
- 340751E7150F38FD00B89C47 /* UIVideoButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 340751E6150F38FD00B89C47 /* UIVideoButton.m */; };
34216F401547EBCD00EA9777 /* VideoZoomHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 34216F3F1547EBCD00EA9777 /* VideoZoomHandler.m */; };
344ABDF114850AE9007420B6 /* libc++.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 344ABDEF14850AE9007420B6 /* libc++.1.dylib */; settings = {ATTRIBUTES = (Weak, ); }; };
- 369CCF81C921CD7C4E49A637 /* Pods_msgNotificationContent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82E9DEDA2A78C6DBBD1A54DB /* Pods_msgNotificationContent.framework */; };
570742581D5A0691004B9C84 /* ShopView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 570742561D5A0691004B9C84 /* ShopView.xib */; };
570742611D5A09B8004B9C84 /* ShopView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5707425F1D5A09B8004B9C84 /* ShopView.m */; };
570742671D5A63DB004B9C84 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 570742661D5A63DB004B9C84 /* StoreKit.framework */; };
@@ -109,7 +105,6 @@
61AE364F20C00B370089D9D3 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61AE364E20C00B370089D9D3 /* ShareViewController.m */; };
61AE365220C00B370089D9D3 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 61AE365020C00B370089D9D3 /* MainInterface.storyboard */; };
61AE365620C00B370089D9D3 /* linphoneExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 61AE364B20C00B370089D9D3 /* linphoneExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
- 61AEBEA321906AFC00F35E7F /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
61AEBEBD2191990A00F35E7F /* DevicesListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 61AEBEBC2191990A00F35E7F /* DevicesListView.m */; };
61AEBEBF2191991F00F35E7F /* DevicesListView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 61AEBEBE2191991F00F35E7F /* DevicesListView.xib */; };
61AEBEC62191E47500F35E7F /* chevron_list_close.png in Resources */ = {isa = PBXBuildFile; fileRef = 61AEBEC52191E47500F35E7F /* chevron_list_close.png */; };
@@ -563,11 +558,7 @@
6341807C1BBC103100F71761 /* ChatConversationCreateTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6341807B1BBC103100F71761 /* ChatConversationCreateTableView.m */; };
63423C0A1C4501D000D9A050 /* Contact.m in Sources */ = {isa = PBXBuildFile; fileRef = 63423C091C4501D000D9A050 /* Contact.m */; };
634610061B61330300548952 /* UILabel+Boldify.m in Sources */ = {isa = PBXBuildFile; fileRef = 634610051B61330300548952 /* UILabel+Boldify.m */; };
- 6346100F1B61409800548952 /* CallOutgoingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6346100E1B61409800548952 /* CallOutgoingView.m */; };
- 634610121B6140A500548952 /* CallOutgoingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 634610101B6140A500548952 /* CallOutgoingView.xib */; };
635173F91BA082A40095EB0A /* UIChatBubblePhotoCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 635173F81BA082A40095EB0A /* UIChatBubblePhotoCell.m */; };
- 6352A5751BE0D4B800594C1C /* CallSideMenuView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6352A5731BE0D4B800594C1C /* CallSideMenuView.m */; };
- 6352A5761BE0D4B800594C1C /* CallSideMenuView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6352A5741BE0D4B800594C1C /* CallSideMenuView.xib */; };
635775251B6673EC00C8B704 /* HistoryDetailsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 635775241B6673EC00C8B704 /* HistoryDetailsTableView.m */; };
636316D11A1DEBCB0009B839 /* AboutView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 636316D31A1DEBCB0009B839 /* AboutView.xib */; };
636316D41A1DEC650009B839 /* SettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 636316D61A1DEC650009B839 /* SettingsView.xib */; };
@@ -577,7 +568,6 @@
6377AC801BDE4069007F7625 /* UIBackToCallButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 6377AC7F1BDE4069007F7625 /* UIBackToCallButton.m */; };
6381DA7D1C1AD5EA00DF3BBD /* UIBouncingView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6381DA7C1C1AD5EA00DF3BBD /* UIBouncingView.m */; };
638F1A621C2021B2004B8E02 /* DialerView~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 638F1A601C2021B2004B8E02 /* DialerView~ipad.xib */; };
- 638F1A881C2167C2004B8E02 /* CallView~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 638F1A861C2167C2004B8E02 /* CallView~ipad.xib */; };
638F1A911C21993D004B8E02 /* UICompositeView~ipad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 638F1A8F1C21993D004B8E02 /* UICompositeView~ipad.xib */; };
639CEAFD1A1DF4D9004DE38F /* StatusBarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEAFF1A1DF4D9004DE38F /* StatusBarView.xib */; };
639CEB001A1DF4E4004DE38F /* UIHistoryCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEB021A1DF4E4004DE38F /* UIHistoryCell.xib */; };
@@ -585,7 +575,6 @@
639CEB091A1DF4FA004DE38F /* UIChatCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639CEB0B1A1DF4FA004DE38F /* UIChatCell.xib */; };
639E9C801C0DB13D00019A75 /* UICheckBoxTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 639E9C7F1C0DB13D00019A75 /* UICheckBoxTableView.m */; };
639E9C931C0DB7BE00019A75 /* FirstLoginView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9C951C0DB7BE00019A75 /* FirstLoginView.xib */; };
- 639E9C9D1C0DB7DF00019A75 /* UICallPausedCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9C9F1C0DB7DF00019A75 /* UICallPausedCell.xib */; };
639E9CA01C0DB7E500019A75 /* UIChatBubblePhotoCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9CA21C0DB7E500019A75 /* UIChatBubblePhotoCell.xib */; };
639E9CA31C0DB7EA00019A75 /* UIChatBubbleTextCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9CA51C0DB7EA00019A75 /* UIChatBubbleTextCell.xib */; };
639E9CA61C0DB7F200019A75 /* UIChatCreateCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 639E9CA81C0DB7F200019A75 /* UIChatCreateCell.xib */; };
@@ -609,7 +598,6 @@
63B81A101B57DA33009604A6 /* UIScrollView+TPKeyboardAvoidingAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 63B81A0B1B57DA33009604A6 /* UIScrollView+TPKeyboardAvoidingAdditions.m */; };
63B8D68C1BCBE65600C12B09 /* ChatConversationCreateView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63B8D68E1BCBE65600C12B09 /* ChatConversationCreateView.xib */; };
63B8D6A21BCBF43100C12B09 /* UIChatCreateCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 63B8D6A01BCBF43100C12B09 /* UIChatCreateCell.m */; };
- 63BC49E21BA2CDFC004EC273 /* UICallPausedCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 63BC49E11BA2CDFC004EC273 /* UICallPausedCell.m */; };
63BE7A781D75BDF6000990EF /* ShopTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63BE7A771D75BDF6000990EF /* ShopTableView.m */; };
63C441C31BBC23ED0053DC5E /* UIAssistantTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 63C441C21BBC23ED0053DC5E /* UIAssistantTextField.m */; };
63CD4B4F1A5AAC8C00B84282 /* DTAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63CD4B4E1A5AAC8C00B84282 /* DTAlertView.m */; };
@@ -622,16 +610,12 @@
63E27A321C4FECD000D332AE /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A311C4FECD000D332AE /* LaunchScreen.xib */; };
63E27A521C50EDB000D332AE /* hold.mkv in Resources */ = {isa = PBXBuildFile; fileRef = 63E27A511C50EB2700D332AE /* hold.mkv */; };
63E59A3F1ADE70D900646FB3 /* InAppProductsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E59A3E1ADE70D900646FB3 /* InAppProductsManager.m */; };
- 63E802DB1C625AEF000D5509 /* BuildFile in Resources */ = {isa = PBXBuildFile; };
+ 63E802DB1C625AEF000D5509 /* (null) in Resources */ = {isa = PBXBuildFile; };
63EC8D391D7438660066547B /* AssistantLinkView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63EC8D3B1D7438660066547B /* AssistantLinkView.xib */; };
63F1DF441BCE618E00EDED90 /* UIAddressTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */; };
- 63F1DF4B1BCE983200EDED90 /* CallConferenceTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF4A1BCE983200EDED90 /* CallConferenceTableView.m */; };
- 63F1DF4F1BCE985F00EDED90 /* UICallConferenceCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F1DF4D1BCE985F00EDED90 /* UICallConferenceCell.m */; };
- 63F1DF511BCE986A00EDED90 /* UICallConferenceCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63F1DF531BCE986A00EDED90 /* UICallConferenceCell.xib */; };
63FB30351A680E73008CA393 /* UIRoundedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 63FB30341A680E73008CA393 /* UIRoundedImageView.m */; };
70E542F313E147E3002BA2C0 /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70E542F213E147E3002BA2C0 /* OpenGLES.framework */; };
70E542F513E147EB002BA2C0 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70E542F413E147EB002BA2C0 /* QuartzCore.framework */; };
- 8C1B67061E671826001EA2FE /* AudioHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 8C1B67051E671826001EA2FE /* AudioHelper.m */; };
8C2595DF1DEDCC8E007A6424 /* CallKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C2595DE1DEDCC8E007A6424 /* CallKit.framework */; };
8C2A81951F87B8000012A66B /* chat_group_avatar@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8C2A81931F87B7FF0012A66B /* chat_group_avatar@2x.png */; };
8C2A81961F87B8000012A66B /* chat_group_avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = 8C2A81941F87B8000012A66B /* chat_group_avatar.png */; };
@@ -670,8 +654,11 @@
8CF25D961F9F336100BEA0C1 /* check_unselected.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CF25D941F9F336100BEA0C1 /* check_unselected.png */; };
8CF25D9D1F9F76BD00BEA0C1 /* chat_group_informations.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CF25D9B1F9F76BC00BEA0C1 /* chat_group_informations.png */; };
8CF25D9E1F9F76BD00BEA0C1 /* chat_group_informations@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8CF25D9C1F9F76BD00BEA0C1 /* chat_group_informations@2x.png */; };
- 93566413F75DA69D2811A716 /* Pods_msgNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F30EA7BEA39DA427CE0754E /* Pods_msgNotificationService.framework */; };
- A634ABAFCB39B6AAE4CA991D /* Pods_linphone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65CEDD144CABFAA70A29AF27 /* Pods_linphone.framework */; };
+ 9C0B30F54D61774AFD1473CE /* Pods_linphone.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B464E44A606CB50A65A96FE2 /* Pods_linphone.framework */; };
+ C60B66682721AFFA0026AC7D /* CallStatisticsData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60B66672721AFFA0026AC7D /* CallStatisticsData.swift */; };
+ C60D265627299C94006238BB /* ControlsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60D265527299C94006238BB /* ControlsViewModel.swift */; };
+ C60D265827299F70006238BB /* CoreExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60D265727299F6F006238BB /* CoreExtensions.swift */; };
+ C60D265C272AA0BD006238BB /* UIImageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C60D265B272AA0BD006238BB /* UIImageExtensions.swift */; };
C61B1BF22667D075001A4E4A /* menu_security_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF12667D075001A4E4A /* menu_security_default.png */; };
C61B1BF42667D202001A4E4A /* more_menu_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF32667D202001A4E4A /* more_menu_default.png */; };
C61B1BF72667EC6B001A4E4A /* ephemeral_messages_color_A.png in Resources */ = {isa = PBXBuildFile; fileRef = C61B1BF62667EC6B001A4E4A /* ephemeral_messages_color_A.png */; };
@@ -681,14 +668,105 @@
C622E3F226A81290004F5434 /* vr_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EC26A8128F004F5434 /* vr_off.png */; };
C622E3F326A81290004F5434 /* vr_pause.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3ED26A8128F004F5434 /* vr_pause.png */; };
C622E3F426A81290004F5434 /* vr_play.png in Resources */ = {isa = PBXBuildFile; fileRef = C622E3EE26A81290004F5434 /* vr_play.png */; };
+ C6277DA8274BF1CE00406FB9 /* voip_radio_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C6277DA6274BF1CD00406FB9 /* voip_radio_on.png */; };
+ C6277DA9274BF1CE00406FB9 /* voip_radio_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C6277DA7274BF1CD00406FB9 /* voip_radio_off.png */; };
+ C6278497273C21E1002FAA29 /* LocalVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6278496273C21E1002FAA29 /* LocalVideoView.swift */; };
C64A854E2667B67200252AD2 /* EphemeralSettingsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C64A854D2667B67200252AD2 /* EphemeralSettingsView.m */; };
C64A85502667B67A00252AD2 /* EphemeralSettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C64A854F2667B67A00252AD2 /* EphemeralSettingsView.xib */; };
C64A85522667B74100252AD2 /* ephemeral_messages_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C64A85512667B74100252AD2 /* ephemeral_messages_default.png */; };
+ C6586149273E595700A0DBFC /* VoipExtraButtonsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6586148273E595700A0DBFC /* VoipExtraButtonsView.swift */; };
+ C658614C273E5B5E00A0DBFC /* VoipExtraButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C658614B273E5B5E00A0DBFC /* VoipExtraButton.swift */; };
+ C65A5D3027216B86005BA038 /* ActiveCallOrConferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65A5D2F27216B86005BA038 /* ActiveCallOrConferenceView.swift */; };
+ C65A5D3B27216CC0005BA038 /* MutableLiveData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65A5D3727216CC0005BA038 /* MutableLiveData.swift */; };
+ C65A5D3F27216E3A005BA038 /* CallData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65A5D3E27216E3A005BA038 /* CallData.swift */; };
+ C65A5D45272196AE005BA038 /* OptionalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C65A5D44272196AE005BA038 /* OptionalExtensions.swift */; };
C666756F264C925800A0273C /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; };
C6667571264C925B00A0273C /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; };
C66B03BB26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = C66B03BD26E8EB1A009B5EDC /* UIChatReplyBubbleView.xib */; };
C66B040A26EFDA55009B5EDC /* reply_cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = C66B040926EFDA54009B5EDC /* reply_cancel.png */; };
C66B040E26F095D1009B5EDC /* cancel_forward.png in Resources */ = {isa = PBXBuildFile; fileRef = C66B040D26F095CE009B5EDC /* cancel_forward.png */; };
+ C6710F4F2722903200ED888F /* RotatingSpinner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F4E2722903200ED888F /* RotatingSpinner.swift */; };
+ C6710F512722932600ED888F /* UIImageViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F502722932600ED888F /* UIImageViewExtensions.swift */; };
+ C6710F53272297C400ED888F /* VoipTexts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F52272297C400ED888F /* VoipTexts.swift */; };
+ C6710F5527229D5900ED888F /* LightDarkColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F5427229D5900ED888F /* LightDarkColor.swift */; };
+ C6710F5727229DEE00ED888F /* TextStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F5627229DEE00ED888F /* TextStyle.swift */; };
+ C6710F592722A9B800ED888F /* StyledLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F582722A9B800ED888F /* StyledLabel.swift */; };
+ C6710F5C2722AAED00ED888F /* UIDeviceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710F5B2722AAED00ED888F /* UIDeviceExtensions.swift */; };
+ C6710F612722AECB00ED888F /* Roboto-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C6710F5E2722AECB00ED888F /* Roboto-Italic.ttf */; };
+ C6710F622722AECB00ED888F /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C6710F5F2722AECB00ED888F /* Roboto-Bold.ttf */; };
+ C6710F632722AECB00ED888F /* Roboto-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C6710F602722AECB00ED888F /* Roboto-Regular.ttf */; };
+ C6710F652722B13000ED888F /* voip_spinner.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F642722B13000ED888F /* voip_spinner.png */; };
+ C6710F9F2722B20000ED888F /* voip_numpad_1.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F662722B1EF00ED888F /* voip_numpad_1.png */; };
+ C6710FA02722B20000ED888F /* voip_numpad_0.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F672722B1EF00ED888F /* voip_numpad_0.png */; };
+ C6710FA12722B20000ED888F /* voip_copy.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F682722B1EF00ED888F /* voip_copy.png */; };
+ C6710FA22722B20000ED888F /* voip_merge_calls.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F692722B1EF00ED888F /* voip_merge_calls.png */; };
+ C6710FA32722B20000ED888F /* voip_call_numpad.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F6A2722B1EF00ED888F /* voip_call_numpad.png */; };
+ C6710FA42722B20000ED888F /* voip_single_contact_avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F6B2722B1F000ED888F /* voip_single_contact_avatar.png */; };
+ C6710FA52722B20000ED888F /* voip_mandatory.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F6C2722B1F000ED888F /* voip_mandatory.png */; };
+ C6710FA62722B20000ED888F /* voip_bluetooth.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F6D2722B1F000ED888F /* voip_bluetooth.png */; };
+ C6710FA72722B20000ED888F /* voip_call_more.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F6E2722B1F000ED888F /* voip_call_more.png */; };
+ C6710FA82722B20000ED888F /* voip_multiple_contacts_avatar.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F6F2722B1F100ED888F /* voip_multiple_contacts_avatar.png */; };
+ C6710FA92722B20000ED888F /* voip_call_stats.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F702722B1F100ED888F /* voip_call_stats.png */; };
+ C6710FAA2722B20000ED888F /* voip_conference_active_speaker.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F712722B1F100ED888F /* voip_conference_active_speaker.png */; };
+ C6710FAB2722B20000ED888F /* voip_edit.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F722722B1F200ED888F /* voip_edit.png */; };
+ C6710FAC2722B20000ED888F /* voip_call_chat.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F732722B1F200ED888F /* voip_call_chat.png */; };
+ C6710FAD2722B20000ED888F /* voip_numpad_5.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F742722B1F200ED888F /* voip_numpad_5.png */; };
+ C6710FAE2722B20000ED888F /* voip_hangup.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F752722B1F300ED888F /* voip_hangup.png */; };
+ C6710FAF2722B20000ED888F /* voip_conference_new.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F762722B1F300ED888F /* voip_conference_new.png */; };
+ C6710FB02722B20000ED888F /* voip_chat_rooms_list.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F772722B1F300ED888F /* voip_chat_rooms_list.png */; };
+ C6710FB12722B20000ED888F /* voip_micro_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F782722B1F300ED888F /* voip_micro_on.png */; };
+ C6710FB22722B20000ED888F /* voip_calls_list.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F792722B1F400ED888F /* voip_calls_list.png */; };
+ C6710FB32722B20000ED888F /* voip_pause.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F7A2722B1F400ED888F /* voip_pause.png */; };
+ C6710FB42722B20000ED888F /* voip_conference_mosaic.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F7B2722B1F500ED888F /* voip_conference_mosaic.png */; };
+ C6710FB52722B20000ED888F /* voip_delete.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F7C2722B1F500ED888F /* voip_delete.png */; };
+ C6710FB62722B20000ED888F /* voip_audio_routes.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F7D2722B1F500ED888F /* voip_audio_routes.png */; };
+ C6710FB72722B20000ED888F /* voip_numpad_6.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F7E2722B1F600ED888F /* voip_numpad_6.png */; };
+ C6710FB82722B20000ED888F /* voip_numpad_3.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F7F2722B1F600ED888F /* voip_numpad_3.png */; };
+ C6710FB92722B20000ED888F /* voip_call_add.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F802722B1F600ED888F /* voip_call_add.png */; };
+ C6710FBA2722B20000ED888F /* voip_numpad_9.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F812722B1F700ED888F /* voip_numpad_9.png */; };
+ C6710FBB2722B20000ED888F /* voip_call.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F822722B1F700ED888F /* voip_call.png */; };
+ C6710FBC2722B20000ED888F /* voip_change_camera.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F832722B1F700ED888F /* voip_change_camera.png */; };
+ C6710FBD2722B20000ED888F /* voip_call_header_paused.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F842722B1F800ED888F /* voip_call_header_paused.png */; };
+ C6710FBE2722B20000ED888F /* voip_export.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F852722B1F800ED888F /* voip_export.png */; };
+ C6710FBF2722B20000ED888F /* voip_earpiece.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F862722B1F800ED888F /* voip_earpiece.png */; };
+ C6710FC02722B20000ED888F /* voip_numpad_hash.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F872722B1F800ED888F /* voip_numpad_hash.png */; };
+ C6710FC12722B20000ED888F /* voip_micro_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F882722B1F900ED888F /* voip_micro_off.png */; };
+ C6710FC22722B20000ED888F /* voip_numpad_2.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F892722B1F900ED888F /* voip_numpad_2.png */; };
+ C6710FC32722B20000ED888F /* voip_cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F8A2722B1F900ED888F /* voip_cancel.png */; };
+ C6710FC42722B20000ED888F /* voip_numpad_star.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F8B2722B1FA00ED888F /* voip_numpad_star.png */; };
+ C6710FC52722B20000ED888F /* voip_camera_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F8C2722B1FA00ED888F /* voip_camera_off.png */; };
+ C6710FC62722B20000ED888F /* voip_menu_more.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F8D2722B1FA00ED888F /* voip_menu_more.png */; };
+ C6710FC72722B20000ED888F /* voip_numpad_4.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F8E2722B1FB00ED888F /* voip_numpad_4.png */; };
+ C6710FC82722B20000ED888F /* voip_numpad_7.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F8F2722B1FB00ED888F /* voip_numpad_7.png */; };
+ C6710FC92722B20000ED888F /* voip_speaker_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F902722B1FB00ED888F /* voip_speaker_on.png */; };
+ C6710FCA2722B20000ED888F /* voip_call_header_incoming.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F912722B1FC00ED888F /* voip_call_header_incoming.png */; };
+ C6710FCB2722B20000ED888F /* voip_remote_recording.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F922722B1FC00ED888F /* voip_remote_recording.png */; };
+ C6710FCC2722B20000ED888F /* voip_call_list_menu.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F932722B1FC00ED888F /* voip_call_list_menu.png */; };
+ C6710FCD2722B20000ED888F /* voip_call_participants.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F942722B1FD00ED888F /* voip_call_participants.png */; };
+ C6710FCE2722B20000ED888F /* voip_speaker_off.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F952722B1FD00ED888F /* voip_speaker_off.png */; };
+ C6710FCF2722B20000ED888F /* voip_numpad_8.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F962722B1FD00ED888F /* voip_numpad_8.png */; };
+ C6710FD02722B20000ED888F /* voip_call_header_active.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F972722B1FE00ED888F /* voip_call_header_active.png */; };
+ C6710FD12722B20000ED888F /* voip_dropdown.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F982722B1FE00ED888F /* voip_dropdown.png */; };
+ C6710FD22722B20000ED888F /* voip_call_record.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F992722B1FE00ED888F /* voip_call_record.png */; };
+ C6710FD32722B20000ED888F /* voip_conference_paused_big.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F9A2722B1FE00ED888F /* voip_conference_paused_big.png */; };
+ C6710FD42722B20000ED888F /* voip_camera_on.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F9B2722B1FF00ED888F /* voip_camera_on.png */; };
+ C6710FD52722B20000ED888F /* voip_call_header_outgoing.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F9C2722B1FF00ED888F /* voip_call_header_outgoing.png */; };
+ C6710FD62722B20000ED888F /* voip_conference_play_big.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F9D2722B20000ED888F /* voip_conference_play_big.png */; };
+ C6710FD72722B20000ED888F /* voip_info.png in Resources */ = {isa = PBXBuildFile; fileRef = C6710F9E2722B20000ED888F /* voip_info.png */; };
+ C6710FD92722BD0100ED888F /* UICallTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FD82722BD0100ED888F /* UICallTimer.swift */; };
+ C6710FDC2722C3BB00ED888F /* UIVIewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FDB2722C3BB00ED888F /* UIVIewExtensions.swift */; };
+ C6710FDE2722D44B00ED888F /* IncomingOuntgoingCommonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FDD2722D44A00ED888F /* IncomingOuntgoingCommonView.swift */; };
+ C6710FE12722F0E400ED888F /* Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FE02722F0E400ED888F /* Avatar.swift */; };
+ C6710FE527230B5800ED888F /* AddressExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FE427230B5800ED888F /* AddressExtensions.swift */; };
+ C6710FE72723234400ED888F /* OutgoingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FE62723234400ED888F /* OutgoingCallView.swift */; };
+ C6710FE92723DD7D00ED888F /* CallControlButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FE82723DD7D00ED888F /* CallControlButton.swift */; };
+ C6710FEB2726874D00ED888F /* ButtonTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6710FEA2726874D00ED888F /* ButtonTheme.swift */; };
+ C67C97B1274FB4C10074A0D8 /* VoipConferenceDisplayModeSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67C97B0274FB4C10074A0D8 /* VoipConferenceDisplayModeSelectionView.swift */; };
+ C67C97B4274FC5EF0074A0D8 /* AudioRoutesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67C97B3274FC5EE0074A0D8 /* AudioRoutesView.swift */; };
+ C67C97B8274FD76B0074A0D8 /* AudioRouteUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = C67C97B7274FD76B0074A0D8 /* AudioRouteUtils.swift */; };
+ C6824FBA27219D890043D4FC /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6824FB927219D890043D4FC /* IncomingCallView.swift */; };
+ C683B20E2722702300D4E15C /* VoipTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = C683B20D2722702300D4E15C /* VoipTheme.swift */; };
+ C683B213272276CF00D4E15C /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C683B212272276CF00D4E15C /* UIColorExtensions.swift */; };
C6A1BB3526E8815400540D50 /* menu_info.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3126E8815300540D50 /* menu_info.png */; };
C6A1BB3626E8815400540D50 /* menu_forward_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3226E8815400540D50 /* menu_forward_default.png */; };
C6A1BB3726E8815400540D50 /* menu_copy_text_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB3326E8815400540D50 /* menu_copy_text_default.png */; };
@@ -698,13 +776,46 @@
C6A1BB4126E889AD00540D50 /* forward_message_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4026E889AD00540D50 /* forward_message_default.png */; };
C6A1BB4326E88F7C00540D50 /* menu_resend_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4226E88F7C00540D50 /* menu_resend_default.png */; };
C6A1BB4526E890BD00540D50 /* file_voice_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6A1BB4426E890BD00540D50 /* file_voice_default.png */; };
+ C6B04D61274B954500F70559 /* ParticipantsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D60274B954500F70559 /* ParticipantsListView.swift */; };
+ C6B04D63274B95D500F70559 /* VoipParticipantCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D62274B95D400F70559 /* VoipParticipantCell.swift */; };
+ C6B04D67274BD61300F70559 /* VoipConferenceActiveSpeakerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D66274BD61200F70559 /* VoipConferenceActiveSpeakerView.swift */; };
+ C6B04D69274BD6A100F70559 /* VoipActiveSpeakerParticipantCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B04D68274BD6A100F70559 /* VoipActiveSpeakerParticipantCell.swift */; };
C6B4444226AAD0980076C517 /* file_video_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443D26AAD0970076C517 /* file_video_default.png */; };
C6B4444326AAD0980076C517 /* file_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443E26AAD0970076C517 /* file_default.png */; };
C6B4444426AAD0980076C517 /* file_picture_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4443F26AAD0970076C517 /* file_picture_default.png */; };
C6B4444526AAD0980076C517 /* file_audio_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4444026AAD0970076C517 /* file_audio_default.png */; };
C6B4444626AAD0980076C517 /* file_pdf_default.png in Resources */ = {isa = PBXBuildFile; fileRef = C6B4444126AAD0970076C517 /* file_pdf_default.png */; };
C6B4444826AADA530076C517 /* SwiftUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6B4444726AADA530076C517 /* SwiftUtil.swift */; };
+ C6C65E89272723DC00E48FC6 /* UIVIewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C65E88272723DC00E48FC6 /* UIVIewControllerExtensions.swift */; };
+ C6C65E8B2727274A00E48FC6 /* NumpadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C65E8A2727274A00E48FC6 /* NumpadView.swift */; };
+ C6C98CCE27438A3F00059B55 /* DismissableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CCD27438A3F00059B55 /* DismissableView.swift */; };
+ C6C98CD027439A7F00059B55 /* VoipCallCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CCF27439A7F00059B55 /* VoipCallCell.swift */; };
+ C6C98CD22743FD0B00059B55 /* VoipCallContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CD12743FD0B00059B55 /* VoipCallContextMenu.swift */; };
+ C6C98CD527453ED900059B55 /* ConferenceViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CD427453ED700059B55 /* ConferenceViewModel.swift */; };
+ C6C98CD727453F9600059B55 /* ConferenceParticipantData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CD627453F9600059B55 /* ConferenceParticipantData.swift */; };
+ C6C98CDB274541E400059B55 /* ConferenceParticipantDeviceData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CDA274541E400059B55 /* ConferenceParticipantDeviceData.swift */; };
+ C6C98CDD274547C500059B55 /* ParticipantExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CDC274547C500059B55 /* ParticipantExtensions.swift */; };
+ C6C98CDF2745590500059B55 /* ConferenceExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CDE2745590400059B55 /* ConferenceExtensions.swift */; };
+ C6C98CE1274568F800059B55 /* VoipConferenceGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6C98CE0274568F700059B55 /* VoipConferenceGridView.swift */; };
+ C6D09F3D273EE467003C2173 /* BouncingCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F3C273EE467003C2173 /* BouncingCounter.swift */; };
+ C6D09F3F274273FB003C2173 /* CallStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F3E274273FB003C2173 /* CallStatsView.swift */; };
+ C6D09F4127428626003C2173 /* IceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F4027428626003C2173 /* IceState.swift */; };
+ C6D09F43274288D4003C2173 /* PayloadType.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F42274288D4003C2173 /* PayloadType.swift */; };
+ C6D09F4B27438707003C2173 /* CallsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D09F4A27438706003C2173 /* CallsListView.swift */; };
+ C6D1EC4A274D212B0091881C /* UICamSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = C6D1EC49274D212B0091881C /* UICamSwitch.m */; };
+ C6D52B45274648E500904660 /* VoipGridParticipantCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6D52B44274648E500904660 /* VoipGridParticipantCell.swift */; };
C6DA657C261C950C0020CB43 /* VFSUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6DA657B261C950C0020CB43 /* VFSUtil.swift */; };
+ C6EA2F4827514D09008E60F8 /* voip_call_forward.png in Resources */ = {isa = PBXBuildFile; fileRef = C6EA2F4727514D08008E60F8 /* voip_call_forward.png */; };
+ C6F2D4EF27392D970071BA52 /* CallsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4EE27392D960071BA52 /* CallsViewModel.swift */; };
+ C6F2D4F1273935860071BA52 /* CallExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4F0273935860071BA52 /* CallExtensions.swift */; };
+ C6F2D4F32739475C0071BA52 /* ActiveCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4F22739475C0071BA52 /* ActiveCallView.swift */; };
+ C6F2D4F72739861F0071BA52 /* RemotelyRecording.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4F62739861F0071BA52 /* RemotelyRecording.swift */; };
+ C6F2D4F9273A4CD70071BA52 /* SharedLayoutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4F8273A4CD60071BA52 /* SharedLayoutConstants.swift */; };
+ C6F2D4FD273A85A90071BA52 /* PausedCallOrConferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4FC273A85A90071BA52 /* PausedCallOrConferenceView.swift */; };
+ C6F2D4FF273AF01A0071BA52 /* ControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D4FE273AF01A0071BA52 /* ControlsView.swift */; };
+ C6F2D501273B0EFC0071BA52 /* VoipDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D500273B0EFC0071BA52 /* VoipDialog.swift */; };
+ C6F2D503273BAC030071BA52 /* ButtonWithStateBackgrounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D502273BAC030071BA52 /* ButtonWithStateBackgrounds.swift */; };
+ C6F2D505273BB3BB0071BA52 /* UIApplication+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6F2D504273BB3BB0071BA52 /* UIApplication+Extension.swift */; };
C90FAA7915AF54E6002091CB /* HistoryDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = C90FAA7715AF54E6002091CB /* HistoryDetailsView.m */; };
CF15F21E20E4F9A3008B1DE6 /* UIImageViewDeletable.m in Sources */ = {isa = PBXBuildFile; fileRef = CF15F21C20E4F9A3008B1DE6 /* UIImageViewDeletable.m */; };
CF15F21F20E4F9A3008B1DE6 /* UIImageViewDeletable.xib in Resources */ = {isa = PBXBuildFile; fileRef = CF15F21D20E4F9A3008B1DE6 /* UIImageViewDeletable.xib */; };
@@ -721,7 +832,6 @@
CFBD7A2A20E504AE007C5286 /* delete_img.png in Resources */ = {isa = PBXBuildFile; fileRef = CFBD7A2320E504AD007C5286 /* delete_img.png */; };
D306459E1611EC2A00BB571E /* UILoadingImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = D306459D1611EC2900BB571E /* UILoadingImageView.m */; };
D3128FE115AABC7E00A2147A /* ContactDetailsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3128FDF15AABC7E00A2147A /* ContactDetailsView.m */; };
- D31AAF5E159B3919002C6B02 /* CallPausedTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = D31AAF5D159B3919002C6B02 /* CallPausedTableView.m */; };
D31B4B21159876C0002E6C72 /* UICompositeView.m in Sources */ = {isa = PBXBuildFile; fileRef = D31B4B1F159876C0002E6C72 /* UICompositeView.m */; };
D31C9C98158A1CDF00756B45 /* UIHistoryCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D31C9C97158A1CDE00756B45 /* UIHistoryCell.m */; };
D326483815887D5200930C67 /* OrderedDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = D326483715887D5200930C67 /* OrderedDictionary.m */; };
@@ -736,7 +846,6 @@
D35860D615B549B500513429 /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = D35860D515B549B500513429 /* Utils.m */; };
D35E7597159460580066B1C1 /* ChatsListView.m in Sources */ = {isa = PBXBuildFile; fileRef = D35E7595159460560066B1C1 /* ChatsListView.m */; };
D35E759F159460B70066B1C1 /* SettingsView.m in Sources */ = {isa = PBXBuildFile; fileRef = D35E759D159460B50066B1C1 /* SettingsView.m */; };
- D36FB2D51589EF7C0036F6F2 /* UIPauseButton.m in Sources */ = {isa = PBXBuildFile; fileRef = D36FB2D41589EF7C0036F6F2 /* UIPauseButton.m */; };
D378AB2A15DCDB4A0098505D /* ImagePickerView.m in Sources */ = {isa = PBXBuildFile; fileRef = D378AB2915DCDB490098505D /* ImagePickerView.m */; };
D37C639B15AADEF6009D0BAC /* ContactDetailsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = D37C639A15AADEF5009D0BAC /* ContactDetailsTableView.m */; };
D37DC6C11594AE1800B2A5EB /* LinphoneCoreSettingsStore.m in Sources */ = {isa = PBXBuildFile; fileRef = D37DC6C01594AE1800B2A5EB /* LinphoneCoreSettingsStore.m */; };
@@ -769,10 +878,8 @@
D38187C115FE345B00C3EDCA /* DialerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D38187C415FE345B00C3EDCA /* DialerView.xib */; };
D38187CD15FE346700C3EDCA /* HistoryDetailsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D38187D015FE346700C3EDCA /* HistoryDetailsView.xib */; };
D38187D115FE346B00C3EDCA /* HistoryListView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D38187D415FE346B00C3EDCA /* HistoryListView.xib */; };
- D38187D915FE347700C3EDCA /* CallIncomingView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D38187DC15FE347700C3EDCA /* CallIncomingView.xib */; };
D38187DD15FE348A00C3EDCA /* AssistantView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D38187E015FE348A00C3EDCA /* AssistantView.xib */; };
D38187F815FE355D00C3EDCA /* TabBarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D38187FB15FE355D00C3EDCA /* TabBarView.xib */; };
- D381881915FE3FCA00C3EDCA /* CallView.xib in Resources */ = {isa = PBXBuildFile; fileRef = D381881C15FE3FCA00C3EDCA /* CallView.xib */; };
D3A55FBC15877E5E003FD403 /* UIContactCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D3A55FBB15877E5E003FD403 /* UIContactCell.m */; };
D3A8BB7015A6C7D500F96BE5 /* UIChatBubbleTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D3A8BB6F15A6C7D500F96BE5 /* UIChatBubbleTextCell.m */; };
D3C6526715AC1A8F0092A874 /* UIContactDetailsCell.m in Sources */ = {isa = PBXBuildFile; fileRef = D3C6526615AC1A8F0092A874 /* UIContactDetailsCell.m */; };
@@ -783,11 +890,10 @@
D3ED3E871586291E006C0DE4 /* TabBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3ED3E851586291B006C0DE4 /* TabBarView.m */; };
D3ED3EA71587334E006C0DE4 /* HistoryListTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3ED3EA51587334C006C0DE4 /* HistoryListTableView.m */; };
D3ED3EB81587392C006C0DE4 /* HistoryListView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3ED3EB615873929006C0DE4 /* HistoryListView.m */; };
- D3F26BF115986B73005F9CAB /* CallIncomingView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3F26BEF15986B71005F9CAB /* CallIncomingView.m */; };
D3F795D615A582810077328B /* ChatConversationView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3F795D415A582800077328B /* ChatConversationView.m */; };
D3F7998115BD32370018C273 /* TPMultiLayoutViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D3F7998015BD32370018C273 /* TPMultiLayoutViewController.m */; };
- D3F83EEC1582021700336684 /* CallView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3F83EEA1582021700336684 /* CallView.m */; };
D3F83F8E15822ABE00336684 /* PhoneMainView.m in Sources */ = {isa = PBXBuildFile; fileRef = D3F83F8D15822ABD00336684 /* PhoneMainView.m */; };
+ E42B8736B8196388DDF2771A /* Pods_msgNotificationService.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 72B39681098B0AF5B59B3A61 /* Pods_msgNotificationService.framework */; };
EA0007A62356008F003CC6BF /* msgNotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = EA5F25D9232BD3E200475F2E /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
EA3650DB2330D2E30001148A /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5F25DB232BD3E200475F2E /* NotificationService.swift */; };
EA88A405242A6216007FEC61 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 63AADBC41B6A0FF200AA16FD /* Localizable.strings */; };
@@ -859,8 +965,10 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 046DEFE77AD0675DA9932C4C /* Pods-liblinphoneTesterTests.distributionadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-liblinphoneTesterTests.distributionadhoc.xcconfig"; path = "Pods/Target Support Files/Pods-liblinphoneTesterTests/Pods-liblinphoneTesterTests.distributionadhoc.xcconfig"; sourceTree = ""; };
- 13B1BD646346F33BF57412F2 /* Pods-messagesNotification.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-messagesNotification.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-messagesNotification/Pods-messagesNotification.distribution.xcconfig"; sourceTree = ""; };
+ 02DBDD5A09F46796AEC2485B /* Pods-msgNotificationContent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.release.xcconfig"; path = "Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.release.xcconfig"; sourceTree = ""; };
+ 063D57B2E4769739DC5DA5C0 /* Pods-msgNotificationService.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationService.release.xcconfig"; path = "Target Support Files/Pods-msgNotificationService/Pods-msgNotificationService.release.xcconfig"; sourceTree = ""; };
+ 0DF941C97B75E7BA39A90600 /* Pods-linphone.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphone.release.xcconfig"; path = "Target Support Files/Pods-linphone/Pods-linphone.release.xcconfig"; sourceTree = ""; };
+ 1060E68152C51FCE5ACBF779 /* Pods-msgNotificationContent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.debug.xcconfig"; path = "Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.debug.xcconfig"; sourceTree = ""; };
152F22351B15E889008C0621 /* libxml2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libxml2.dylib; path = usr/lib/libxml2.dylib; sourceTree = SDKROOT; };
1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
1D3623240D0F684500981E51 /* LinphoneAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LinphoneAppDelegate.h; sourceTree = ""; };
@@ -869,8 +977,6 @@
1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
2214EB7812F846B1002A5394 /* UICallButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICallButton.h; sourceTree = ""; };
2214EB7912F846B1002A5394 /* UICallButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICallButton.m; sourceTree = ""; };
- 2214EB8712F84EBB002A5394 /* UIHangUpButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIHangUpButton.h; sourceTree = ""; };
- 2214EB8812F84EBB002A5394 /* UIHangUpButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIHangUpButton.m; sourceTree = ""; };
2214EBF112F86360002A5394 /* UIMutedMicroButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIMutedMicroButton.h; sourceTree = ""; };
2214EBF212F86360002A5394 /* UIMutedMicroButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIMutedMicroButton.m; sourceTree = ""; };
22276E8613C73D8A00210156 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
@@ -888,15 +994,12 @@
22744043106F33FC006EC466 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
22744056106F9BC9006EC466 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
228697C311AC29B800E9E0CA /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; };
- 22968A5D12F875C600588287 /* UISpeakerButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UISpeakerButton.h; sourceTree = ""; };
- 22968A5E12F875C600588287 /* UISpeakerButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UISpeakerButton.m; sourceTree = ""; };
- 22AA8AFF13D83F6300B30535 /* UICamSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICamSwitch.h; sourceTree = ""; };
- 22AA8B0013D83F6300B30535 /* UICamSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICamSwitch.m; sourceTree = ""; };
22B5EFA210CE50BD00777D97 /* AddressBookUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBookUI.framework; path = System/Library/Frameworks/AddressBookUI.framework; sourceTree = SDKROOT; };
22B5F03410CE6B2F00777D97 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; };
22C7555E1317E59C007BC101 /* UIBluetoothButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIBluetoothButton.h; sourceTree = ""; };
22C7555F1317E59C007BC101 /* UIBluetoothButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIBluetoothButton.m; sourceTree = ""; };
22D1B68012A3E0BE001AE361 /* libresolv.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libresolv.dylib; path = usr/lib/libresolv.dylib; sourceTree = SDKROOT; };
+ 22D3BF4C45858F6B07F4D2A4 /* Pods-linphone.distributionadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphone.distributionadhoc.xcconfig"; path = "Target Support Files/Pods-linphone/Pods-linphone.distributionadhoc.xcconfig"; sourceTree = ""; };
22E0A81C111C44E100B04932 /* AboutView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AboutView.m; sourceTree = ""; };
22E0A81D111C44E100B04932 /* AboutView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AboutView.h; sourceTree = ""; };
22F2508B107141E100AC9B3F /* DialerView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DialerView.h; sourceTree = ""; };
@@ -905,8 +1008,6 @@
244523AD1E8266CC0037A187 /* chat_error.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chat_error.png; sourceTree = ""; };
244523AE1E8266CC0037A187 /* chat_read.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chat_read.png; sourceTree = ""; };
244523BC1E8D3A6C0037A187 /* chat_unsecure.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chat_unsecure.png; sourceTree = ""; };
- 24585CBE78DA4F005C7F9D71 /* Pods-liblinphoneTester.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-liblinphoneTester.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-liblinphoneTester/Pods-liblinphoneTester.distribution.xcconfig"; sourceTree = ""; };
- 248C326F4AD75E654C1CB37A /* Pods-liblinphoneTesterTests.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-liblinphoneTesterTests.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-liblinphoneTesterTests/Pods-liblinphoneTesterTests.distribution.xcconfig"; sourceTree = ""; };
249660941FD6A359001D55AA /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
24A3459D1D95797700881A5C /* UIShopTableCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = UIShopTableCell.xib; sourceTree = ""; };
24A345A51D95798A00881A5C /* UIShopTableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIShopTableCell.m; sourceTree = ""; };
@@ -927,16 +1028,14 @@
288765FC0DF74451002DB57D /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
32CA4F630368D1EE00C91783 /* linphone_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = linphone_Prefix.pch; sourceTree = ""; };
- 34027665305514025971F85C /* Pods-msgNotificationContent.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.release.xcconfig"; path = "Pods/Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.release.xcconfig"; sourceTree = ""; };
340751961506459A00B89C47 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
- 340751E5150F38FC00B89C47 /* UIVideoButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIVideoButton.h; sourceTree = ""; };
- 340751E6150F38FD00B89C47 /* UIVideoButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIVideoButton.m; sourceTree = ""; };
+ 3411568BE5527EB500F75EBB /* Pods-msgNotificationService.distributionadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationService.distributionadhoc.xcconfig"; path = "Target Support Files/Pods-msgNotificationService/Pods-msgNotificationService.distributionadhoc.xcconfig"; sourceTree = ""; };
34216F3E1547EBCD00EA9777 /* VideoZoomHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VideoZoomHandler.h; path = LinphoneUI/VideoZoomHandler.h; sourceTree = ""; };
34216F3F1547EBCD00EA9777 /* VideoZoomHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VideoZoomHandler.m; path = LinphoneUI/VideoZoomHandler.m; sourceTree = ""; };
344ABDEF14850AE9007420B6 /* libc++.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libc++.1.dylib"; path = "usr/lib/libc++.1.dylib"; sourceTree = SDKROOT; };
344ABDF014850AE9007420B6 /* libstdc++.6.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = "libstdc++.6.dylib"; path = "usr/lib/libstdc++.6.dylib"; sourceTree = SDKROOT; };
- 38A3AE51B9E09ABF29222E5F /* Pods-liblinphoneTester.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-liblinphoneTester.release.xcconfig"; path = "Pods/Target Support Files/Pods-liblinphoneTester/Pods-liblinphoneTester.release.xcconfig"; sourceTree = ""; };
- 38DF35D11A7C0F45E990C83A /* Pods-linphone.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphone.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-linphone/Pods-linphone.distribution.xcconfig"; sourceTree = ""; };
+ 507103607396F28FF4427108 /* Pods-msgNotificationContent.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.distribution.xcconfig"; path = "Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.distribution.xcconfig"; sourceTree = ""; };
+ 53432234870660E9876CBCA8 /* Pods-linphone.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphone.debug.xcconfig"; path = "Target Support Files/Pods-linphone/Pods-linphone.debug.xcconfig"; sourceTree = ""; };
570742571D5A0691004B9C84 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ShopView.xib; sourceTree = ""; };
5707425F1D5A09B8004B9C84 /* ShopView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShopView.m; sourceTree = ""; };
570742601D5A09B8004B9C84 /* ShopView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShopView.h; sourceTree = ""; };
@@ -956,6 +1055,7 @@
614C087723D1A35F00217F80 /* ProviderDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = ""; };
614C087923D1A37400217F80 /* CallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; };
614D09CD21E74D5400C43EDF /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
+ 6150F32455334A0A7B3D46C8 /* Pods-msgNotificationContent.distributionadhoc.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.distributionadhoc.xcconfig"; path = "Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.distributionadhoc.xcconfig"; sourceTree = ""; };
61586B7A217A16EE0038AC45 /* menu_about.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_about.png; sourceTree = ""; };
61586B82217A16FD0038AC45 /* menu_about@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_about@2x.png"; sourceTree = ""; };
61586B84217A17070038AC45 /* menu_assistant.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = menu_assistant.png; sourceTree = ""; };
@@ -1000,10 +1100,6 @@
6187B1B624B3271500D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AssistantLinkView.strings; sourceTree = ""; };
6187B1B724B3271600D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AssistantView.strings; sourceTree = ""; };
6187B1B824B3271600D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AssistantViewScreens.strings; sourceTree = ""; };
- 6187B1B924B3271700D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/CallIncomingView.strings; sourceTree = ""; };
- 6187B1BA24B3271700D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/CallOutgoingView.strings; sourceTree = ""; };
- 6187B1BB24B3271700D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/CallView.strings; sourceTree = ""; };
- 6187B1BC24B3271800D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = "hu.lproj/CallView~ipad.strings"; sourceTree = ""; };
6187B1BD24B3271800D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/ChatConversationCreateView.strings; sourceTree = ""; };
6187B1BE24B3271900D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/ChatConversationImdnView.strings; sourceTree = ""; };
6187B1BF24B3271900D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/ChatConversationInfoView.strings; sourceTree = ""; };
@@ -1018,8 +1114,6 @@
6187B1C824B3271D00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/HistoryDetailsView.strings; sourceTree = ""; };
6187B1C924B3271D00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/HistoryListView.strings; sourceTree = ""; };
6187B1CA24B3271E00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/ImageView.strings; sourceTree = ""; };
- 6187B1CB24B3271E00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/UICallConferenceCell.strings; sourceTree = ""; };
- 6187B1CC24B3271E00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/UICallPausedCell.strings; sourceTree = ""; };
6187B1CD24B3271F00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/UIChatBubblePhotoCell.strings; sourceTree = ""; };
6187B1CE24B3271F00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/UIChatBubbleTextCell.strings; sourceTree = ""; };
6187B1CF24B3271F00D580FB /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/UIChatCell.strings; sourceTree = ""; };
@@ -1519,14 +1613,8 @@
63423C091C4501D000D9A050 /* Contact.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Contact.m; sourceTree = ""; };
634610041B61330300548952 /* UILabel+Boldify.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UILabel+Boldify.h"; sourceTree = ""; };
634610051B61330300548952 /* UILabel+Boldify.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UILabel+Boldify.m"; sourceTree = ""; };
- 6346100D1B61409800548952 /* CallOutgoingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallOutgoingView.h; sourceTree = ""; };
- 6346100E1B61409800548952 /* CallOutgoingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallOutgoingView.m; sourceTree = ""; };
- 634610111B6140A500548952 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/CallOutgoingView.xib; sourceTree = ""; };
635173F71BA082A40095EB0A /* UIChatBubblePhotoCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIChatBubblePhotoCell.h; sourceTree = ""; };
635173F81BA082A40095EB0A /* UIChatBubblePhotoCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIChatBubblePhotoCell.m; sourceTree = ""; };
- 6352A5721BE0D4B800594C1C /* CallSideMenuView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallSideMenuView.h; sourceTree = ""; };
- 6352A5731BE0D4B800594C1C /* CallSideMenuView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallSideMenuView.m; sourceTree = ""; };
- 6352A5741BE0D4B800594C1C /* CallSideMenuView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallSideMenuView.xib; sourceTree = ""; };
635775231B6673EC00C8B704 /* HistoryDetailsTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HistoryDetailsTableView.h; sourceTree = ""; };
635775241B6673EC00C8B704 /* HistoryDetailsTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HistoryDetailsTableView.m; sourceTree = ""; };
636316D21A1DEBCB0009B839 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/AboutView.xib; sourceTree = ""; };
@@ -1543,7 +1631,6 @@
6381DA7B1C1AD5EA00DF3BBD /* UIBouncingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIBouncingView.h; sourceTree = ""; };
6381DA7C1C1AD5EA00DF3BBD /* UIBouncingView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIBouncingView.m; sourceTree = ""; };
638F1A611C2021B2004B8E02 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Base.lproj/DialerView~ipad.xib"; sourceTree = ""; };
- 638F1A871C2167C2004B8E02 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Base.lproj/CallView~ipad.xib"; sourceTree = ""; };
638F1A901C21993D004B8E02 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Base.lproj/UICompositeView~ipad.xib"; sourceTree = ""; };
639CEAFE1A1DF4D9004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/StatusBarView.xib; sourceTree = ""; };
639CEB011A1DF4E4004DE38F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIHistoryCell.xib; sourceTree = ""; };
@@ -1552,7 +1639,6 @@
639E9C7E1C0DB13D00019A75 /* UICheckBoxTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICheckBoxTableView.h; sourceTree = ""; };
639E9C7F1C0DB13D00019A75 /* UICheckBoxTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICheckBoxTableView.m; sourceTree = ""; };
639E9C941C0DB7BE00019A75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/FirstLoginView.xib; sourceTree = ""; };
- 639E9C9E1C0DB7DF00019A75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UICallPausedCell.xib; sourceTree = ""; };
639E9CA11C0DB7E500019A75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIChatBubblePhotoCell.xib; sourceTree = ""; };
639E9CA41C0DB7EA00019A75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIChatBubbleTextCell.xib; sourceTree = ""; };
639E9CA71C0DB7F200019A75 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIChatCreateCell.xib; sourceTree = ""; };
@@ -1586,8 +1672,6 @@
63B8D68D1BCBE65600C12B09 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ChatConversationCreateView.xib; sourceTree = ""; };
63B8D69F1BCBF43100C12B09 /* UIChatCreateCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIChatCreateCell.h; sourceTree = ""; };
63B8D6A01BCBF43100C12B09 /* UIChatCreateCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIChatCreateCell.m; sourceTree = ""; };
- 63BC49E01BA2CDFC004EC273 /* UICallPausedCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICallPausedCell.h; sourceTree = ""; };
- 63BC49E11BA2CDFC004EC273 /* UICallPausedCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICallPausedCell.m; sourceTree = ""; };
63BE7A761D75BDF6000990EF /* ShopTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShopTableView.h; sourceTree = ""; };
63BE7A771D75BDF6000990EF /* ShopTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShopTableView.m; sourceTree = ""; };
63C441C11BBC23ED0053DC5E /* UIAssistantTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIAssistantTextField.h; sourceTree = ""; };
@@ -1611,30 +1695,13 @@
63EEE40D1BBA9B250087D3AF /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
63F1DF421BCE618E00EDED90 /* UIAddressTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIAddressTextField.h; sourceTree = ""; };
63F1DF431BCE618E00EDED90 /* UIAddressTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIAddressTextField.m; sourceTree = ""; };
- 63F1DF491BCE983100EDED90 /* CallConferenceTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallConferenceTableView.h; sourceTree = ""; };
- 63F1DF4A1BCE983200EDED90 /* CallConferenceTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CallConferenceTableView.m; sourceTree = ""; };
- 63F1DF4C1BCE985F00EDED90 /* UICallConferenceCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UICallConferenceCell.h; sourceTree = ""; };
- 63F1DF4D1BCE985F00EDED90 /* UICallConferenceCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UICallConferenceCell.m; sourceTree = ""; };
- 63F1DF521BCE986A00EDED90 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UICallConferenceCell.xib; sourceTree = ""; };
63FB30331A680E73008CA393 /* UIRoundedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIRoundedImageView.h; sourceTree = ""; };
63FB30341A680E73008CA393 /* UIRoundedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIRoundedImageView.m; sourceTree = ""; };
- 65CEDD144CABFAA70A29AF27 /* Pods_linphone.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_linphone.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 68D9EC27FCECD5DE2E19CD3C /* Pods-liblinphoneTester.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-liblinphoneTester.debug.xcconfig"; path = "Pods/Target Support Files/Pods-liblinphoneTester/Pods-liblinphoneTester.debug.xcconfig"; sourceTree = ""; };
- 6E1BC45342F5201DABD7FE55 /* Pods_latestCallsWidget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_latestCallsWidget.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 6F30EA7BEA39DA427CE0754E /* Pods_msgNotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_msgNotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
70E542F213E147E3002BA2C0 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
70E542F413E147EB002BA2C0 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
- 7513CBF7F2BA0A9F99977C2B /* Pods_richNotifications.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_richNotifications.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 791017662FE117B9B12E8938 /* Pods-messagesNotification.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-messagesNotification.debug.xcconfig"; path = "Pods/Target Support Files/Pods-messagesNotification/Pods-messagesNotification.debug.xcconfig"; sourceTree = ""; };
- 799BA1104845EB01ACE764D8 /* Pods-linphone.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphone.debug.xcconfig"; path = "Pods/Target Support Files/Pods-linphone/Pods-linphone.debug.xcconfig"; sourceTree = ""; };
- 7D8CCFE176C634813E3A2593 /* Pods-liblinphoneTesterTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-liblinphoneTesterTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-liblinphoneTesterTests/Pods-liblinphoneTesterTests.debug.xcconfig"; sourceTree = ""; };
- 82E9DEDA2A78C6DBBD1A54DB /* Pods_msgNotificationContent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_msgNotificationContent.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 85FB19B6A8124D942C8471F1 /* Pods-linphoneTests.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphoneTests.distribution.xcconfig"; path = "Pods/Target Support Files/Pods-linphoneTests/Pods-linphoneTests.distribution.xcconfig"; sourceTree = ""; };
- 8B488C393394746F9D630789 /* Pods_latestChatroomsWidget.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_latestChatroomsWidget.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 8B4C43A28E90775F6FCA2CEE /* Pods-msgNotificationContent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-msgNotificationContent.debug.xcconfig"; path = "Pods/Target Support Files/Pods-msgNotificationContent/Pods-msgNotificationContent.debug.xcconfig"; sourceTree = ""; };
+ 72B39681098B0AF5B59B3A61 /* Pods_msgNotificationService.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_msgNotificationService.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 79B41078A602EFB886981917 /* Pods-linphone.distribution.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-linphone.distribution.xcconfig"; path = "Target Support Files/Pods-linphone/Pods-linphone.distribution.xcconfig"; sourceTree = ""; };
8C1A1F7C1FA331D40064BE00 /* libsoci_sqlite3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsoci_sqlite3.a; path = "liblinphone-sdk/apple-darwin/lib/libsoci_sqlite3.a"; sourceTree = ""; };
- 8C1B67051E671826001EA2FE /* AudioHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioHelper.m; sourceTree = ""; };
- 8C1B67081E6718BC001EA2FE /* AudioHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AudioHelper.h; path = Utils/AudioHelper.h; sourceTree = ""; };
8C23BCB71D82AAC3005F19BB /* linphone.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = linphone.entitlements; sourceTree = ""; };
8C2595DE1DEDCC8E007A6424 /* CallKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CallKit.framework; path = System/Library/Frameworks/CallKit.framework; sourceTree = SDKROOT; };
8C2A81931F87B7FF0012A66B /* chat_group_avatar@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "chat_group_avatar@2x.png"; sourceTree = ""; };
@@ -1671,10 +1738,6 @@
8CBD7BAD20B6B82F00E5DCC0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/UIChatCreateCollectionViewCell.xib; sourceTree = ""; };
8CBD7BB120B6B86900E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AssistantView.strings; sourceTree = ""; };
8CBD7BB220B6B86A00E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AssistantViewScreens.strings; sourceTree = ""; };
- 8CBD7BB320B6B86B00E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/CallIncomingView.strings; sourceTree = ""; };
- 8CBD7BB420B6B86B00E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/CallOutgoingView.strings; sourceTree = ""; };
- 8CBD7BB520B6B86C00E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/CallView.strings; sourceTree = ""; };
- 8CBD7BB620B6B86D00E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = "fr.lproj/CallView~ipad.strings"; sourceTree = ""; };
8CBD7BB720B6B86E00E5DCC0 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/ChatConversationInfoView.strings; sourceTree = "