Merge branch 'feature/call_features' into 'master'

Various features for testflight upload and version 1 testing

See merge request BC/private/linphone-iphone-6.0!17
This commit is contained in:
Quentin Arguillere 2024-01-15 10:13:06 +00:00
commit 9c020f94f9
40 changed files with 1502 additions and 896 deletions

36
GoogleService-Info.plist Normal file
View file

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>221368768663-0ufgu96cel0auk4v0me863lgm252b9n2.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.221368768663-0ufgu96cel0auk4v0me863lgm252b9n2</string>
<key>API_KEY</key>
<string>AIzaSyDJTtlRCM7IqdVUU2dSIYq2YIsTz6bqnkI</string>
<key>GCM_SENDER_ID</key>
<string>221368768663</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>org.linphone.phone</string>
<key>PROJECT_ID</key>
<string>linphone-iphone</string>
<key>STORAGE_BUCKET</key>
<string>linphone-iphone.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false/>
<key>IS_ANALYTICS_ENABLED</key>
<false/>
<key>IS_APPINVITE_ENABLED</key>
<true/>
<key>IS_GCM_ENABLED</key>
<true/>
<key>IS_SIGNIN_ENABLED</key>
<true/>
<key>GOOGLE_APP_ID</key>
<string>1:221368768663:ios:a2c822bc087b5a219431d2</string>
<key>DATABASE_URL</key>
<string>https://linphone-iphone.firebaseio.com</string>
</dict>
</plist>

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; };
662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69D82B25DE18007118BF /* TelecomManager.swift */; };
662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */; };
66C491F92B24D25B00CEA16D /* ConfigExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */; };
@ -97,6 +98,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
660D8A702B517D260092694D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
662B69D82B25DE18007118BF /* TelecomManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelecomManager.swift; sourceTree = "<group>"; };
662B69DA2B25DE25007118BF /* ProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = "<group>"; };
66C491F82B24D25A00CEA16D /* ConfigExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigExtension.swift; sourceTree = "<group>"; };
@ -249,6 +251,7 @@
D719ABAA2ABC67BF00B41C10 = {
isa = PBXGroup;
children = (
660D8A702B517D260092694D /* GoogleService-Info.plist */,
D719ABB52ABC67BF00B41C10 /* Linphone */,
D719ABB42ABC67BF00B41C10 /* Products */,
A31AF2AB8C6A3D7B7EA3B424 /* Pods */,
@ -600,6 +603,7 @@
D732A90D2B0376F500DB42BA /* linphonerc-factory in Resources */,
D783C77C2B1089B200622CC2 /* assistant_linphone_default_values in Resources */,
D70C93DE2AC2D0F60063CA3B /* Localizable.xcstrings in Resources */,
660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -835,16 +839,22 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 4;
DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
"DEBUG=1",
);
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Linphone/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "Share photos with your friends and customize avatars";
INFOPLIST_KEY_NSCameraUsageDescription = "Camera usage is required for video VOIP calls";
INFOPLIST_KEY_NSContactsUsageDescription = "Make calls with your friends";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Microphone usage is required for VOIP calls";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Share photos with your friends and customize avatars";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
@ -856,15 +866,17 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.3;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 6.0;
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
@ -879,16 +891,18 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 4;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Linphone/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "Share photos with your friends and customize avatars";
INFOPLIST_KEY_NSCameraUsageDescription = "Camera usage is required for video VOIP calls";
INFOPLIST_KEY_NSContactsUsageDescription = "Make calls with your friends";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Microphone usage is required for VOIP calls";
INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "Share photos with your friends and customize avatars";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
@ -900,15 +914,17 @@
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 13.3;
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 6.0;
OTHER_SWIFT_FLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = auto;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -1,6 +1,7 @@
{
"images" : [
{
"filename" : "1024.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "phone-list.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.1334 15.1013L15.8935 13.2014L15.8818 13.196C15.6617 13.1018 15.4216 13.0641 15.1832 13.0861C14.9448 13.1081 14.7157 13.1892 14.5165 13.322C14.493 13.3375 14.4705 13.3543 14.449 13.3724L12.2584 15.2399C10.8706 14.5658 9.43775 13.1438 8.76365 11.774L10.6339 9.55006C10.6519 9.52756 10.669 9.50506 10.6852 9.48076C10.8151 9.28213 10.894 9.05445 10.9147 8.81798C10.9355 8.5815 10.8974 8.34357 10.804 8.12535V8.11455L8.89865 3.86742C8.77512 3.58236 8.5627 3.34489 8.29311 3.19047C8.02353 3.03605 7.71123 2.97296 7.40284 3.01062C6.1833 3.1711 5.06388 3.77001 4.25366 4.69552C3.44343 5.62102 2.9978 6.80981 3.00001 8.03985C3.00001 15.1859 8.81405 20.9999 15.9601 20.9999C17.1901 21.0021 18.3789 20.5565 19.3044 19.7463C20.2299 18.9361 20.8289 17.8166 20.9893 16.5971C21.0271 16.2888 20.9641 15.9766 20.8098 15.707C20.6556 15.4375 20.4183 15.225 20.1334 15.1013ZM15.9601 19.5599C12.9058 19.5566 9.97757 18.3418 7.81786 16.1821C5.65814 14.0224 4.44335 11.0941 4.44002 8.03985C4.43663 7.16099 4.75327 6.31094 5.33079 5.64845C5.90831 4.98597 6.70723 4.55635 7.57834 4.43983C7.57799 4.44342 7.57799 4.44704 7.57834 4.45063L9.46835 8.68066L7.60804 10.9073C7.58916 10.929 7.57201 10.9522 7.55674 10.9766C7.4213 11.1844 7.34185 11.4237 7.32608 11.6713C7.31031 11.9188 7.35876 12.1663 7.46674 12.3896C8.28215 14.0573 9.96246 15.725 11.6482 16.5395C11.8731 16.6465 12.122 16.6933 12.3705 16.6753C12.6189 16.6573 12.8585 16.5752 13.0657 16.4369C13.0888 16.4213 13.111 16.4045 13.1323 16.3865L15.3202 14.5199L19.5502 16.4144C19.5502 16.4144 19.5574 16.4144 19.5601 16.4144C19.445 17.2868 19.016 18.0873 18.3534 18.6662C17.6908 19.2452 16.84 19.5629 15.9601 19.5599Z" fill="white"/>
<path d="M12.7 8.90039H20.5" stroke="white" stroke-linecap="round"/>
<path d="M12.7 6.19922H20.5" stroke="white" stroke-linecap="round"/>
<path d="M12.7 3.5H20.5" stroke="white" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "phone-transfer.svg",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.9888 17.1636C20.8216 18.4339 20.1977 19.6 19.2337 20.444C18.2696 21.288 17.0313 21.7522 15.75 21.7499C8.30626 21.7499 2.25001 15.6936 2.25001 8.24986C2.24771 6.96857 2.7119 5.73025 3.55588 4.7662C4.39986 3.80214 5.56592 3.17827 6.83626 3.01111C7.15739 2.97181 7.48261 3.03739 7.76342 3.19807C8.04422 3.35875 8.26555 3.60591 8.39438 3.90267L10.3744 8.32392V8.33517C10.4729 8.56247 10.5136 8.81063 10.4928 9.05749C10.472 9.30435 10.3904 9.54222 10.2553 9.74986C10.2384 9.77517 10.2206 9.79861 10.2019 9.82205L8.25001 12.1358C8.9522 13.5627 10.4447 15.042 11.8903 15.7461L14.1722 13.8045C14.1946 13.7857 14.2181 13.7682 14.2425 13.752C14.4497 13.6131 14.6885 13.5283 14.9369 13.5054C15.1853 13.4825 15.4355 13.5221 15.6647 13.6208L15.6769 13.6264L20.0934 15.6055C20.3909 15.7337 20.6389 15.9548 20.8003 16.2356C20.9616 16.5165 21.0278 16.842 20.9888 17.1636ZM19.5 16.9761C19.5 16.9761 19.4934 16.9761 19.4897 16.9761L15.0834 15.0017L12.8006 16.9442C12.7785 16.963 12.7553 16.9805 12.7313 16.9967C12.5154 17.1407 12.2659 17.2263 12.0071 17.245C11.7483 17.2638 11.489 17.215 11.2547 17.1036C9.49876 16.2552 7.74845 14.518 6.89907 12.7808C6.7866 12.5482 6.73613 12.2904 6.75255 12.0325C6.76898 11.7747 6.85174 11.5254 6.99282 11.3089C7.00872 11.2835 7.02659 11.2594 7.04626 11.2367L9.00001 8.92017L7.03126 4.51392C7.03089 4.51018 7.03089 4.50641 7.03126 4.50267C6.12212 4.62126 5.28739 5.0672 4.68339 5.75697C4.0794 6.44673 3.74755 7.33302 3.75001 8.24986C3.75348 11.4314 5.01888 14.4816 7.26856 16.7313C9.51825 18.981 12.5685 20.2464 15.75 20.2499C16.6663 20.253 17.5523 19.9223 18.2425 19.3196C18.9327 18.7169 19.3797 17.8835 19.5 16.9752V16.9761Z" fill="white"/>
<path d="M11.5076 5.75317C11.5075 5.6718 11.5235 5.59121 11.5546 5.51602C11.5858 5.44083 11.6314 5.37252 11.6889 5.31498C11.7465 5.25744 11.8148 5.2118 11.89 5.18068C11.9652 5.14956 12.0458 5.13357 12.1271 5.13361L15.8866 5.13416L14.7543 4.00176C14.6381 3.88563 14.5729 3.72813 14.5729 3.5639C14.5729 3.39968 14.6381 3.24218 14.7543 3.12605C14.8704 3.00993 15.0279 2.94469 15.1921 2.94469C15.3563 2.94469 15.5138 3.00993 15.63 3.12605L17.8192 5.31532C17.9354 5.43145 18.0006 5.58895 18.0006 5.75317C18.0006 5.9174 17.9354 6.0749 17.8192 6.19103L15.63 8.3803C15.5138 8.49642 15.3563 8.56166 15.1921 8.56166C15.0279 8.56166 14.8704 8.49642 14.7543 8.3803C14.6381 8.26417 14.5729 8.10667 14.5729 7.94244C14.5729 7.77822 14.6381 7.62071 14.7543 7.50459L15.8866 6.37219L12.1271 6.37274C12.0458 6.37278 11.9652 6.35679 11.89 6.32567C11.8148 6.29455 11.7465 6.24891 11.6889 6.19137C11.6314 6.13383 11.5858 6.06551 11.5546 5.99032C11.5235 5.91513 11.5075 5.83455 11.5076 5.75317Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -38,7 +38,7 @@ final class CoreContext: ObservableObject {
private var mCore: Core!
private var mIterateSuscription: AnyCancellable?
private var mCoreSuscriptions = Set<AnyCancellable?>()
private init() {
do {
try initialiseCore()
@ -68,17 +68,17 @@ final class CoreContext: ObservableObject {
Factory.Instance.logCollectionPath = configDir
Factory.Instance.enableLogCollection(state: LogCollectionState.Enabled)
let url = NSURL(fileURLWithPath: configDir)
if let pathComponent = url.appendingPathComponent("linphonerc") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: filePath) {
let path = Bundle.main.path(forResource: "linphonerc-default", ofType: nil)
if path != nil {
try? FileManager.default.copyItem(at: NSURL(fileURLWithPath: path!) as URL, to: pathComponent)
}
}
}
let url = NSURL(fileURLWithPath: configDir)
if let pathComponent = url.appendingPathComponent("linphonerc") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: filePath) {
let path = Bundle.main.path(forResource: "linphonerc-default", ofType: nil)
if path != nil {
try? FileManager.default.copyItem(at: NSURL(fileURLWithPath: path!) as URL, to: pathComponent)
}
}
}
let config = try? Factory.Instance.createConfigWithFactory(
path: "\(configDir)/linphonerc",
@ -87,11 +87,13 @@ final class CoreContext: ObservableObject {
if config != nil {
self.mCore = try? Factory.Instance.createCoreWithConfig(config: config!, systemContext: nil)
}
self.mCore.autoIterateEnabled = false
self.mCore.callkitEnabled = true
self.mCore.pushNotificationEnabled = true
self.mCore.setUserAgent(name: "Linphone iOS 6.0 Beta (\(UIDevice.current.localizedModel)) - Linphone SDK : \(self.coreVersion)", version: "6.0")
self.mCoreSuscriptions.insert(self.mCore.publisher?.onGlobalStateChanged?.postOnMainQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
if cbVal.state == GlobalState.On {
self.defaultAccount = self.mCore.defaultAccount
@ -102,15 +104,25 @@ final class CoreContext: ObservableObject {
}
})
self.mCoreSuscriptions.insert(self.mCore.publisher?.onGlobalStateChanged?.postOnCoreQueue { (cbVal: (core: Core, state: GlobalState, message: String)) in
if cbVal.state == GlobalState.On {
#if DEBUG
let pushEnvironment = ".dev"
#else
let pushEnvironment = ""
#endif
for account in cbVal.core.accountList where account.params?.pushNotificationConfig?.provider != ("apns" + pushEnvironment) {
let newParams = account.params?.clone()
Log.info("Account \(String(describing: newParams?.identityAddress?.asStringUriOnly())) - updating apple push provider from \(String(describing: newParams?.pushNotificationConfig?.provider)) to apns\(pushEnvironment)")
newParams?.pushNotificationConfig?.provider = "apns" + pushEnvironment
account.params = newParams
}
}
})
self.mCore.videoCaptureEnabled = true
self.mCore.videoDisplayEnabled = true
self.mCore.recordAwareEnabled = true
let videoActivationPolicy = self.mCore.videoActivationPolicy!
videoActivationPolicy.automaticallyAccept = true
self.mCore.videoActivationPolicy! = videoActivationPolicy
try? self.mCore.start()
// Create a Core listener to listen for the callback we need
// In this case, we want to know about the account registration status
@ -118,11 +130,14 @@ final class CoreContext: ObservableObject {
NSLog("New configuration state is \(cbVal.status) = \(cbVal.message)\n")
if cbVal.status == Config.ConfiguringState.Successful {
ToastViewModel.shared.toastMessage = "Successful"
ToastViewModel.shared.displayToast.toggle()
} else {
ToastViewModel.shared.toastMessage = "Failed"
ToastViewModel.shared.displayToast.toggle()
}
ToastViewModel.shared.displayToast = true
}
/*
else {
ToastViewModel.shared.toastMessage = "Failed"
ToastViewModel.shared.displayToast = true
}
*/
})
self.mCoreSuscriptions.insert(self.mCore.publisher?.onAccountRegistrationStateChanged?.postOnMainQueue { (cbVal: (core: Core, account: Account, state: RegistrationState, message: String)) in
@ -140,7 +155,7 @@ final class CoreContext: ObservableObject {
self.loggingInProgress = true
} else {
ToastViewModel.shared.toastMessage = "Registration failed"
ToastViewModel.shared.displayToast.toggle()
ToastViewModel.shared.displayToast = true
self.loggingInProgress = false
self.loggedIn = false
}
@ -171,9 +186,11 @@ final class CoreContext: ObservableObject {
cbValue.info,
forPasteboardType: UTType.plainText.identifier
)
ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard"
ToastViewModel.shared.displayToast.toggle()
DispatchQueue.main.async {
ToastViewModel.shared.toastMessage = "Success_send_logs"
ToastViewModel.shared.displayToast = true
}
}
})
@ -184,6 +201,7 @@ final class CoreContext: ObservableObject {
self.mCore.iterate()
}
try? self.mCore.start()
}
}

