diff --git a/Linphone/Assets.xcassets/arrows-merge.imageset/Contents.json b/Linphone/Assets.xcassets/arrows-merge.imageset/Contents.json new file mode 100644 index 000000000..9b143aad9 --- /dev/null +++ b/Linphone/Assets.xcassets/arrows-merge.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "arrows-merge.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/arrows-merge.imageset/arrows-merge.svg b/Linphone/Assets.xcassets/arrows-merge.imageset/arrows-merge.svg new file mode 100644 index 000000000..9bd183b23 --- /dev/null +++ b/Linphone/Assets.xcassets/arrows-merge.imageset/arrows-merge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index cd6131cc6..0e0fdb998 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -713,6 +713,40 @@ }, "Calls" : { + }, + "calls_list_dialog_merge_into_conference_label" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create conference" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer une conférence" + } + } + } + }, + "calls_list_dialog_merge_into_conference_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Merge all calls into conference?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fusionner les appels en une conférence ?" + } + } + } }, "Cancel" : { "localizations" : { diff --git a/Linphone/UI/Call/Fragments/CallsListFragment.swift b/Linphone/UI/Call/Fragments/CallsListFragment.swift index ae6bf53b5..9de87ee1a 100644 --- a/Linphone/UI/Call/Fragments/CallsListFragment.swift +++ b/Linphone/UI/Call/Fragments/CallsListFragment.swift @@ -31,6 +31,7 @@ struct CallsListFragment: View { @State private var delayedColor = Color.white @State var isShowCallsListBottomSheet: Bool = false + @State private var isShowPopup = false @Binding var isShowCallsListFragment: Bool @@ -65,6 +66,18 @@ struct CallsListFragment: View { Spacer() + if callViewModel.callsCounter > 1 { + Button { + self.isShowPopup = true + } label: { + Image("arrows-merge") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + } } .frame(maxWidth: .infinity) .frame(height: 50) @@ -87,6 +100,24 @@ struct CallsListFragment: View { } } .background(.white) + + if self.isShowPopup { + PopupView(isShowPopup: $isShowPopup, + title: Text("calls_list_dialog_merge_into_conference_title"), + content: nil, + titleFirstButton: Text("Cancel"), + actionFirstButton: {self.isShowPopup.toggle()}, + titleSecondButton: Text("calls_list_dialog_merge_into_conference_label"), + actionSecondButton: { + callViewModel.mergeCallsIntoConference() + self.isShowPopup.toggle() + isShowCallsListFragment.toggle() + }) + .background(.black.opacity(0.65)) + .onTapGesture { + self.isShowPopup.toggle() + } + } } .navigationBarHidden(true) } diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 03602c008..458053ad6 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -158,6 +158,7 @@ class CallViewModel: ObservableObject { displayNameTmp = self.currentCall!.remoteAddress!.username! } } + DispatchQueue.main.async { self.displayName = displayNameTmp } @@ -298,7 +299,6 @@ class CallViewModel: ObservableObject { coreContext.doOnCoreQueue { core in if self.currentCall?.conference != nil { let conf = self.currentCall!.conference! - self.isConference = true let displayNameTmp = conf.subject ?? "" @@ -368,6 +368,8 @@ class CallViewModel: ObservableObject { DispatchQueue.main.async { self.displayName = displayNameTmp + self.isConference = true + self.myParticipantModel = myParticipantModelTmp self.activeSpeakerParticipant = activeSpeakerParticipantTmp @@ -1121,5 +1123,32 @@ class CallViewModel: ObservableObject { } } } + + func mergeCallsIntoConference() { + self.coreContext.doOnCoreQueue { core in + let callsCount = core.callsNb + let defaultAccount = core.defaultAccount + var subject = "" + + if (defaultAccount != nil && defaultAccount!.params != nil && defaultAccount!.params!.audioVideoConferenceFactoryAddress != nil) { + Log.info("[CallViewModel] Merging \(callsCount) calls into a remotely hosted conference") + subject = "Remote group call" + } else { + Log.info("[CallViewModel] Merging \(callsCount) calls into a locally hosted conference") + subject = "Local group call" + } + do { + let params = try core.createConferenceParams(conference: nil) + params.subject = subject + // Prevent group call to start in audio only layout + params.videoEnabled = true + + let conference = try core.createConferenceWithParams(params: params) + try conference.addParticipants(calls: core.calls) + } catch { + + } + } + } } // swiftlint:enable type_body_length