diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist new file mode 100644 index 000000000..f996be8f2 --- /dev/null +++ b/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 221368768663-0ufgu96cel0auk4v0me863lgm252b9n2.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.221368768663-0ufgu96cel0auk4v0me863lgm252b9n2 + API_KEY + AIzaSyDJTtlRCM7IqdVUU2dSIYq2YIsTz6bqnkI + GCM_SENDER_ID + 221368768663 + PLIST_VERSION + 1 + BUNDLE_ID + org.linphone.phone + PROJECT_ID + linphone-iphone + STORAGE_BUCKET + linphone-iphone.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:221368768663:ios:a2c822bc087b5a219431d2 + DATABASE_URL + https://linphone-iphone.firebaseio.com + + diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index c49a03650..b4c229e18 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -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 = ""; }; 662B69D82B25DE18007118BF /* TelecomManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelecomManager.swift; sourceTree = ""; }; 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = ""; }; 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigExtension.swift; sourceTree = ""; }; @@ -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"; diff --git a/Linphone/Assets.xcassets/AppIcon.appiconset/1024.png b/Linphone/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 000000000..7b3df4579 Binary files /dev/null and b/Linphone/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/Linphone/Assets.xcassets/AppIcon.appiconset/Contents.json b/Linphone/Assets.xcassets/AppIcon.appiconset/Contents.json index 532cd729c..eab818e5a 100644 --- a/Linphone/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Linphone/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "1024.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/Linphone/Assets.xcassets/phone-list.imageset/Contents.json b/Linphone/Assets.xcassets/phone-list.imageset/Contents.json new file mode 100644 index 000000000..93d7f6f6b --- /dev/null +++ b/Linphone/Assets.xcassets/phone-list.imageset/Contents.json @@ -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 + } +} diff --git a/Linphone/Assets.xcassets/phone-list.imageset/phone-list.svg b/Linphone/Assets.xcassets/phone-list.imageset/phone-list.svg new file mode 100644 index 000000000..d070e2710 --- /dev/null +++ b/Linphone/Assets.xcassets/phone-list.imageset/phone-list.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/Linphone/Assets.xcassets/phone-transfer.imageset/Contents.json b/Linphone/Assets.xcassets/phone-transfer.imageset/Contents.json new file mode 100644 index 000000000..702f535c8 --- /dev/null +++ b/Linphone/Assets.xcassets/phone-transfer.imageset/Contents.json @@ -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 + } +} diff --git a/Linphone/Assets.xcassets/phone-transfer.imageset/phone-transfer.svg b/Linphone/Assets.xcassets/phone-transfer.imageset/phone-transfer.svg new file mode 100644 index 000000000..c63342fd6 --- /dev/null +++ b/Linphone/Assets.xcassets/phone-transfer.imageset/phone-transfer.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 98bdb7127..f69d10386 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -38,7 +38,7 @@ final class CoreContext: ObservableObject { private var mCore: Core! private var mIterateSuscription: AnyCancellable? private var mCoreSuscriptions = Set() - + 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() } } diff --git a/Linphone/Info.plist b/Linphone/Info.plist index 340c36eb2..0cc800b37 100644 --- a/Linphone/Info.plist +++ b/Linphone/Info.plist @@ -2,10 +2,10 @@ - NSCameraUsageDescription - Camera usage is required for video VOIP calls - NSMicrophoneUsageDescription - Microphone usage is required for VOIP calls + ITSAppUsesNonExemptEncryption + + ITSEncryptionExportComplianceCode + b5cb085f-772a-4a4f-8c77-5d1332b1f93f UIAppFonts NotoSans-Light.ttf diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index 61e22bdf3..0930db967 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -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() } } } diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 467b2feee..ec823b015 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -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" : { diff --git a/Linphone/Ressources/linphonerc-factory b/Linphone/Ressources/linphonerc-factory index 4074322fe..0abf8269d 100644 --- a/Linphone/Ressources/linphonerc-factory +++ b/Linphone/Ressources/linphonerc-factory @@ -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 diff --git a/Linphone/TelecomManager/ProviderDelegate.swift b/Linphone/TelecomManager/ProviderDelegate.swift index c12cbed92..44ef6095e 100644 --- a/Linphone/TelecomManager/ProviderDelegate.swift +++ b/Linphone/TelecomManager/ProviderDelegate.swift @@ -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 . -*/ + * 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 . + */ // 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.") diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift index 855e70305..aa93fd28a 100644 --- a/Linphone/TelecomManager/TelecomManager.swift +++ b/Linphone/TelecomManager/TelecomManager.swift @@ -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: [ diff --git a/Linphone/UI/Assistant/Fragments/LoginFragment.swift b/Linphone/UI/Assistant/Fragments/LoginFragment.swift index 8ae3e46e1..51fbd099c 100644 --- a/Linphone/UI/Assistant/Fragments/LoginFragment.swift +++ b/Linphone/UI/Assistant/Fragments/LoginFragment.swift @@ -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()) } diff --git a/Linphone/UI/Assistant/Fragments/RegisterFragment.swift b/Linphone/UI/Assistant/Fragments/RegisterFragment.swift index 194bd5b5a..dbc339513 100644 --- a/Linphone/UI/Assistant/Fragments/RegisterFragment.swift +++ b/Linphone/UI/Assistant/Fragments/RegisterFragment.swift @@ -65,8 +65,11 @@ struct RegisterFragment: View { } } } + .navigationTitle("") + .navigationBarHidden(true) } .navigationViewStyle(StackNavigationViewStyle()) + .navigationTitle("") .navigationBarHidden(true) } } diff --git a/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountLoginFragment.swift b/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountLoginFragment.swift index 6bb7e3bd0..8a8d5aa0b 100644 --- a/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountLoginFragment.swift +++ b/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountLoginFragment.swift @@ -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) diff --git a/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift b/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift index a3aa14ad5..f9f9fb1f8 100644 --- a/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift +++ b/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift @@ -171,8 +171,11 @@ struct ThirdPartySipAccountWarningFragment: View { .frame(minHeight: geometry.size.height) } } + .navigationTitle("") + .navigationBarHidden(true) } .navigationViewStyle(StackNavigationViewStyle()) + .navigationTitle("") .navigationBarHidden(true) } } diff --git a/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift b/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift index dd9149693..819931bd3 100644 --- a/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift +++ b/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift @@ -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) } } } diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index cd23af51e..709a814df 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -18,16 +18,17 @@ */ // swiftlint:disable type_body_length +// swiftlint:disable line_length import SwiftUI import CallKit import AVFAudio import linphonesw struct CallView: View { - - @ObservedObject private var coreContext = CoreContext.shared - @ObservedObject private var telecomManager = TelecomManager.shared - @ObservedObject private var contactsManager = ContactsManager.shared + + @ObservedObject private var coreContext = CoreContext.shared + @ObservedObject private var telecomManager = TelecomManager.shared + @ObservedObject private var contactsManager = ContactsManager.shared @ObservedObject var callViewModel: CallViewModel @@ -35,8 +36,8 @@ struct CallView: View { @State private var orientation = UIDevice.current.orientation let pub = NotificationCenter.default.publisher(for: AVAudioSession.routeChangeNotification) - - @State var startDate = Date.now + + @State var startDate = Date.now @State var audioRouteSheet: Bool = false @State var hideButtonsSheet: Bool = false @State var options: Int = 1 @@ -45,10 +46,10 @@ struct CallView: View { @State var angleDegree = 0.0 @State var fullscreenVideo = false - - var body: some View { - GeometryReader { geo in - if #available(iOS 16.4, *) { + + var body: some View { + GeometryReader { geo in + if #available(iOS 16.4, *) { innerView(geometry: geo) .sheet(isPresented: .constant( @@ -60,54 +61,55 @@ struct CallView: View { ) ) { GeometryReader { _ in - VStack(spacing: 0) { - HStack(spacing: 12) { - Button { + VStack(spacing: 0) { + HStack(spacing: 12) { + Button { callViewModel.terminateCall() - } label: { - Image("phone-disconnect") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 90, height: 60) - .background(Color.redDanger500) - .cornerRadius(40) - - Spacer() - - Button { + } label: { + Image("phone-disconnect") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 90, height: 60) + .background(Color.redDanger500) + .cornerRadius(40) + + Spacer() + + Button { callViewModel.toggleVideo() - } label: { - Image(callViewModel.cameraDisplayed ? "video-camera" : "video-camera-slash") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Button { + } label: { + Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash") + .renderingMode(.template) + .resizable() + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) + .cornerRadius(40) + .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) + + Button { callViewModel.toggleMuteMicrophone() - } label: { + } label: { Image(callViewModel.micMutted ? "microphone-slash" : "microphone") - .renderingMode(.template) - .resizable() + .renderingMode(.template) + .resizable() .foregroundStyle(callViewModel.micMutted ? .black : .white) - .frame(width: 32, height: 32) - - } - .frame(width: 60, height: 60) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) .background(callViewModel.micMutted ? .white : Color.gray500) - .cornerRadius(40) - - Button { - if AVAudioSession.sharedInstance().availableInputs != nil + .cornerRadius(40) + + Button { + if AVAudioSession.sharedInstance().availableInputs != nil && !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty { hideButtonsSheet = true @@ -123,200 +125,210 @@ struct CallView: View { } } - } label: { + } label: { Image(imageAudioRoute) - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) .onAppear(perform: getAudioRouteImage) - .onReceive(pub) { (output) in + .onReceive(pub) { _ in self.getAudioRouteImage() } - - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - } - .frame(height: geo.size.height * 0.15) - .padding(.horizontal, 20) + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + } + .frame(height: geo.size.height * 0.15) + .padding(.horizontal, 20) .padding(.top, -6) - - HStack(spacing: 0) { - VStack { - Button { - } label: { - Image("screencast") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Screen share") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - VStack { - Button { - } label: { - Image("users") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Participants") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - VStack { - Button { - } label: { - Image("chat-teardrop-text") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Messages") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - VStack { - Button { - } label: { - Image("notebook") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Disposition") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - } - .frame(height: geo.size.height * 0.15) - - HStack(spacing: 0) { - VStack { - Button { - } label: { - Image("phone-call") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Call list") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - VStack { - Button { + + HStack(spacing: 0) { + VStack { + Button { + } label: { + Image("phone-transfer") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background(Color.gray600) + .cornerRadius(40) + .disabled(true) + + Text("Transfer") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + + VStack { + Button { + } label: { + Image("phone-plus") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background(Color.gray600) + .cornerRadius(40) + .disabled(true) + + Text("New call") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + + VStack { + Button { + } label: { + Image("phone-list") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background(Color.gray600) + .cornerRadius(40) + .disabled(true) + + Text("Call list") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + + VStack { + Button { + } label: { + Image("dialer") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background(Color.gray600) + .cornerRadius(40) + .disabled(true) + + Text("Dialer") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + } + .frame(height: geo.size.height * 0.15) + + HStack(spacing: 0) { + VStack { + Button { + } label: { + Image("chat-teardrop-text") + .renderingMode(.template) + .resizable() + //.foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .foregroundStyle(Color.gray500) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + //.background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) + .background(Color.gray600) + .cornerRadius(40) + //.disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) + .disabled(true) + + Text("Messages") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + + VStack { + Button { callViewModel.togglePause() - } label: { - Image("pause") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Pause") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - VStack { - Button { + } label: { + Image(callViewModel.isPaused ? "play" : "pause") + .renderingMode(.template) + .resizable() + .foregroundStyle(telecomManager.isPausedByRemote ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background(telecomManager.isPausedByRemote ? Color.gray600 : (callViewModel.isPaused ? Color.greenSuccess500 : Color.gray500)) + .cornerRadius(40) + .disabled(telecomManager.isPausedByRemote) + + Text("Pause") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + + VStack { + Button { callViewModel.toggleRecording() - } label: { - Image("record-fill") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Record") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - - VStack { - Button { - } label: { - Image("video-camera") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 32, height: 32) - } - .frame(width: 60, height: 60) - .background(Color.gray500) - .cornerRadius(40) - - Text("Disposition") - .foregroundStyle(.white) - .default_text_style(styleSize: 15) - } - .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) - .hidden() - } - .frame(height: geo.size.height * 0.15) - - Spacer() - } - .frame(maxHeight: .infinity, alignment: .top) + } label: { + Image("record-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : (callViewModel.isRecording ? Color.redDanger500 : Color.gray500)) + .cornerRadius(40) + .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) + + Text("Record") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + + VStack { + Button { + } label: { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + + Text("Disposition") + .foregroundStyle(.white) + .default_text_style(styleSize: 15) + } + .frame(width: geo.size.width * 0.25, height: geo.size.width * 0.25) + .hidden() + } + .frame(height: geo.size.height * 0.15) + + Spacer() + } + .frame(maxHeight: .infinity, alignment: .top) .presentationBackground(.black) - .presentationDetents([.fraction(0.1), .medium]) - .interactiveDismissDisabled() - .presentationBackgroundInteraction(.enabled) - } - } - .sheet(isPresented: $audioRouteSheet, onDismiss: { + .presentationDetents([.fraction(0.1), .fraction(0.45)]) + .interactiveDismissDisabled() + .presentationBackgroundInteraction(.enabled) + } + } + .sheet(isPresented: $audioRouteSheet, onDismiss: { audioRouteSheet = false hideButtonsSheet = false - }) { + }) { VStack(spacing: 0) { Button(action: { options = 1 @@ -346,9 +358,9 @@ struct CallView: View { Image(!callViewModel.isHeadPhoneAvailable() ? "ear" : "headset") .renderingMode(.template) - .resizable() + .resizable() .foregroundStyle(.white) - .frame(width: 25, height: 25, alignment: .leading) + .frame(width: 25, height: 25, alignment: .leading) } }) .frame(maxHeight: .infinity) @@ -416,21 +428,32 @@ struct CallView: View { } .padding(.horizontal, 20) .presentationBackground(Color.gray600) - .presentationDetents([.fraction(0.3)]) + .presentationDetents([.fraction(0.3)]) .frame(maxHeight: .infinity) } - } - } - } - - @ViewBuilder + } else { + innerView(geometry: geo) + } + } + } + + @ViewBuilder + // swiftlint:disable:next cyclomatic_complexity func innerView(geometry: GeometryProxy) -> some View { - VStack { + VStack { if !fullscreenVideo { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) + if #available(iOS 16.0, *) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 0) + } else if idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 1) + } HStack { if callViewModel.direction == .Outgoing { @@ -451,9 +474,33 @@ struct CallView: View { .foregroundStyle(.white) } + if !telecomManager.outgoingCallStarted && telecomManager.callInProgress { + Text("|") + .foregroundStyle(.white) + + ZStack { + Text(callViewModel.timeElapsed.convertDurationToString()) + .onReceive(callViewModel.timer) { firedDate in + callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate)) + } + .foregroundStyle(.white) + .if(callViewModel.isPaused || telecomManager.isPausedByRemote) { view in + view.hidden() + } + + if callViewModel.isPaused { + Text("Paused") + .foregroundStyle(.white) + } else if telecomManager.isPausedByRemote { + Text("Paused by remote") + .foregroundStyle(.white) + } + } + } + Spacer() - if callViewModel.cameraDisplayed { + if telecomManager.remoteVideo { Button { callViewModel.switchCamera() } label: { @@ -469,65 +516,65 @@ struct CallView: View { .frame(height: 40) .zIndex(1) } - - ZStack { - VStack { - Spacer() - - if callViewModel.remoteAddress != nil { - let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!) - - let contactAvatarModel = addressFriend != nil - ? ContactsManager.shared.avatarListModel.first(where: { - ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy) - && $0.friend!.name == addressFriend!.name - && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly() - }) - : ContactAvatarModel(friend: nil, withPresence: false) - - if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { - if contactAvatarModel != nil { - Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true) - } - } else { - if callViewModel.remoteAddress!.displayName != nil { - Image(uiImage: contactsManager.textToImage( - firstName: callViewModel.remoteAddress!.displayName!, - lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1 - ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1] - : "")) - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - - } else { - Image(uiImage: contactsManager.textToImage( - firstName: callViewModel.remoteAddress!.username ?? "Username Error", - lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1 - ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1] - : "")) - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - } - - } - } else { - Image("profil-picture-default") - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - } - - Text(callViewModel.displayName) - .padding(.top) - .foregroundStyle(.white) - - Text(callViewModel.remoteAddressString) - .foregroundStyle(.white) - - Spacer() - } + + ZStack { + VStack { + Spacer() + + if callViewModel.remoteAddress != nil { + let addressFriend = contactsManager.getFriendWithAddress(address: callViewModel.remoteAddress!) + + let contactAvatarModel = addressFriend != nil + ? ContactsManager.shared.avatarListModel.first(where: { + ($0.friend!.consolidatedPresence == .Online || $0.friend!.consolidatedPresence == .Busy) + && $0.friend!.name == addressFriend!.name + && $0.friend!.address!.asStringUriOnly() == addressFriend!.address!.asStringUriOnly() + }) + : ContactAvatarModel(friend: nil, withPresence: false) + + if addressFriend != nil && addressFriend!.photo != nil && !addressFriend!.photo!.isEmpty { + if contactAvatarModel != nil { + Avatar(contactAvatarModel: contactAvatarModel!, avatarSize: 100, hidePresence: true) + } + } else { + if callViewModel.remoteAddress!.displayName != nil { + Image(uiImage: contactsManager.textToImage( + firstName: callViewModel.remoteAddress!.displayName!, + lastName: callViewModel.remoteAddress!.displayName!.components(separatedBy: " ").count > 1 + ? callViewModel.remoteAddress!.displayName!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + + } else { + Image(uiImage: contactsManager.textToImage( + firstName: callViewModel.remoteAddress!.username ?? "Username Error", + lastName: callViewModel.remoteAddress!.username!.components(separatedBy: " ").count > 1 + ? callViewModel.remoteAddress!.username!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + } + + } + } else { + Image("profil-picture-default") + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + } + + Text(callViewModel.displayName) + .padding(.top) + .foregroundStyle(.white) + + Text(callViewModel.remoteAddressString) + .foregroundStyle(.white) + + Spacer() + } LinphoneVideoViewHolder { view in coreContext.doOnCoreQueue { core in @@ -535,7 +582,7 @@ struct CallView: View { } } .frame( - width: + width: angleDegree == 0 ? 120 * ((geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom) / 160) : 120 * ((geometry.size.width + geometry.safeAreaInsets.leading + geometry.safeAreaInsets.trailing) / 120), @@ -547,10 +594,12 @@ struct CallView: View { .scaledToFill() .clipped() .onTapGesture { - fullscreenVideo.toggle() + if telecomManager.remoteVideo { + fullscreenVideo.toggle() + } } - if callViewModel.cameraDisplayed { + if telecomManager.remoteVideo { HStack { Spacer() VStack { @@ -572,31 +621,65 @@ struct CallView: View { ) } - if !telecomManager.callStarted && !fullscreenVideo { - VStack { - ActivityIndicator() - .frame(width: 20, height: 20) - .padding(.top, 100) - + if callViewModel.isRecording { + HStack { + VStack { + Image("record-fill") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.redDanger500) + .frame(width: 32, height: 32) + .padding(10) + .if(fullscreenVideo) { view in + view.padding(.top, 30) + } + Spacer() + } + Spacer() + } + .frame( + maxWidth: fullscreenVideo ? geometry.size.width : geometry.size.width - 8, + maxHeight: fullscreenVideo ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - 140 + ) + } + + if telecomManager.outgoingCallStarted { + VStack { + ActivityIndicator() + .frame(width: 20, height: 20) + .padding(.top, 100) + Text(callViewModel.counterToMinutes()) + .onAppear { + callViewModel.timeElapsed = 0 + startDate = Date.now + } .onReceive(callViewModel.timer) { firedDate in callViewModel.timeElapsed = Int(firedDate.timeIntervalSince(startDate)) - - } - .padding(.top) - .foregroundStyle(.white) - - Spacer() - } - .background(.clear) - } - } + + } + .onDisappear { + callViewModel.timeElapsed = 0 + startDate = Date.now + } + .padding(.top) + .foregroundStyle(.white) + + Spacer() + } + .frame( + maxWidth: fullscreenVideo ? geometry.size.width : geometry.size.width - 8, + maxHeight: fullscreenVideo ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - 140 + ) + .background(.clear) + } + } .frame( maxWidth: fullscreenVideo ? geometry.size.width : geometry.size.width - 8, maxHeight: fullscreenVideo ? geometry.size.height + geometry.safeAreaInsets.top + geometry.safeAreaInsets.bottom : geometry.size.height - 140 ) - .background(Color.gray600) - .cornerRadius(20) + .background(Color.gray600) + .cornerRadius(20) .padding(.horizontal, fullscreenVideo ? 0 : 4) .onRotate { newOrientation in orientation = newOrientation @@ -625,19 +708,104 @@ struct CallView: View { callViewModel.orientationUpdate(orientation: orientation) } - + if !fullscreenVideo { if telecomManager.callStarted { - if telecomManager.callStarted && idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { - HStack(spacing: 12) { - HStack { - + if #available(iOS 16.0, *) { + if telecomManager.callStarted && idiom != .pad && !(orientation == .landscapeLeft || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + HStack(spacing: 12) { + HStack { + + } + .frame(height: 60) } - .frame(height: 60) + .padding(.horizontal, 25) + .padding(.top, 20) + } else { + HStack(spacing: 12) { + Button { + callViewModel.terminateCall() + } label: { + Image("phone-disconnect") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + + } + .frame(width: 90, height: 60) + .background(Color.redDanger500) + .cornerRadius(40) + + Spacer() + + Button { + callViewModel.toggleVideo() + } label: { + Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash") + .renderingMode(.template) + .resizable() + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) + .cornerRadius(40) + .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) + + Button { + callViewModel.toggleMuteMicrophone() + } label: { + Image(callViewModel.micMutted ? "microphone-slash" : "microphone") + .renderingMode(.template) + .resizable() + .foregroundStyle(callViewModel.micMutted ? .black : .white) + .frame(width: 32, height: 32) + + } + .frame(width: 60, height: 60) + .background(callViewModel.micMutted ? .white : Color.gray500) + .cornerRadius(40) + + Button { + if AVAudioSession.sharedInstance().availableInputs != nil + && !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty { + + hideButtonsSheet = true + + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + audioRouteSheet = true + } + } else { + do { + try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty ? .speaker : .none) + } catch _ { + + } + } + + } label: { + Image(imageAudioRoute) + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 32, height: 32) + .onAppear(perform: getAudioRouteImage) + .onReceive(pub) { _ in + self.getAudioRouteImage() + } + + } + .frame(width: 60, height: 60) + .background(Color.gray500) + .cornerRadius(40) + } + .frame(height: geometry.size.height * 0.15) + .padding(.horizontal, 20) + .padding(.top, -6) } - .padding(.horizontal, 25) - .padding(.top, 20) } else { HStack(spacing: 12) { Button { @@ -659,16 +827,17 @@ struct CallView: View { Button { callViewModel.toggleVideo() } label: { - Image("video-camera") + Image(telecomManager.remoteVideo ? "video-camera" : "video-camera-slash") .renderingMode(.template) .resizable() - .foregroundStyle(.white) + .foregroundStyle((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray500 : .white) .frame(width: 32, height: 32) } .frame(width: 60, height: 60) - .background(Color.gray500) + .background((callViewModel.isPaused || telecomManager.isPausedByRemote) ? Color.gray600 : Color.gray500) .cornerRadius(40) + .disabled(callViewModel.isPaused || telecomManager.isPausedByRemote) Button { callViewModel.toggleMuteMicrophone() @@ -685,12 +854,32 @@ struct CallView: View { .cornerRadius(40) Button { + if AVAudioSession.sharedInstance().availableInputs != nil + && !AVAudioSession.sharedInstance().availableInputs!.filter({ $0.portType.rawValue.contains("Bluetooth") }).isEmpty { + + hideButtonsSheet = true + + DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { + audioRouteSheet = true + } + } else { + do { + try AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty ? .speaker : .none) + } catch _ { + + } + } + } label: { - Image("speaker-high") + Image(imageAudioRoute) .renderingMode(.template) .resizable() .foregroundStyle(.white) .frame(width: 32, height: 32) + .onAppear(perform: getAudioRouteImage) + .onReceive(pub) { _ in + self.getAudioRouteImage() + } } .frame(width: 60, height: 60) @@ -742,13 +931,13 @@ struct CallView: View { .padding(.top, 20) } } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.gray900) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.gray900) .if(fullscreenVideo) { view in view.ignoresSafeArea(.all) } - } + } func getAudioRouteImage() { imageAudioRoute = AVAudioSession.sharedInstance().currentRoute.outputs.filter({ $0.portType.rawValue == "Speaker" }).isEmpty @@ -769,3 +958,4 @@ struct CallView: View { CallView(callViewModel: CallViewModel()) } // swiftlint:enable type_body_length +// swiftlint:enable line_length diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 834b8874b..b6abd4aa5 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -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 { diff --git a/Linphone/UI/Main/Contacts/ContactsView.swift b/Linphone/UI/Main/Contacts/ContactsView.swift index a8191f047..fea21d9c7 100644 --- a/Linphone/UI/Main/Contacts/ContactsView.swift +++ b/Linphone/UI/Main/Contacts/ContactsView.swift @@ -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) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index 0e256c39e..f15e7bd3a 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -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 { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index a2627196a..72d4baece 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -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, diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift index bf6a9645d..644e6f16c 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift @@ -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) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift index 41bfc686b..3f594f977 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift @@ -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 }) } diff --git a/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift b/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift index fb469c160..efd2e1426 100644 --- a/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift +++ b/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift @@ -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" } diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 64f63dc5b..267968710 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -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 diff --git a/Linphone/UI/Main/Fragments/SideMenu.swift b/Linphone/UI/Main/Fragments/SideMenu.swift index 029f90ff1..e9fdc8d70 100644 --- a/Linphone/UI/Main/Fragments/SideMenu.swift +++ b/Linphone/UI/Main/Fragments/SideMenu.swift @@ -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 + } + } + } } diff --git a/Linphone/UI/Main/Fragments/ToastView.swift b/Linphone/UI/Main/Fragments/ToastView.swift index 40d5e7e16..565128b7e 100644 --- a/Linphone/UI/Main/Fragments/ToastView.swift +++ b/Linphone/UI/Main/Fragments/ToastView.swift @@ -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() } diff --git a/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift b/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift index dc3915be6..f8d2b0ddd 100644 --- a/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift +++ b/Linphone/UI/Main/History/Fragments/DialerBottomSheet.swift @@ -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 { } diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index 7ebc04890..a2fd12066 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -17,6 +17,8 @@ * along with this program. If not, see . */ +// 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... */ +// 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 diff --git a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift index 60ff5196d..7a391f3de 100644 --- a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift +++ b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift @@ -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.. 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 \ No newline at end of file + 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 +