View file

@ -2,10 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSCameraUsageDescription</key>
<string>Camera usage is required for video VOIP calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone usage is required for VOIP calls</string>
<key>ITSAppUsesNonExemptEncryption</key>
<true/>
<key>ITSEncryptionExportComplianceCode</key>
<string>b5cb085f-772a-4a4f-8c77-5d1332b1f93f</string>
<key>UIAppFonts</key>
<array>
<string>NotoSans-Light.ttf</string>

View file

@ -18,6 +18,9 @@
*/
import SwiftUI
#if USE_CRASHLYTICS
import Firebase
#endif
@main
struct LinphoneApp: App {
@ -30,6 +33,13 @@ struct LinphoneApp: App {
@State private var historyViewModel: HistoryViewModel?
@State private var historyListViewModel: HistoryListViewModel?
@State private var startCallViewModel: StartCallViewModel?
@State private var callViewModel: CallViewModel?
init() {
#if USE_CRASHLYTICS
FirebaseApp.configure()
#endif
}
var body: some Scene {
WindowGroup {
@ -37,19 +47,27 @@ struct LinphoneApp: App {
if !sharedMainViewModel.welcomeViewDisplayed {
WelcomeView()
} else if coreContext.defaultAccount == nil || sharedMainViewModel.displayProfileMode {
AssistantView()
ZStack {
AssistantView()
ToastView()
.zIndex(3)
}
} else if coreContext.defaultAccount != nil
&& coreContext.loggedIn
&& contactViewModel != nil
&& editContactViewModel != nil
&& historyViewModel != nil
&& historyListViewModel != nil
&& startCallViewModel != nil {
&& startCallViewModel != nil
&& callViewModel != nil {
ContentView(
contactViewModel: contactViewModel!,
editContactViewModel: editContactViewModel!,
historyViewModel: historyViewModel!,
historyListViewModel: historyListViewModel!,
startCallViewModel: startCallViewModel!
startCallViewModel: startCallViewModel!,
callViewModel: callViewModel!
)
} else {
SplashScreen()
@ -62,6 +80,7 @@ struct LinphoneApp: App {
historyViewModel = HistoryViewModel()
historyListViewModel = HistoryListViewModel()
startCallViewModel = StartCallViewModel()
callViewModel = CallViewModel()
}
}
}

View file

@ -107,6 +107,9 @@
},
"+" : {
},
"|" : {
},
"0" : {
@ -268,6 +271,9 @@
},
"Deny all" : {
},
"Dialer" : {
},
"Display Name" : {
@ -365,7 +371,10 @@
"Log out" : {
},
"Logout" : {
"Logs cleared" : {
},
"Logs URL copied into clipboard" : {
},
"Message" : {
@ -376,9 +385,6 @@
},
"Missed call" : {
},
"My Profile" : {
},
"New call" : {
@ -415,9 +421,6 @@
},
"Outgoing Call" : {
},
"Participants" : {
},
"password" : {
"extractionState" : "manual",
@ -438,6 +441,12 @@
},
"Pause" : {
},
"Paused" : {
},
"Paused by remote" : {
},
"Personnalize your profil mode" : {
@ -474,9 +483,6 @@
},
"Scan QR code" : {
},
"Screen share" : {
},
"Search contact or history call" : {
@ -540,6 +546,9 @@
},
"to Linphone" : {
},
"Transfer" : {
},
"Transport" : {

View file

@ -31,6 +31,8 @@ ec_calibrator_cool_tones=1
[video]
auto_resize_preview_to_keep_ratio=1
max_conference_size=vga
automatically_accept=1
automatically_initiate=0
[misc]
enable_basic_to_client_group_chat_room_migration=0

View file

@ -1,21 +1,21 @@
/*
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
* Copyright (c) 2010-2020 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// swiftlint:disable line_length
import Foundation
@ -56,19 +56,19 @@ class CallInfo {
}
/*
* A delegate to support callkit.
*/
* A delegate to support callkit.
*/
class ProviderDelegate: NSObject {
let provider: CXProvider
var uuids: [String: UUID] = [:]
var callInfos: [UUID: CallInfo] = [:]
override init() {
provider = CXProvider(configuration: ProviderDelegate.providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
get {
let providerConfiguration = CXProviderConfiguration()
@ -97,18 +97,18 @@ class ProviderDelegate: NSObject {
let callId = callInfo?.callId ?? ""
/*
if (ConfigManager.instance().config?.hasEntry(section: "app", key: "max_calls") == 1) { // moved from misc to app section intentionally upon app start or remote configuration
if let maxCalls = ConfigManager.instance().config?.getInt(section: "app",key: "max_calls",defaultValue: 10), Core.get().callsNb > maxCalls {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: declining call, as max calls (\(maxCalls)) reached call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)]")
decline(uuid: uuid)
CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
try? call?.decline(reason: .Busy)
}
return
}
}
*/
if (ConfigManager.instance().config?.hasEntry(section: "app", key: "max_calls") == 1) { // moved from misc to app section intentionally upon app start or remote configuration
if let maxCalls = ConfigManager.instance().config?.getInt(section: "app",key: "max_calls",defaultValue: 10), Core.get().callsNb > maxCalls {
Log.directLog(BCTBX_LOG_MESSAGE, text: "CallKit: declining call, as max calls (\(maxCalls)) reached call-id: [\(String(describing: callId))] and UUID: [\(uuid.description)]")
decline(uuid: uuid)
CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
try? call?.decline(reason: .Busy)
}
return
}
}
*/
Log.info("CallKit: report new incoming call with call-id: [\(callId)] and UUID: [\(uuid.description)]")
// TelecomManager.instance().setHeldOtherCalls(exceptCallid: callId ?? "") // ALREADY COMMENTED ON LINPHONE-IPHONE 5.2
@ -140,7 +140,7 @@ class ProviderDelegate: NSObject {
}
}
}
func updateCall(uuid: UUID, handle: String, hasVideo: Bool = false, displayName: String) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
@ -148,11 +148,11 @@ class ProviderDelegate: NSObject {
update.hasVideo = hasVideo
provider.reportCall(with: uuid, updated: update)
}
func reportOutgoingCallStartedConnecting(uuid: UUID) {
provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil)
}
func reportOutgoingCallConnected(uuid: UUID) {
provider.reportOutgoingCall(with: uuid, connectedAt: nil)
}
@ -164,7 +164,7 @@ class ProviderDelegate: NSObject {
func decline(uuid: UUID) {
provider.reportCall(with: uuid, endedAt: .init(), reason: .unanswered)
}
func endCallNotExist(uuid: UUID, timeout: DispatchTime) {
DispatchQueue.main.asyncAfter(deadline: timeout) {
CoreContext.shared.doOnCoreQueue(synchronous: true) { core in
@ -188,7 +188,7 @@ extension ProviderDelegate: CXProviderDelegate {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
// remove call infos first, otherwise CXEndCallAction will be called more than onece
if callId != nil {
uuids.removeValue(forKey: callId!)
@ -203,15 +203,17 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
let uuid = action.callUUID
let callInfo = callInfos[uuid]
let callId = callInfo?.callId ?? ""
DispatchQueue.main.async {
withAnimation {
TelecomManager.shared.callInProgress = true
if TelecomManager.shared.callInProgress == false {
DispatchQueue.main.async {
withAnimation {
TelecomManager.shared.callInProgress = true
}
}
}
CoreContext.shared.doOnCoreQueue { core in
@ -219,15 +221,17 @@ extension ProviderDelegate: CXProviderDelegate {
let call = core.getCallByCallid(callId: callId)
if UIApplication.shared.applicationState != .active {
TelecomManager.shared.backgroundContextCall = call
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
if #available(iOS 16.0, *) {
if call?.cameraEnabled == true {
call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported
DispatchQueue.main.async() {
if UIApplication.shared.applicationState != .active {
TelecomManager.shared.backgroundContextCall = call
TelecomManager.shared.backgroundContextCameraIsEnabled = call?.params?.videoEnabled == true || call?.callLog?.wasConference() == true
if #available(iOS 16.0, *) {
if call?.cameraEnabled == true {
call?.cameraEnabled = AVCaptureSession().isMultitaskingCameraAccessSupported
}
} else {
call?.cameraEnabled = false // Disable camera while app is not on foreground
}
} else {
call?.cameraEnabled = false // Disable camera while app is not on foreground
}
}
TelecomManager.shared.callkitAudioSessionActivated = false
@ -240,7 +244,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId ?? ""
@ -273,29 +277,29 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
} else {
if call?.conference != nil && core.callsNb > 1 {/*
try TelecomManager.shared.lc?.enterConference()
action.fulfill()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
*/} else {
try call!.resume()
// We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point
// where we actually start the media streams.
TelecomManager.shared.actionToFulFill = action
// HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !!
// When resuming a SIP call after a native call has ended remotely, didActivate: audioSession
// is never called.
// It looks like in this case, it is implicit.
// As a result we have to notify the Core that the AudioSession is active.
// The SpeakerBox demo application written by Apple exhibits this behavior.
// https://developer.apple.com/documentation/callkit/making_and_receiving_voip_calls_with_callkit
// We can clearly see there that startAudio() is called immediately in the CXSetHeldCallAction
// handler, while it is called from didActivate: audioSession otherwise.
// Callkit's design is not consistent, or its documentation imcomplete, wich is somewhat disapointing.
//
Log.info("Assuming AudioSession is active when executing a CXSetHeldCallAction with isOnHold=false.")
core.activateAudioSession(actived: true)
TelecomManager.shared.callkitAudioSessionActivated = true
}
try TelecomManager.shared.lc?.enterConference()
action.fulfill()
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self)
*/} else {
try call!.resume()
// We'll notify callkit that the action is fulfilled when receiving the 200Ok, which is the point
// where we actually start the media streams.
TelecomManager.shared.actionToFulFill = action
// HORRIBLE HACK HERE - PLEASE APPLE FIX THIS !!
// When resuming a SIP call after a native call has ended remotely, didActivate: audioSession
// is never called.
// It looks like in this case, it is implicit.
// As a result we have to notify the Core that the AudioSession is active.
// The SpeakerBox demo application written by Apple exhibits this behavior.
// https://developer.apple.com/documentation/callkit/making_and_receiving_voip_calls_with_callkit
// We can clearly see there that startAudio() is called immediately in the CXSetHeldCallAction
// handler, while it is called from didActivate: audioSession otherwise.
// Callkit's design is not consistent, or its documentation imcomplete, wich is somewhat disapointing.
//
Log.info("Assuming AudioSession is active when executing a CXSetHeldCallAction with isOnHold=false.")
core.activateAudioSession(actived: true)
TelecomManager.shared.callkitAudioSessionActivated = true
}
}
}
} catch {
@ -330,7 +334,7 @@ extension ProviderDelegate: CXProviderDelegate {
}
}
}
func provider(_ provider: CXProvider, perform action: CXSetGroupCallAction) {
CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: Call grouped callUUid : \(action.callUUID) with callUUID: \(String(describing: action.callUUIDToGroupWith)).")
@ -338,7 +342,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId
@ -348,7 +352,7 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {
let uuid = action.callUUID
let callId = callInfos[uuid]?.callId ?? ""
@ -366,18 +370,18 @@ extension ProviderDelegate: CXProviderDelegate {
action.fulfill()
}
}
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
let uuid = action.uuid
let callId = callInfos[uuid]?.callId
Log.error("CallKit: Call time out with call-id: \(String(describing: callId)) an UUID: \(uuid.description).")
action.fulfill()
}
func providerDidReset(_ provider: CXProvider) {
Log.info("CallKit: did reset.")
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: audio session activated.")
@ -385,7 +389,7 @@ extension ProviderDelegate: CXProviderDelegate {
TelecomManager.shared.callkitAudioSessionActivated = true
}
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
CoreContext.shared.doOnCoreQueue { core in
Log.info("CallKit: audio session deactivated.")

View file

@ -41,7 +41,11 @@ class TelecomManager: ObservableObject {
let callController: CXCallController // to support callkit
@Published var callInProgress: Bool = false
@Published var callStarted: Bool = false
@Published var callStarted: Bool = false
@Published var outgoingCallStarted: Bool = false
@Published var remoteVideo: Bool = false
@Published var isRecordingByRemote: Bool = false
@Published var isPausedByRemote: Bool = false
var actionToFulFill: CXCallAction?
var callkitAudioSessionActivated: Bool?
@ -116,34 +120,47 @@ class TelecomManager: ObservableObject {
}
}
func doCallWithCore(addr: Address) {
CoreContext.shared.doOnCoreQueue { core in
func doCallWithCore(addr: Address, isVideo: Bool) {
CoreContext.shared.doOnCoreQueue { core in
do {
try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: false, isConference: false)
try self.startCallCallKit(core: core, addr: addr, isSas: false, isVideo: isVideo, isConference: false)
} catch {
Log.error("[TelecomManager] unable to create address for a new outgoing call : \(addr) \(error) ")
}
}
}
}
}
private func makeRecordFilePath() -> String{
var filePath = "recording_"
let now = Date()
let dateFormat = DateFormatter()
dateFormat.dateFormat = "E-d-MMM-yyyy-HH-mm-ss"
let date = dateFormat.string(from: now)
filePath = filePath.appending("\(date).mkv")
let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
let writablePath = paths[0]
return writablePath.appending("/\(filePath)")
}
func doCall(core: Core, addr: Address, isSas: Bool, isVideo: Bool, isConference: Bool = false) throws {
// let displayName = FastAddressBook.displayName(for: addr.getCobject)
let lcallParams = try 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
}
if (displayName != nil) {
try addr.setDisplayname(newValue: displayName!)
}
if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
}
*/
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
}
if (displayName != nil) {
try addr.setDisplayname(newValue: displayName!)
}
if(ConfigManager.instance().lpConfigBoolForKey(key: "override_domain_with_default_one")) {
try addr.setDomain(newValue: ConfigManager.instance().lpConfigStringForKey(key: "domain", section: "assistant"))
}
*/
if nextCallIsTransfer {
let call = core.currentCall
@ -154,17 +171,20 @@ class TelecomManager: ObservableObject {
// let writablePath = AppManager.recordingFilePathFromCall(address: addr.username! )
// Log.directLog(BCTBX_LOG_DEBUG, text: "record file path: \(writablePath)")
// lcallParams.recordFile = writablePath
lcallParams.recordFile = makeRecordFilePath()
if isSas {
lcallParams.mediaEncryption = .ZRTP
}
if isConference {
/* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
lcallParams.videoEnabled = true
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
} else {
lcallParams.videoEnabled = false
}*/
/* if (ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! != .AudioOnly) {
lcallParams.videoEnabled = true
lcallParams.videoDirection = ConferenceWaitingRoomViewModel.sharedModel.isVideoEnabled.value == true ? .SendRecv : .RecvOnly
lcallParams.conferenceVideoLayout = ConferenceWaitingRoomViewModel.sharedModel.joinLayout.value! == .Grid ? .Grid : .ActiveSpeaker
} else {
lcallParams.videoEnabled = false
}*/
} else {
lcallParams.videoEnabled = isVideo
}
@ -184,9 +204,12 @@ class TelecomManager: ObservableObject {
}
DispatchQueue.main.async {
self.outgoingCallStarted = true
self.callStarted = true
withAnimation {
self.callInProgress = true
if self.callInProgress == false {
withAnimation {
self.callInProgress = true
}
}
}
}
@ -195,14 +218,16 @@ class TelecomManager: ObservableObject {
func acceptCall(core: Core, call: Call, hasVideo: Bool) {
do {
let callParams = try core.createCallParams(call: call)
callParams.recordFile = makeRecordFilePath()
callParams.videoEnabled = hasVideo
/*if (ConfigManager.instance().lpConfigBoolForKey(key: "edge_opt_preference")) {
let low_bandwidth = (AppManager.network() == .network_2g)
if (low_bandwidth) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
}
callParams.lowBandwidthEnabled = low_bandwidth
}*/
let low_bandwidth = (AppManager.network() == .network_2g)
if (low_bandwidth) {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Low bandwidth mode")
}
callParams.lowBandwidthEnabled = low_bandwidth
}*/
// We set the record file name here because we can't do it after the call is started.
// let address = call.callLog?.fromAddress
@ -211,10 +236,10 @@ class TelecomManager: ObservableObject {
// callParams.recordFile = writablePath
/*
if let chatView : ChatConversationView = PhoneMainView.instance().VIEW(ChatConversationView.compositeViewDescription()), chatView.isVoiceRecording {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.")
chatView.stopVoiceRecording()
}*/
if let chatView : ChatConversationView = PhoneMainView.instance().VIEW(ChatConversationView.compositeViewDescription()), chatView.isVoiceRecording {
Log.directLog(BCTBX_LOG_MESSAGE, text: "Voice recording in progress, stopping it befoce accepting the call.")
chatView.stopVoiceRecording()
}*/
if call.callLog?.wasConference() == true {
// Prevent incoming group call to start in audio only layout
@ -226,9 +251,9 @@ class TelecomManager: ObservableObject {
try call.acceptWithParams(params: callParams)
DispatchQueue.main.async {
self.callStarted = true
}
DispatchQueue.main.async {
self.callStarted = true
}
} catch {
Log.error("accept call failed \(error)")
}
@ -311,10 +336,52 @@ class TelecomManager: ObservableObject {
if cstate == .PushIncomingReceived {
displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling")
} else {
let video = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
if video {
Log.info("[Call] Remote video is activated")
DispatchQueue.main.async {
self.remoteVideo = (core.videoActivationPolicy?.automaticallyAccept ?? false) && (call.remoteParams?.videoEnabled ?? false)
if self.remoteVideo {
Log.info("[Call] Remote video is activated")
}
self.isRecordingByRemote = call.remoteParams?.isRecording ?? false
if self.isRecordingByRemote && ToastViewModel.shared.toastMessage.isEmpty {
var displayName = ""
let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress!)
if friend != nil && friend!.address != nil && friend!.address!.displayName != nil {
displayName = friend!.address!.displayName!
} else {
if call.remoteAddress!.displayName != nil {
displayName = call.remoteAddress!.displayName!
} else if call.remoteAddress!.username != nil {
displayName = call.remoteAddress!.username!
}
}
ToastViewModel.shared.toastMessage = "\(displayName) is recording"
ToastViewModel.shared.displayToast = true
Log.info("[Call] Call is recording by \(call.remoteAddress!.asStringUriOnly())")
}
if !self.isRecordingByRemote && ToastViewModel.shared.toastMessage.contains("is recording") {
withAnimation {
ToastViewModel.shared.toastMessage = ""
ToastViewModel.shared.displayToast = false
}
Log.info("[Call] Recording is stopped by \(call.remoteAddress!.asStringUriOnly())")
}
switch call.state {
case Call.State.PausedByRemote:
self.isPausedByRemote = true
default:
self.isPausedByRemote = false
}
}
if call.userData == nil {
@ -322,24 +389,28 @@ class TelecomManager: ObservableObject {
TelecomManager.setAppData(sCall: call, appData: appData)
}
/*
if let conference = call.conference, ConferenceViewModel.shared.conference.value == nil {
Log.info("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it")
ConferenceViewModel.shared.initConference(conference)
ConferenceViewModel.shared.configureConference(conference)
}
*/
if let conference = call.conference, ConferenceViewModel.shared.conference.value == nil {
Log.info("[Call] Found conference attached to call and no conference in dedicated view model, init & configure it")
ConferenceViewModel.shared.initConference(conference)
ConferenceViewModel.shared.configureConference(conference)
}
*/
switch cstate {
case .IncomingReceived:
let addr = call.remoteAddress
let displayName = incomingDisplayName(call: call)
#if targetEnvironment(simulator)
#if targetEnvironment(simulator)
DispatchQueue.main.async {
withAnimation {
TelecomManager.shared.callInProgress = true
self.outgoingCallStarted = false
self.callStarted = true
if self.callInProgress == false {
withAnimation {
self.callInProgress = true
}
}
}
#endif
#endif
if call.replacedCall != nil {
endCallKitReplacedCall = false
@ -351,22 +422,22 @@ class TelecomManager: ObservableObject {
providerDelegate.callInfos.updateValue(callInfo!, forKey: uuid!)
providerDelegate.uuids.removeValue(forKey: callId)
providerDelegate.uuids.updateValue(uuid!, forKey: callInfo!.callId)
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: remoteVideo, displayName: displayName)
}
} else if TelecomManager.callKitEnabled(core: core) {
/*
let isConference = isConferenceCall(call: call)
let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
if (isEarlyConference) {
CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
let uuid = providerDelegate.uuids["\(callId)"]
if (uuid != nil) {
displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))"
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
}
}
}
*/
let isEarlyConference = isConference && CallsViewModel.shared.currentCallData.value??.isConferenceCall.value != true // Conference info not be received yet.
if (isEarlyConference) {
CallsViewModel.shared.currentCallData.readCurrentAndObserve { _ in
let uuid = providerDelegate.uuids["\(callId)"]
if (uuid != nil) {
displayName = "\(VoipTexts.conference_incoming_title): \(CallsViewModel.shared.currentCallData.value??.remoteConferenceSubject.value ?? "") (\(CallsViewModel.shared.currentCallData.value??.conferenceParticipantsCountLabel.value ?? ""))"
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
}
}
}
*/
let uuid = providerDelegate.uuids["\(callId)"]
if call.replacedCall == nil {
TelecomManager.uuidReplacedCall = callId
@ -374,23 +445,28 @@ class TelecomManager: ObservableObject {
if uuid != nil {
// Tha app is now registered, updated the call already existed.
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: video, displayName: displayName)
providerDelegate.updateCall(uuid: uuid!, handle: addr!.asStringUriOnly(), hasVideo: remoteVideo, displayName: displayName)
} else {
displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: video, callId: callId, displayName: displayName)
displayIncomingCall(call: call, handle: addr!.asStringUriOnly(), hasVideo: remoteVideo, callId: callId, displayName: displayName)
}
} /* else if UIApplication.shared.applicationState != .active {
// not support callkit , use notif
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Incoming call", comment: "")
content.body = displayName
content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
content.categoryIdentifier = "call_cat"
content.userInfo = ["CallId": callId]
let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
} */
// not support callkit , use notif
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Incoming call", comment: "")
content.body = displayName
content.sound = UNNotificationSound.init(named: UNNotificationSoundName.init("notes_of_the_optimistic.caf"))
content.categoryIdentifier = "call_cat"
content.userInfo = ["CallId": callId]
let req = UNNotificationRequest.init(identifier: "call_request", content: content, trigger: nil)
UNUserNotificationCenter.current().add(req, withCompletionHandler: nil)
} */
case .StreamsRunning:
if TelecomManager.callKitEnabled(core: core) {
DispatchQueue.main.async {
self.outgoingCallStarted = false
}
let uuid = providerDelegate.uuids["\(callId)"]
if uuid != nil {
let callInfo = providerDelegate.callInfos[uuid!]
@ -404,10 +480,10 @@ class TelecomManager: ObservableObject {
}
/*
if speakerBeforePause {
speakerBeforePause = false
AudioRouteUtils.routeAudioToSpeaker(core: core)
}
if speakerBeforePause {
speakerBeforePause = false
AudioRouteUtils.routeAudioToSpeaker(core: core)
}
*/
actionToFulFill?.fulfill()
@ -432,12 +508,12 @@ class TelecomManager: ObservableObject {
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid!)
} else {
if false { /* isConferenceCall(call: call) {
let uuid = UUID()
let callInfo = CallInfo.newOutgoingCallInfo(addr: call.remoteAddress!, isSas: call.params?.mediaEncryption == .ZRTP, displayName: VoipTexts.conference_default_title, isVideo: call.params?.videoEnabled == true, isConference:true)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "")
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
Core.get().activateAudioSession(actived: true) */
let uuid = UUID()
let callInfo = CallInfo.newOutgoingCallInfo(addr: call.remoteAddress!, isSas: call.params?.mediaEncryption == .ZRTP, displayName: VoipTexts.conference_default_title, isVideo: call.params?.videoEnabled == true, isConference:true)
providerDelegate.callInfos.updateValue(callInfo, forKey: uuid)
providerDelegate.uuids.updateValue(uuid, forKey: "")
providerDelegate.reportOutgoingCallStartedConnecting(uuid: uuid)
Core.get().activateAudioSession(actived: true) */
} else {
referedToCall = callId
}
@ -446,19 +522,6 @@ class TelecomManager: ObservableObject {
case .End,
.Error:
DispatchQueue.main.async {
withAnimation {
self.callInProgress = false
self.callStarted = false
}
}
var displayName = "Unknown"
if call.dir == .Incoming {
displayName = incomingDisplayName(call: call)
} else { // if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
displayName = "TODOContactName"
}
UIDevice.current.isProximityMonitoringEnabled = false
if core.callsNb == 0 {
core.outputAudioDevice = core.defaultOutputAudioDevice
@ -468,18 +531,34 @@ class TelecomManager: ObservableObject {
// bluetoothEnabled = false
}
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil)
DispatchQueue.main.async {
withAnimation {
self.outgoingCallStarted = false
self.callInProgress = false
self.callStarted = false
}
// Deliver the notification.
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request) { (error: Error?) in
if error != nil {
Log.info("Error while adding notification request : \(error!.localizedDescription)")
var displayName = "Unknown"
if call.dir == .Incoming {
displayName = self.incomingDisplayName(call: call)
} else { // if let addr = call.remoteAddress, let contactName = FastAddressBook.displayName(for: addr.getCobject) {
displayName = "TODOContactName"
}
if UIApplication.shared.applicationState != .active && (callLog == nil || callLog?.status == .Missed || callLog?.status == .Aborted || callLog?.status == .EarlyAborted) {
// Configure the notification's payload.
let content = UNMutableNotificationContent()
content.title = NSString.localizedUserNotificationString(forKey: NSLocalizedString("Missed call", comment: ""), arguments: nil)
content.body = NSString.localizedUserNotificationString(forKey: displayName, arguments: nil)
// Deliver the notification.
let request = UNNotificationRequest(identifier: "call_request", content: content, trigger: nil) // Schedule the notification.
let center = UNUserNotificationCenter.current()
center.add(request) { (error: Error?) in
if error != nil {
Log.info("Error while adding notification request : \(error!.localizedDescription)")
}
}
}
}
@ -524,22 +603,6 @@ class TelecomManager: ObservableObject {
default:
break
}
// AudioRouteUtils.isBluetoothAvailable(core: core)
// AudioRouteUtils.isHeadsetAudioRouteAvailable(core: core)
// AudioRouteUtils.isBluetoothAudioRouteAvailable(core: core)
/*
let readyForRoutechange = callkitAudioSessionActivated == nil || (callkitAudioSessionActivated == true)
if readyForRoutechange && (cstate == .IncomingReceived || cstate == .OutgoingInit || cstate == .Connected || cstate == .StreamsRunning) {
if (call.currentParams?.videoEnabled ?? false) && AudioRouteUtils.isReceiverEnabled(core: core) && call.conference == nil {
AudioRouteUtils.routeAudioToSpeaker(core: core, call: call)
} else if AudioRouteUtils.isBluetoothAvailable(core: core) {
// Use bluetooth device by default if one is available
AudioRouteUtils.routeAudioToBluetooth(core: core, call: call)
}
}
*/
}
// post Notification kLinphoneCallUpdate
NotificationCenter.default.post(name: Notification.Name("LinphoneCallUpdate"), object: self, userInfo: [

View file

@ -63,6 +63,8 @@ struct LoginFragment: View {
TextField("username", text: $accountLoginViewModel.username)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
@ -90,6 +92,8 @@ struct LoginFragment: View {
} else {
TextField("password", text: $accountLoginViewModel.passwd)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.frame(height: 25)
.focused($isPasswordFocused)
}
@ -287,6 +291,8 @@ struct LoginFragment: View {
.background(.black.opacity(0.65))
}
}
.navigationTitle("")
.navigationBarHidden(true)
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -65,8 +65,11 @@ struct RegisterFragment: View {
}
}
}
.navigationTitle("")
.navigationBarHidden(true)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle("")
.navigationBarHidden(true)
}
}

View file

@ -81,6 +81,8 @@ struct ThirdPartySipAccountLoginFragment: View {
TextField("username", text: $accountLoginViewModel.username)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
@ -108,6 +110,8 @@ struct ThirdPartySipAccountLoginFragment: View {
} else {
TextField("password", text: $accountLoginViewModel.passwd)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.frame(height: 25)
.focused($isPasswordFocused)
}
@ -139,6 +143,8 @@ struct ThirdPartySipAccountLoginFragment: View {
TextField("sip.linphone.org", text: $accountLoginViewModel.domain)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
@ -158,6 +164,8 @@ struct ThirdPartySipAccountLoginFragment: View {
TextField("Display Name", text: $accountLoginViewModel.displayName)
.default_text_style(styleSize: 15)
.disableAutocorrection(true)
.autocapitalization(.none)
.disabled(coreContext.loggedIn)
.frame(height: 25)
.padding(.horizontal, 20)
@ -204,8 +212,6 @@ struct ThirdPartySipAccountLoginFragment: View {
Button(action: {
self.accountLoginViewModel.login()
accountLoginViewModel.domain = "sip.linphone.org"
accountLoginViewModel.transportType = "TLS"
}, label: {
Text(coreContext.loggedIn ? "Log out" : "assistant_account_login")
.default_text_style_white_600(styleSize: 20)

View file

@ -171,8 +171,11 @@ struct ThirdPartySipAccountWarningFragment: View {
.frame(minHeight: geometry.size.height)
}
}
.navigationTitle("")
.navigationBarHidden(true)
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationTitle("")
.navigationBarHidden(true)
}
}

View file

@ -45,7 +45,7 @@ class AccountLoginViewModel: ObservableObject {
core.loadConfigFromXml(xmlUri: assistantLinphone)
}
}
// Get the transport protocol to use.
// TLS is strongly recommended
// Only use UDP if you don't have the choice
@ -91,7 +91,12 @@ class AccountLoginViewModel: ObservableObject {
accountParams.registerEnabled = true
accountParams.pushNotificationAllowed = true
accountParams.remotePushNotificationAllowed = false
accountParams.pushNotificationConfig?.provider = "apns.dev"
#if DEBUG
let pushEnvironment = ".dev"
#else
let pushEnvironment = ""
#endif
accountParams.pushNotificationConfig?.provider = "apns" + pushEnvironment
// Now that our AccountParams is configured, we can create the Account object
let account = try core.createAccount(params: accountParams)
@ -106,6 +111,9 @@ class AccountLoginViewModel: ObservableObject {
self.coreContext.defaultAccount = account
}
self.domain = "sip.linphone.org"
self.transportType = "TLS"
} catch { NSLog(error.localizedDescription) }
}
}

File diff suppressed because it is too large Load diff

View file

@ -33,14 +33,16 @@ class CallViewModel: ObservableObject {
@Published var avatarModel: ContactAvatarModel?
@Published var micMutted: Bool = false
@Published var cameraDisplayed: Bool = false
@State var timeElapsed: Int = 0
@Published var isRecording: Bool = false
@Published var isRemoteRecording: Bool = false
@Published var isPaused: Bool = false
@Published var timeElapsed: Int = 0
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var currentCall: Call?
init() {
do {
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat, options: .allowBluetooth)
try AVAudioSession.sharedInstance().setActive(true)
@ -48,6 +50,10 @@ class CallViewModel: ObservableObject {
}
resetCallView()
}
func resetCallView() {
coreContext.doOnCoreQueue { core in
if core.currentCall != nil && core.currentCall!.remoteAddress != nil {
self.currentCall = core.currentCall
@ -69,7 +75,9 @@ class CallViewModel: ObservableObject {
//self.avatarModel = ???
self.micMutted = self.currentCall!.microphoneMuted
self.cameraDisplayed = self.currentCall!.cameraEnabled == true
self.isRecording = self.currentCall!.params!.isRecording
self.isPaused = self.isCallPaused()
self.timeElapsed = 0
}
}
}
@ -77,8 +85,9 @@ class CallViewModel: ObservableObject {
func terminateCall() {
withAnimation {
telecomManager.callInProgress = false
telecomManager.outgoingCallStarted = false
telecomManager.callStarted = false
telecomManager.callInProgress = false
}
coreContext.doOnCoreQueue { _ in
@ -92,6 +101,7 @@ class CallViewModel: ObservableObject {
func acceptCall() {
withAnimation {
telecomManager.outgoingCallStarted = false
telecomManager.callInProgress = true
telecomManager.callStarted = true
}
@ -128,8 +138,6 @@ class CallViewModel: ObservableObject {
"[CallViewModel] Updating call with video enabled set to \(params.videoEnabled)"
)
try self.currentCall!.update(params: params)
self.cameraDisplayed = self.currentCall!.cameraEnabled == true
} catch {
}
@ -164,10 +172,9 @@ class CallViewModel: ObservableObject {
} else {
Log.info("[CallViewModel] Starting call recording \(self.currentCall!.params!.isRecording)")
self.currentCall!.startRecording()
Log.info("[CallViewModel] Starting call recording \(self.currentCall!.params!.isRecording)")
}
//var recording = self.currentCall!.params!.isRecording
//isRecording.postValue(recording)
self.isRecording = self.currentCall!.params!.isRecording
}
}
}
@ -179,9 +186,11 @@ class CallViewModel: ObservableObject {
if self.isCallPaused() {
Log.info("[CallViewModel] Resuming call \(self.currentCall!.remoteAddress!.asStringUriOnly())")
try self.currentCall!.resume()
self.isPaused = false
} else {
Log.info("[CallViewModel] Pausing call \(self.currentCall!.remoteAddress!.asStringUriOnly())")
try self.currentCall!.pause()
self.isPaused = true
}
} catch _ {
@ -190,7 +199,7 @@ class CallViewModel: ObservableObject {
}
}
private func isCallPaused() -> Bool {
func isCallPaused() -> Bool {
var result = false
if self.currentCall != nil {
switch self.currentCall!.state {

View file

@ -41,8 +41,10 @@ struct ContactsView: View {
}
} label: {
Image("user-plus")
.renderingMode(.template)
.foregroundStyle(.white)
.padding()
.background(.white)
.background(Color.orangeMain500)
.clipShape(Circle())
.shadow(color: .black.opacity(0.2), radius: 4)

View file

@ -90,7 +90,7 @@ struct ContactInnerActionsFragment: View {
.onTapGesture {
withAnimation {
telecomManager.doCallWithCore(
addr: contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index]
addr: contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.addresses[index], isVideo: false
)
}
}
@ -272,7 +272,9 @@ struct ContactInnerActionsFragment: View {
Button {
if contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend != nil {
contactViewModel.objectWillChange.send()
contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.edit()
contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.starred.toggle()
contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.done()
}
} label: {
HStack {

View file

@ -86,19 +86,19 @@ struct ContactInnerFragment: View {
contactViewModel: contactViewModel,
isShowEditContactFragment: .constant(false),
isShowDismissPopup: $isShowDismissPopup)) {
Image("pencil-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.top, 2)
}
.simultaneousGesture(
TapGesture().onEnded {
editContactViewModel.selectedEditFriend = contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend
editContactViewModel.resetValues()
Image("pencil-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.top, 2)
}
)
.simultaneousGesture(
TapGesture().onEnded {
editContactViewModel.selectedEditFriend = contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend
editContactViewModel.resetValues()
}
)
}
}
.frame(maxWidth: .infinity)
@ -132,10 +132,10 @@ struct ContactInnerFragment: View {
.frame(maxWidth: .infinity)
.padding(.top, 10)
Text(contactAvatarModel.lastPresenceInfo)
Text(contactAvatarModel.lastPresenceInfo)
.foregroundStyle(contactAvatarModel.lastPresenceInfo == "Online"
? Color.greenSuccess500
: Color.orangeWarning600)
? Color.greenSuccess500
: Color.orangeWarning600)
.multilineTextAlignment(.center)
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity)
@ -151,7 +151,7 @@ struct ContactInnerFragment: View {
Spacer()
Button(action: {
telecomManager.doCallWithCore(addr: contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.address!)
telecomManager.doCallWithCore(addr: contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.address!, isVideo: false)
}, label: {
VStack {
HStack(alignment: .center) {
@ -180,7 +180,8 @@ struct ContactInnerFragment: View {
Image("chat-teardrop-text")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
//.foregroundStyle(Color.grayMain2c600)
.foregroundStyle(Color.grayMain2c300)
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
@ -200,7 +201,7 @@ struct ContactInnerFragment: View {
Spacer()
Button(action: {
telecomManager.doCallWithCore(addr: contactsManager.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.address!, isVideo: true)
}, label: {
VStack {
HStack(alignment: .center) {
@ -209,11 +210,6 @@ struct ContactInnerFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -229,7 +225,7 @@ struct ContactInnerFragment: View {
.padding(.top, 20)
.frame(maxWidth: .infinity)
.background(Color.gray100)
ContactInnerActionsFragment(
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,

View file

@ -58,7 +58,9 @@ struct ContactsListBottomSheet: View {
Spacer()
Button {
if contactViewModel.selectedFriend != nil {
contactViewModel.selectedFriend!.edit()
contactViewModel.selectedFriend!.starred.toggle()
contactViewModel.selectedFriend!.done()
}
MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)

View file

@ -98,5 +98,8 @@ struct ContactsListFragment: View {
}
#Preview {
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false), startCallFunc: {_ in })
ContactsListFragment(contactViewModel: ContactViewModel()
, contactsListViewModel: ContactsListViewModel()
, showingSheet: .constant(false)
, startCallFunc: {_ in })
}

View file

@ -44,7 +44,8 @@ class ContactAvatarModel: ObservableObject {
if friend!.consolidatedPresence == .Online || friend!.consolidatedPresence == .Busy {
if friend!.consolidatedPresence == .Online || friend!.presenceModel!.latestActivityTimestamp != -1 {
self.lastPresenceInfo = friend!.consolidatedPresence == .Online ? "Online" : getCallTime(startDate: friend!.presenceModel!.latestActivityTimestamp)
self.lastPresenceInfo = (friend!.consolidatedPresence == .Online) ?
"Online" : getCallTime(startDate: friend!.presenceModel!.latestActivityTimestamp)
} else {
self.lastPresenceInfo = "Away"
}
@ -68,7 +69,8 @@ class ContactAvatarModel: ObservableObject {
self.presenceStatus = cbValue.consolidatedPresence
if cbValue.consolidatedPresence == .Online || cbValue.consolidatedPresence == .Busy {
if cbValue.consolidatedPresence == .Online || cbValue.presenceModel!.latestActivityTimestamp != -1 {
self.lastPresenceInfo = cbValue.consolidatedPresence == .Online ? "Online" : self.getCallTime(startDate: cbValue.presenceModel!.latestActivityTimestamp)
self.lastPresenceInfo = cbValue.consolidatedPresence == .Online ?
"Online" : self.getCallTime(startDate: cbValue.presenceModel!.latestActivityTimestamp)
} else {
self.lastPresenceInfo = "Away"
}

View file

@ -18,6 +18,7 @@
*/
// swiftlint:disable type_body_length
// swiftlint:disable line_length
import SwiftUI
import linphonesw
@ -38,6 +39,7 @@ struct ContentView: View {
@ObservedObject var historyViewModel: HistoryViewModel
@ObservedObject var historyListViewModel: HistoryListViewModel
@ObservedObject var startCallViewModel: StartCallViewModel
@ObservedObject var callViewModel: CallViewModel
@State var index = 0
@State private var orientation = UIDevice.current.orientation
@ -149,6 +151,7 @@ struct ContentView: View {
Menu {
if index == 0 {
Button {
contactViewModel.indexDisplayedFriend = nil
isMenuOpen = false
magicSearch.allContact = true
MagicSearchSingleton.shared.searchForContacts(
@ -166,6 +169,7 @@ struct ContentView: View {
}
Button {
contactViewModel.indexDisplayedFriend = nil
isMenuOpen = false
magicSearch.allContact = false
MagicSearchSingleton.shared.searchForContacts(
@ -280,9 +284,8 @@ struct ContentView: View {
text = newValue
}
))
.default_text_style_white_700(styleSize: 15)
.default_text_style_700(styleSize: 15)
.padding(.all, 6)
.accentColor(.white)
.focused($focusedField)
.onAppear {
self.focusedField = true
@ -661,15 +664,16 @@ struct ContentView: View {
}
if telecomManager.callInProgress {
CallView(callViewModel: CallViewModel())
CallView(callViewModel: callViewModel)
.zIndex(3)
.transition(.scale.combined(with: .move(edge: .top)))
.onAppear {
callViewModel.resetCallView()
}
}
// if sharedMainViewModel.displayToast {
ToastView()
.zIndex(3)
// }
}
}
.overlay {
@ -693,12 +697,14 @@ struct ContentView: View {
.onChange(of: scenePhase) { newPhase in
if newPhase == .active {
coreContext.onForeground()
/*
if !isShowStartCallFragment {
contactsManager.fetchContacts()
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
historyListViewModel.computeCallLogsList()
}
}
*/
print("Active")
} else if newPhase == .inactive {
print("Inactive")
@ -722,7 +728,9 @@ struct ContentView: View {
editContactViewModel: EditContactViewModel(),
historyViewModel: HistoryViewModel(),
historyListViewModel: HistoryListViewModel(),
startCallViewModel: StartCallViewModel()
startCallViewModel: StartCallViewModel(),
callViewModel: CallViewModel()
)
}
// swiftlint:enable type_body_length
// swiftlint:enable line_length

View file

@ -43,19 +43,44 @@ struct SideMenu: View {
HStack {
List {
Text("My Profile").onTapGesture {
print("My Profile")
}
Text("Send logs").onTapGesture {
sendLogs()
}
Text("Clear logs").onTapGesture {
print("Clear logs")
Core.resetLogCollection()
}
Text("Logout").onTapGesture {
print("Logout")
}
/*
Text("My Profile")
.frame(height: 40)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.white)
.onTapGesture {
print("My Profile")
self.menuClose()
}
*/
Text("Send logs")
.frame(height: 40)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.white)
.onTapGesture {
print("Send logs")
sendLogs()
self.menuClose()
}
Text("Clear logs")
.frame(height: 40)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.white)
.onTapGesture {
print("Clear logs")
clearLogs()
self.menuClose()
}
/*
Text("Logout")
.frame(height: 40)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.white)
.onTapGesture {
print("Logout")
self.menuClose()
}
*/
}
.frame(width: self.width - safeAreaInsets.leading)
.background(Color.white)
@ -75,4 +100,14 @@ struct SideMenu: View {
core.uploadLogCollection()
}
}
func clearLogs() {
coreContext.doOnCoreQueue { core in
Core.resetLogCollection()
DispatchQueue.main.async {
ToastViewModel.shared.toastMessage = "Success_clear_logs"
ToastViewModel.shared.displayToast = true
}
}
}
}

View file

@ -48,12 +48,33 @@ struct ToastView: View {
.default_text_style(styleSize: 15)
.padding(8)
case "Success_clear_logs":
Text("Logs cleared")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_send_logs":
Text("Logs URL copied into clipboard")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Success_copied_into_clipboard":
Text("SIP address copied into clipboard")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case let str where str.contains("is recording"):
Text(toastViewModel.toastMessage)
.multilineTextAlignment(.center)
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
case "Failed":
Text("Invalid QR code!")
@ -93,19 +114,22 @@ struct ToastView: View {
.stroke(toastViewModel.toastMessage.contains("Success") ? Color.greenSuccess500 : Color.redDanger500, lineWidth: 1)
)
.onTapGesture {
withAnimation {
toastViewModel.toastMessage = ""
toastViewModel.displayToast = false
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
if !toastViewModel.toastMessage.contains("is recording") {
withAnimation {
toastViewModel.toastMessage = ""
toastViewModel.displayToast = false
}
}
}
.onAppear {
if !toastViewModel.toastMessage.contains("is recording") {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
toastViewModel.toastMessage = ""
toastViewModel.displayToast = false
}
}
} }
Spacer()
}

View file

@ -275,7 +275,7 @@ struct DialerBottomSheet: View {
if !startCallViewModel.searchField.isEmpty {
do {
let address = try Factory.Instance.createAddress(addr: String("sip:" + startCallViewModel.searchField + "@" + startCallViewModel.domain))
telecomManager.doCallWithCore(addr: address)
telecomManager.doCallWithCore(addr: address, isVideo: false)
} catch {
}

View file

@ -17,6 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// swiftlint:disable line_length
import SwiftUI
import UniformTypeIdentifiers
@ -30,15 +32,15 @@ struct HistoryContactFragment: View {
@ObservedObject var contactAvatarModel: ContactAvatarModel
@ObservedObject var historyViewModel: HistoryViewModel
@ObservedObject var historyListViewModel: HistoryListViewModel
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@ObservedObject var historyListViewModel: HistoryListViewModel
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@State var isMenuOpen = false
@Binding var isShowDeleteAllHistoryPopup: Bool
@Binding var isShowEditContactFragment: Bool
@Binding var indexPage: Int
@Binding var isShowEditContactFragment: Bool
@Binding var indexPage: Int
var body: some View {
NavigationView {
@ -70,25 +72,25 @@ struct HistoryContactFragment: View {
Spacer()
Menu {
let fromAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
let toAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
let fromAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
let toAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
Button {
isMenuOpen = false
if contactsManager.getFriendWithAddress(
address: historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
) != nil {
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
let friendIndex = contactsManager.lastSearch.firstIndex(
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall.asStringUriOnly()})})
if friendIndex != nil {
if contactsManager.getFriendWithAddress(
address: historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
) != nil {
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
let friendIndex = contactsManager.lastSearch.firstIndex(
where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall.asStringUriOnly()})})
if friendIndex != nil {
withAnimation {
historyViewModel.displayedCall = nil
@ -96,28 +98,28 @@ struct HistoryContactFragment: View {
contactViewModel.indexDisplayedFriend = friendIndex
}
}
} else {
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
withAnimation {
}
} else {
let addressCall = historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing
? historyViewModel.displayedCall!.toAddress!
: historyViewModel.displayedCall!.fromAddress!
withAnimation {
historyViewModel.displayedCall = nil
indexPage = 0
isShowEditContactFragment.toggle()
editContactViewModel.sipAddresses.removeAll()
editContactViewModel.sipAddresses.append(String(addressCall.asStringUriOnly().dropFirst(4)))
editContactViewModel.sipAddresses.removeAll()
editContactViewModel.sipAddresses.append(String(addressCall.asStringUriOnly().dropFirst(4)))
editContactViewModel.sipAddresses.append("")
}
}
}
}
} label: {
HStack {
Text(addressFriend != nil ? "See contact" : "Add to contacts")
Text(addressFriend != nil ? "See contact" : "Add to contacts")
Spacer()
Image(addressFriend != nil ? "user-circle" : "plus-circle")
Image(addressFriend != nil ? "user-circle" : "plus-circle")
.resizable()
.frame(width: 25, height: 25, alignment: .leading)
}
@ -125,18 +127,18 @@ struct HistoryContactFragment: View {
Button {
isMenuOpen = false
if historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing {
UIPasteboard.general.setValue(
historyViewModel.displayedCall!.toAddress!.asStringUriOnly().dropFirst(4),
forPasteboardType: UTType.plainText.identifier
)
} else {
UIPasteboard.general.setValue(
historyViewModel.displayedCall!.fromAddress!.asStringUriOnly().dropFirst(4),
forPasteboardType: UTType.plainText.identifier
)
}
if historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.dir == .Outgoing {
UIPasteboard.general.setValue(
historyViewModel.displayedCall!.toAddress!.asStringUriOnly().dropFirst(4),
forPasteboardType: UTType.plainText.identifier
)
} else {
UIPasteboard.general.setValue(
historyViewModel.displayedCall!.fromAddress!.asStringUriOnly().dropFirst(4),
forPasteboardType: UTType.plainText.identifier
)
}
ToastViewModel.shared.toastMessage = "Success_copied_into_clipboard"
ToastViewModel.shared.displayToast.toggle()
@ -192,8 +194,12 @@ struct HistoryContactFragment: View {
ScrollView {
VStack(spacing: 0) {
VStack(spacing: 0) {
if #unavailable(iOS 16.0) {
Rectangle()
.foregroundColor(Color.gray100)
.frame(height: 7)
}
VStack(spacing: 0) {
let fromAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.fromAddress!) : nil
let toAddressFriend = historyViewModel.displayedCall != nil ? contactsManager.getFriendWithAddress(address: historyViewModel.displayedCall!.toAddress!) : nil
let addressFriend = historyViewModel.displayedCall != nil ? (historyViewModel.displayedCall!.dir == .Incoming ? fromAddressFriend : toAddressFriend) : nil
@ -221,13 +227,13 @@ struct HistoryContactFragment: View {
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text("")
.multilineTextAlignment(.center)
@ -250,13 +256,13 @@ struct HistoryContactFragment: View {
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text("")
.multilineTextAlignment(.center)
@ -282,14 +288,14 @@ struct HistoryContactFragment: View {
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text("")
.multilineTextAlignment(.center)
.default_text_style_300(styleSize: 12)
@ -311,14 +317,14 @@ struct HistoryContactFragment: View {
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
Text("")
.multilineTextAlignment(.center)
.default_text_style_300(styleSize: 12)
@ -336,22 +342,22 @@ struct HistoryContactFragment: View {
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 10)
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
} else if historyViewModel.displayedCall!.fromAddress != nil {
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
}
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
Text(historyViewModel.displayedCall!.toAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
} else if historyViewModel.displayedCall!.fromAddress != nil {
Text(historyViewModel.displayedCall!.fromAddress!.asStringUriOnly())
.foregroundStyle(Color.grayMain2c700)
.multilineTextAlignment(.center)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity)
.padding(.top, 5)
}
Text(contactAvatarModel.lastPresenceInfo)
.foregroundStyle(contactAvatarModel.lastPresenceInfo == "Online"
@ -367,21 +373,22 @@ struct HistoryContactFragment: View {
.frame(minHeight: 150)
.frame(maxWidth: .infinity)
.padding(.top, 10)
.padding(.bottom, 2)
.background(Color.gray100)
HStack {
Spacer()
Button(action: {
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
telecomManager.doCallWithCore(
addr: historyViewModel.displayedCall!.toAddress!
)
} else if historyViewModel.displayedCall!.dir == .Incoming && historyViewModel.displayedCall!.fromAddress != nil {
telecomManager.doCallWithCore(
addr: historyViewModel.displayedCall!.fromAddress!
)
}
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
telecomManager.doCallWithCore(
addr: historyViewModel.displayedCall!.toAddress!, isVideo: false
)
} else if historyViewModel.displayedCall!.dir == .Incoming && historyViewModel.displayedCall!.fromAddress != nil {
telecomManager.doCallWithCore(
addr: historyViewModel.displayedCall!.fromAddress!, isVideo: false
)
}
}, label: {
VStack {
HStack(alignment: .center) {
@ -397,6 +404,7 @@ struct HistoryContactFragment: View {
Text("Appel")
.default_text_style(styleSize: 14)
.frame(minWidth: 80)
}
})
@ -410,7 +418,8 @@ struct HistoryContactFragment: View {
Image("chat-teardrop-text")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
//.foregroundStyle(Color.grayMain2c600)
.foregroundStyle(Color.grayMain2c300)
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
@ -424,13 +433,22 @@ struct HistoryContactFragment: View {
Text("Message")
.default_text_style(styleSize: 14)
.frame(minWidth: 80)
}
})
Spacer()
Button(action: {
if historyViewModel.displayedCall!.dir == .Outgoing && historyViewModel.displayedCall!.toAddress != nil {
telecomManager.doCallWithCore(
addr: historyViewModel.displayedCall!.toAddress!, isVideo: true
)
} else if historyViewModel.displayedCall!.dir == .Incoming && historyViewModel.displayedCall!.fromAddress != nil {
telecomManager.doCallWithCore(
addr: historyViewModel.displayedCall!.fromAddress!, isVideo: true
)
}
}, label: {
VStack {
HStack(alignment: .center) {
@ -439,11 +457,6 @@ struct HistoryContactFragment: View {
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -451,71 +464,75 @@ struct HistoryContactFragment: View {
Text("Video Call")
.default_text_style(styleSize: 14)
.frame(minWidth: 80)
}
})
Spacer()
}
.padding(.top, 20)
.padding(.bottom, 10)
.frame(maxWidth: .infinity)
.background(Color.gray100)
VStack(spacing: 0) {
let addressFriend = historyViewModel.displayedCall != nil
? (historyViewModel.displayedCall!.dir == .Incoming ? historyViewModel.displayedCall!.fromAddress!.asStringUriOnly()
: historyViewModel.displayedCall!.toAddress!.asStringUriOnly()) : nil
let callLogsFilter = historyListViewModel.callLogs.filter({ $0.dir == .Incoming
? $0.fromAddress!.asStringUriOnly() == addressFriend
: $0.toAddress!.asStringUriOnly() == addressFriend })
ForEach(0..<callLogsFilter.count, id: \.self) { index in
HStack {
VStack {
Image(historyListViewModel.getCallIconResId(callStatus: callLogsFilter[index].status, callDir: callLogsFilter[index].dir))
.resizable()
.frame(
width: historyListViewModel.getCallIconResId(
callStatus: callLogsFilter[index].status,
callDir: callLogsFilter[index].dir
).contains("rejected") ? 12 : 8,
height: historyListViewModel.getCallIconResId(
callStatus: callLogsFilter[index].status,
callDir: callLogsFilter[index].dir
).contains("rejected") ? 6 : 8)
.padding(.top, 5)
Spacer()
}
VStack {
Text(historyListViewModel.getCallText(
callStatus: callLogsFilter[index].status,
callDir: callLogsFilter[index].dir)
)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
Text(historyListViewModel.getCallTime(startDate: callLogsFilter[index].startDate))
.foregroundStyle(
callLogsFilter[index].status != .Success
? Color.redDanger500
: Color.grayMain2c600
)
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity, alignment: .leading)
}
VStack {
Spacer()
Text(callLogsFilter[index].duration.convertDurationToString())
.default_text_style_300(styleSize: 12)
Spacer()
}
}
.padding(.vertical, 15)
.padding(.horizontal, 20)
}
let addressFriend = historyViewModel.displayedCall != nil
? (historyViewModel.displayedCall!.dir == .Incoming ? historyViewModel.displayedCall!.fromAddress!.asStringUriOnly()
: historyViewModel.displayedCall!.toAddress!.asStringUriOnly()) : nil
let callLogsFilter = historyListViewModel.callLogs.filter({ $0.dir == .Incoming
? $0.fromAddress!.asStringUriOnly() == addressFriend
: $0.toAddress!.asStringUriOnly() == addressFriend })
ForEach(0..<callLogsFilter.count, id: \.self) { index in
HStack {
VStack {
Image(historyListViewModel.getCallIconResId(callStatus: callLogsFilter[index].status, callDir: callLogsFilter[index].dir))
.resizable()
.frame(
width: historyListViewModel.getCallIconResId(
callStatus: callLogsFilter[index].status,
callDir: callLogsFilter[index].dir
).contains("rejected") ? 12 : 8,
height: historyListViewModel.getCallIconResId(
callStatus: callLogsFilter[index].status,
callDir: callLogsFilter[index].dir
).contains("rejected") ? 6 : 8)
.padding(.top, 6)
Spacer()
}
VStack {
Text(historyListViewModel.getCallText(
callStatus: callLogsFilter[index].status,
callDir: callLogsFilter[index].dir)
)
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
Text(historyListViewModel.getCallTime(startDate: callLogsFilter[index].startDate))
.foregroundStyle(
callLogsFilter[index].status != .Success
? Color.redDanger500
: Color.grayMain2c600
)
.default_text_style_300(styleSize: 12)
.frame(maxWidth: .infinity, alignment: .leading)
}
VStack {
Spacer()
Text(callLogsFilter[index].duration.convertDurationToString())
.default_text_style_300(styleSize: 12)
Spacer()
}
}
.padding(.vertical, 15)
.padding(.horizontal, 20)
.frame(maxHeight: 65)
}
}
.background(.white)
.cornerRadius(15)
@ -524,6 +541,7 @@ struct HistoryContactFragment: View {
.frame(maxWidth: sharedMainViewModel.maxWidth)
}
.frame(maxWidth: .infinity)
.padding(.top, 2)
}
.background(Color.gray100)
}
@ -538,14 +556,16 @@ struct HistoryContactFragment: View {
}
#Preview {
HistoryContactFragment(
HistoryContactFragment(
contactAvatarModel: ContactAvatarModel(friend: nil, withPresence: false),
historyViewModel: HistoryViewModel(),
historyListViewModel: HistoryListViewModel(),
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
isShowDeleteAllHistoryPopup: .constant(false),
isShowEditContactFragment: .constant(false),
indexPage: .constant(1)
)
historyListViewModel: HistoryListViewModel(),
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
isShowDeleteAllHistoryPopup: .constant(false),
isShowEditContactFragment: .constant(false),
indexPage: .constant(1)
)
}
// swiftlint:enable line_length

View file

@ -17,6 +17,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// swiftlint:disable line_length
import SwiftUI
import linphonesw
@ -164,11 +166,11 @@ struct HistoryListFragment: View {
withAnimation {
if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil {
telecomManager.doCallWithCore(
addr: historyListViewModel.callLogs[index].toAddress!
addr: historyListViewModel.callLogs[index].toAddress!, isVideo: false
)
} else if historyListViewModel.callLogs[index].fromAddress != nil {
telecomManager.doCallWithCore(
addr: historyListViewModel.callLogs[index].fromAddress!
addr: historyListViewModel.callLogs[index].fromAddress!, isVideo: false
)
}
historyViewModel.displayedCall = nil
@ -211,9 +213,13 @@ struct HistoryListFragment: View {
.padding(.all)
)
}
.navigationTitle("")
.navigationBarHidden(true)
}
}
#Preview {
HistoryListFragment(historyListViewModel: HistoryListViewModel(), historyViewModel: HistoryViewModel(), showingSheet: .constant(false))
}
// swiftlint:enable line_length

View file

@ -90,6 +90,9 @@ struct StartCallFragment: View {
magicSearch.currentFilterSuggestions = newValue
magicSearch.searchForSuggestions()
}
.simultaneousGesture(TapGesture().onEnded {
showingDialer = false
})
HStack {
Button(action: {
@ -105,10 +108,18 @@ struct StartCallFragment: View {
if startCallViewModel.searchField.isEmpty {
Button(action: {
isSearchFieldFocused = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
showingDialer.toggle()
if !showingDialer {
isSearchFieldFocused = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
showingDialer = true
}
} else {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
isSearchFieldFocused = true
}
}
}, label: {
Image(!showingDialer ? "dialer" : "keyboard")
@ -155,7 +166,7 @@ struct StartCallFragment: View {
.padding(.horizontal, 16)
}
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false), startCallFunc: { addr in
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false), startCallFunc: { addr in
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
@ -169,10 +180,10 @@ struct StartCallFragment: View {
withAnimation {
isShowStartCallFragment.toggle()
telecomManager.doCallWithCore(addr: addr)
telecomManager.doCallWithCore(addr: addr, isVideo: false)
}
})
.padding(.horizontal, 16)
.padding(.horizontal, 16)
HStack(alignment: .center) {
Text("Suggestions")
@ -208,6 +219,25 @@ struct StartCallFragment: View {
var suggestionsList: some View {
ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in
Button {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
if contactsManager.lastSearchSuggestions[index].address != nil {
telecomManager.doCallWithCore(
addr: contactsManager.lastSearchSuggestions[index].address!, isVideo: false
)
}
}
} label: {
HStack {
if index < contactsManager.lastSearchSuggestions.count
@ -237,27 +267,6 @@ struct StartCallFragment: View {
.foregroundStyle(Color.orangeMain500)
}
}
.onTapGesture {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
}
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
if contactsManager.lastSearchSuggestions[index].address != nil {
telecomManager.doCallWithCore(
addr: contactsManager.lastSearchSuggestions[index].address!
)
}
}
}
.padding(.horizontal)
}
.buttonStyle(.borderless)

View file

@ -50,8 +50,10 @@ struct HistoryView: View {
}
} label: {
Image("phone-plus")
.renderingMode(.template)
.foregroundStyle(.white)
.padding()
.background(.white)
.background(Color.orangeMain500)
.clipShape(Circle())
.shadow(color: .black.opacity(0.2), radius: 4)

View file

@ -134,6 +134,8 @@ struct WelcomeView: View {
.frame(minHeight: geometry.size.height)
}
}
.navigationTitle("")
.navigationBarHidden(true)
}
.navigationViewStyle(StackNavigationViewStyle())
}

View file

@ -24,6 +24,9 @@ import UIKit
import os
import linphonesw
import linphone
#if USE_CRASHLYTICS
import Firebase
#endif
class Log: LoggingServiceDelegate {
@ -88,6 +91,9 @@ class Log: LoggingServiceDelegate {
} else {
NSLog(log)
}
#if USE_CRASHLYTICS
Crashlytics.crashlytics().log(log)
#endif
}
func onLogMessageWritten(logService: linphonesw.LoggingService, domain: String, level: linphonesw.LogLevel, message: String) {

View file

@ -28,15 +28,25 @@ class PermissionManager: ObservableObject {
@Published var photoLibraryPermissionGranted = false
@Published var cameraPermissionGranted = false
@Published var contactsPermissionGranted = false
@Published var microphonePermissionGranted = false
private init() {}
func getPermissions() {
microphoneRequestPermission()
photoLibraryRequestPermission()
cameraRequestPermission()
contactsRequestPermission()
}
func microphoneRequestPermission() {
AVAudioSession.sharedInstance().requestRecordPermission({ granted in
DispatchQueue.main.async {
self.microphonePermissionGranted = granted
}
})
}
func photoLibraryRequestPermission() {
PHPhotoLibrary.requestAuthorization(for: .readWrite, handler: {status in
DispatchQueue.main.async {

50
Podfile
View file

@ -5,14 +5,20 @@ source "https://github.com/CocoaPods/Specs.git"
def basic_pods
if ENV['PODFILE_PATH'].nil?
pod 'linphone-sdk', '~> 5.3.0-alpha'
pod 'linphone-sdk', '~> 5.4.0-alpha'
else
pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk
end
crashlytics
end
def crashlytics
if not ENV['USE_CRASHLYTICS'].nil?
pod 'Firebase/Analytics'
pod 'Firebase/Crashlytics'
end
end
target 'Linphone' do
# Comment the next line if you don't want to use dynamic frameworks
@ -25,9 +31,35 @@ target 'Linphone' do
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end
app_project = Xcodeproj::Project.open(Dir.glob("*.xcodeproj")[0])
app_project.native_targets.each do |target|
target.build_configurations.each do |config|
if target.name == "Linphone" || target.name == 'msgNotificationService' || target.name == 'msgNotificationContent'
if ENV['USE_CRASHLYTICS'].nil?
if config.name == "Debug" then
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) DEBUG=1'
else
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited)'
end
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited)'
else
# activate crashlytics
if config.name == "Debug" then
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) DEBUG=1 USE_CRASHLYTICS=1'
else
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) USE_CRASHLYTICS=1'
end
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -DUSE_CRASHLYTICS'
end
end
app_project.save
end
end
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end