diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift index 66ca4bc94..e4a7cf0e9 100644 --- a/Linphone/TelecomManager/TelecomManager.swift +++ b/Linphone/TelecomManager/TelecomManager.swift @@ -430,6 +430,27 @@ class TelecomManager: ObservableObject { func onCallStateChanged(core: Core, call: Call, state cstate: Call.State, message: String) { let callLog = call.callLog let callId = callLog?.callId ?? "" + if cstate == .OutgoingInit && !callInProgress { + if let remoteAddress = call.remoteAddress { + let uuid = UUID() + let name = remoteAddress.asStringUriOnly() + let handle = CXHandle(type: .generic, value: remoteAddress.asStringUriOnly()) + let startCallAction = CXStartCallAction(call: uuid, handle: handle) + let transaction = CXTransaction(action: startCallAction) + + let callInfo = CallInfo.newOutgoingCallInfo(addr: remoteAddress, isSas: false, displayName: name, isVideo: true, isConference: true) + providerDelegate.callInfos.updateValue(callInfo, forKey: uuid) + providerDelegate.uuids.updateValue(uuid, forKey: callId) + + setHeldOtherCalls(core: core, exceptCallid: callId) + requestTransaction(transaction, action: "startCall") + DispatchQueue.main.async { + withAnimation { + self.callDisplayed = true + } + } + } + } if cstate == .PushIncomingReceived { Log.info("PushIncomingReceived in core delegate, display callkit call") TelecomManager.shared.displayIncomingCall(call: call, handle: "Calling", hasVideo: false, callId: callId, displayName: "Calling") diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index eef729d0a..ffffd777c 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -718,7 +718,7 @@ struct ConversationFragment: View { } } else { Button { - if conversationViewModel.displayedConversationHistorySize > 0 { + if conversationViewModel.displayedConversationHistorySize > 1 { NotificationCenter.default.post(name: .onScrollToBottom, object: nil) } conversationViewModel.sendMessage() diff --git a/Linphone/UI/Main/Conversations/Fragments/UIList.swift b/Linphone/UI/Main/Conversations/Fragments/UIList.swift index 7fa9c6572..8e9579f59 100644 --- a/Linphone/UI/Main/Conversations/Fragments/UIList.swift +++ b/Linphone/UI/Main/Conversations/Fragments/UIList.swift @@ -362,15 +362,16 @@ struct UIList: UIViewRepresentable { NotificationCenter.default.addObserver(forName: .onScrollToBottom, object: nil, queue: nil) { _ in DispatchQueue.main.async { - if !self.sections.isEmpty { - if self.sections.first != nil - && parent.conversationViewModel.conversationMessagesSection.first != nil - && parent.conversationViewModel.displayedConversation != nil - && self.sections.first!.chatRoomID == parent.conversationViewModel.displayedConversation!.id - && self.sections.first!.rows.count == parent.conversationViewModel.conversationMessagesSection.first!.rows.count { - self.tableView!.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) - } + guard !self.sections.isEmpty, + let firstSection = self.sections.first, + let firstConversationSection = parent.conversationViewModel.conversationMessagesSection.first, + let displayedConversation = parent.conversationViewModel.displayedConversation, + let tableView = self.tableView, + firstSection.chatRoomID == displayedConversation.id, + firstSection.rows.count == firstConversationSection.rows.count else { + return } + tableView.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true) } } diff --git a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift index 04b82b4be..c69dde277 100644 --- a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift +++ b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift @@ -50,8 +50,8 @@ class ConversationModel: ObservableObject, Identifiable { @Published var unreadMessagesCount: Int @Published var avatarModel: ContactAvatarModel - private var conferenceScheduler: ConferenceScheduler? - private var conferenceSchedulerDelegate: ConferenceSchedulerDelegate? + //private var conference: Conference? + private var conferenceDelegate: ConferenceDelegate? init(chatRoom: ChatRoom) { self.chatRoom = chatRoom @@ -138,31 +138,24 @@ class ConversationModel: ObservableObject, Identifiable { conferenceInfo.organizer = account!.params?.identityAddress conferenceInfo.subject = self.chatRoom.subject ?? "Conference" - var participantsList: [ParticipantInfo] = [] + var participantsList: [Address] = [] self.chatRoom.participants.forEach { participant in - do { - let info = try Factory.Instance.createParticipantInfo(address: participant.address!) - // For meetings, all participants must have Speaker role - info.role = Participant.Role.Speaker - participantsList.append(info) - } catch let error { - Log.error( - "\(ConversationModel.TAG) Can't create ParticipantInfo: \(error)" - ) - } + participantsList.append(participant.address!) } - conferenceInfo.addParticipantInfos(participantInfos: participantsList) - Log.info( "\(ConversationModel.TAG) Creating group call with subject \(self.chatRoom.subject ?? "Conference") and \(participantsList.count) participant(s)" ) - - self.conferenceScheduler = try core.createConferenceScheduler(account: account) - if self.conferenceScheduler != nil { - self.conferenceAddDelegate(core: core, conferenceScheduler: self.conferenceScheduler!) - // Will trigger the conference creation/update automatically - self.conferenceScheduler!.info = conferenceInfo + if let conference = LinphoneUtils.createGroupCall(core: core, account: account, subject: self.chatRoom.subject ?? "Conference") { + let callParams = try? core.createCallParams(call: nil) + if let callParams = callParams { + callParams.videoEnabled = true + callParams.videoDirection = .RecvOnly + + print("\(ConversationModel.TAG) Inviting \(participantsList.count) participant(s) into newly created conference") + + try conference.inviteParticipants(addresses: participantsList, params: callParams) + } } } catch let error { Log.error( @@ -172,38 +165,6 @@ class ConversationModel: ObservableObject, Identifiable { } } - func conferenceAddDelegate(core: Core, conferenceScheduler: ConferenceScheduler) { - self.conferenceSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (conferenceScheduler: ConferenceScheduler, state: ConferenceScheduler.State) in - Log.info("\(ConversationModel.TAG) Conference scheduler state is \(state)") - if state == ConferenceScheduler.State.Ready { - conferenceScheduler.removeDelegate(delegate: self.conferenceSchedulerDelegate!) - self.conferenceSchedulerDelegate = nil - - let conferenceAddress = conferenceScheduler.info?.uri - if conferenceAddress != nil { - Log.info( - "\(ConversationModel.TAG) Conference info created, address is \(conferenceAddress!.asStringUriOnly())" - ) - - TelecomManager.shared.doCallWithCore(addr: conferenceAddress!, isVideo: true, isConference: true) - } else { - Log.error("\(ConversationModel.TAG) Conference info URI is null!") - - ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error" - ToastViewModel.shared.displayToast = true - } - } else if state == ConferenceScheduler.State.Error { - conferenceScheduler.removeDelegate(delegate: self.conferenceSchedulerDelegate!) - self.conferenceSchedulerDelegate = nil - Log.error("\(ConversationModel.TAG) Failed to create group call!") - - ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error" - ToastViewModel.shared.displayToast = true - } - }) - conferenceScheduler.addDelegate(delegate: self.conferenceSchedulerDelegate!) - } - func getContentTextMessage() { let lastMessage = self.chatRoom.lastMessageInHistory if lastMessage != nil { diff --git a/Linphone/Utils/LinphoneUtils.swift b/Linphone/Utils/LinphoneUtils.swift index 98fc32bdc..549ba4a6a 100644 --- a/Linphone/Utils/LinphoneUtils.swift +++ b/Linphone/Utils/LinphoneUtils.swift @@ -70,4 +70,34 @@ class LinphoneUtils: NSObject { core.defaultAccount?.params?.limeServerUrl != nil && core.defaultAccount?.params?.conferenceFactoryUri != nil } + + public class func createGroupCall(core: Core, account: Account?, subject: String) -> Conference? { + do { + let conferenceParams = try core.createConferenceParams(conference: nil) + conferenceParams.videoEnabled = true + conferenceParams.account = account + conferenceParams.subject = subject + + // Enable end-to-end encryption if client supports it + if isEndToEndEncryptedChatAvailable(core: core) { + Log.info("\(#function) Requesting EndToEnd security level for conference") + conferenceParams.securityLevel = .EndToEnd + } else { + Log.info("\(#function) Requesting PointToPoint security level for conference") + conferenceParams.securityLevel = .PointToPoint + } + + // Allows to have a chat room within the conference + conferenceParams.chatEnabled = true + + Log.info("\(#function) Creating group call with subject \(conferenceParams.subject ?? "Unknown")") + + let confWithParams = try core.createConferenceWithParams(params: conferenceParams) + + return confWithParams + } catch let error { + Log.info("\(#function) Error while creating group call: \(error)") + return nil + } + } }