From 8aa5b327872a00fccff709a94f33996c2bcbe7b5 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 17 Jun 2025 23:00:17 +0200 Subject: [PATCH] Refactored MeetingsView --- Linphone/UI/Main/ContentView.swift | 112 ++++++++-------- .../Fragments/AddParticipantsFragment.swift | 10 +- .../Meetings/Fragments/MeetingFragment.swift | 39 ++---- .../Meetings/Fragments/MeetingsFragment.swift | 11 +- .../Fragments/MeetingsListBottomSheet.swift | 3 +- .../Fragments/ScheduleMeetingFragment.swift | 13 +- Linphone/UI/Main/Meetings/MeetingsView.swift | 16 +-- .../Meetings/ViewModel/MeetingViewModel.swift | 126 ++++++++---------- .../ViewModel/MeetingsListViewModel.swift | 57 +++++++- LinphoneApp.xcodeproj/project.pbxproj | 4 - 10 files changed, 198 insertions(+), 193 deletions(-) diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index b3f425dfe..a83271c93 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -40,14 +40,11 @@ struct ContentView: View { @State private var contactsListViewModel: ContactsListViewModel? @State private var historyListViewModel: HistoryListViewModel? @State private var conversationsListViewModel: ConversationsListViewModel? + @State private var meetingsListViewModel: MeetingsListViewModel? //@ObservedObject var meetingWaitingRoomViewModel: MeetingWaitingRoomViewModel - //@ObservedObject var meetingsListViewModel: MeetingsListViewModel - //@ObservedObject var meetingViewModel: MeetingViewModel - - //@Binding var index: Int @State private var orientation = UIDevice.current.orientation @State var sideMenuIsOpen: Bool = false @@ -513,9 +510,9 @@ struct ContentView: View { historyListVM.resetFilterCallLogs() } else if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 { conversationsListVM.resetFilterConversations() - } else if sharedMainViewModel.indexView == 3 { - //meetingsListViewModel.currentFilter = "" - //meetingsListViewModel.computeMeetingsList() + } else if let meetingsListVM = meetingsListViewModel, sharedMainViewModel.indexView == 3 { + meetingsListVM.currentFilter = "" + meetingsListVM.computeMeetingsList() } } label: { Image("caret-left") @@ -567,9 +564,9 @@ struct ContentView: View { } else { conversationsListVM.filterConversations(filter: text) } - } else if sharedMainViewModel.indexView == 3 { - //meetingsListViewModel.currentFilter = text - //meetingsListViewModel.computeMeetingsList() + } else if let meetingsListVM = meetingsListViewModel, sharedMainViewModel.indexView == 3 { + meetingsListVM.currentFilter = text + meetingsListVM.computeMeetingsList() } } } else { @@ -602,9 +599,9 @@ struct ContentView: View { historyListVM.filterCallLogs(filter: text) } else if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 { conversationsListVM.filterConversations(filter: text) - } else if sharedMainViewModel.indexView == 3 { - //meetingsListViewModel.currentFilter = text - //meetingsListViewModel.computeMeetingsList() + } else if let meetingsListVM = meetingsListViewModel, sharedMainViewModel.indexView == 3 { + meetingsListVM.currentFilter = text + meetingsListVM.computeMeetingsList() } } } @@ -726,31 +723,38 @@ struct ContentView: View { } } } else if sharedMainViewModel.indexView == 3 { - //TODO a changer - NavigationView { - ZStack(alignment: .bottomTrailing) { - } - } - .navigationViewStyle(.stack) - /* - MeetingsView( - meetingsListViewModel: meetingsListViewModel, - meetingViewModel: meetingViewModel, - isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment, - isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup, - text: $text - ) - .roundedCorner(25, corners: [.topRight, .topLeft]) - .shadow( - color: (orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) - ? .white.opacity(0.0) - : .black.opacity(0.2), - radius: 25 - ) - */ - } + if let meetingsListVM = meetingsListViewModel { + MeetingsView( + isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment, + isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup, + text: $text + ) + .environmentObject(meetingsListVM) + .roundedCorner(25, corners: [.topRight, .topLeft]) + .shadow( + color: (orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + ? .white.opacity(0.0) + : .black.opacity(0.2), + radius: 25 + ) + } else { + NavigationView { + VStack { + Spacer() + + ProgressView() + .controlSize(.large) + + Spacer() + } + .onAppear { + meetingsListViewModel = MeetingsListViewModel() + } + } + } + } } } } @@ -976,7 +980,7 @@ struct ContentView: View { .frame(maxWidth: .infinity) .background(Color.gray100) .ignoresSafeArea(.keyboard) - } else if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 { + } else if let conversationsListVM = conversationsListViewModel, let displayedConversation = sharedMainViewModel.displayedConversation, sharedMainViewModel.indexView == 2 { ConversationFragment( isShowConversationFragment: $isShowConversationFragment, isShowStartCallGroupPopup: $isShowStartCallGroupPopup, @@ -989,13 +993,12 @@ struct ContentView: View { .frame(maxWidth: .infinity) .background(Color.gray100) .ignoresSafeArea(.keyboard) - } else if sharedMainViewModel.indexView == 3 { - /* - MeetingFragment(meetingViewModel: meetingViewModel, meetingsListViewModel: meetingsListViewModel, isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment, isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup) + } else if let meetingsListVM = meetingsListViewModel, let displayedMeeting = sharedMainViewModel.displayedMeeting, sharedMainViewModel.indexView == 3 { + MeetingFragment(isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment, isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup) + .environmentObject(meetingsListVM) .frame(maxWidth: .infinity) .background(Color.gray100) .ignoresSafeArea(.keyboard) - */ } } @@ -1235,19 +1238,14 @@ struct ContentView: View { } } - /* - if isShowScheduleMeetingFragment { + if let meetingsListVM = meetingsListViewModel, isShowScheduleMeetingFragment { ScheduleMeetingFragment( - meetingViewModel: meetingViewModel, - meetingsListViewModel: meetingsListViewModel, isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment ) + .environmentObject(meetingsListVM) .zIndex(3) .transition(.move(edge: .bottom)) - .onAppear { - } } - */ if isShowAccountProfileFragment { AccountProfileFragment( @@ -1276,25 +1274,23 @@ struct ContentView: View { .zIndex(3) .transition(.move(edge: .trailing)) } + */ - if isShowSendCancelMeetingNotificationPopup { + if let meetingsListVM = meetingsListViewModel, isShowSendCancelMeetingNotificationPopup { PopupView(isShowPopup: $isShowSendCancelMeetingNotificationPopup, title: Text("meeting_schedule_cancel_dialog_title"), content: Text("meeting_schedule_cancel_dialog_message"), titleFirstButton: Text("dialog_cancel"), actionFirstButton: { sharedMainViewModel.displayedMeeting = nil - meetingsListViewModel.deleteSelectedMeeting() + meetingsListVM.deleteSelectedMeeting() self.isShowSendCancelMeetingNotificationPopup.toggle( ) }, titleSecondButton: Text("dialog_ok"), actionSecondButton: { sharedMainViewModel.displayedMeeting = nil - if let meetingToDelete = self.meetingsListViewModel.selectedMeetingToDelete { - self.meetingViewModel.cancelMeetingWithNotifications(meeting: meetingToDelete) - meetingsListViewModel.deleteSelectedMeeting() - self.isShowSendCancelMeetingNotificationPopup.toggle() - } + meetingsListVM.cancelMeetingWithNotifications() + self.isShowSendCancelMeetingNotificationPopup.toggle() }) .background(.black.opacity(0.65)) .zIndex(3) @@ -1302,7 +1298,6 @@ struct ContentView: View { self.isShowSendCancelMeetingNotificationPopup.toggle() } } - */ if isShowStartCallGroupPopup { PopupView( @@ -1455,7 +1450,6 @@ class NavigationManager: ObservableObject { //meetingViewModel: MeetingViewModel(), //conversationForwardMessageViewModel: ConversationForwardMessageViewModel(), //accountProfileViewModel: AccountProfileViewModel(), - //index: .constant(0) ) } // swiftlint:enable type_body_length diff --git a/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift b/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift index 79ad5a145..5601d2d16 100644 --- a/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift @@ -189,9 +189,10 @@ struct AddParticipantsFragment: View { .padding(.trailing, 5) } - if index < contactsManager.avatarListModel.count - && contactsManager.avatarListModel[index].friend!.photo != nil - && !contactsManager.avatarListModel[index].friend!.photo!.isEmpty { + if index < contactsManager.avatarListModel.count, + let friend = contactsManager.avatarListModel[index].friend, + let photo = friend.photo, + !photo.isEmpty { Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 50) } else { Image("profil-picture-default") @@ -199,7 +200,8 @@ struct AddParticipantsFragment: View { .frame(width: 50, height: 50) .clipShape(Circle()) } - Text((contactsManager.lastSearch[index].friend?.name)!) + + Text((contactsManager.lastSearch[index].friend?.name ?? "")!) .default_text_style(styleSize: 16) .frame(maxWidth: .infinity, alignment: .leading) .foregroundStyle(Color.orangeMain500) diff --git a/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift b/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift index 546558d50..5c0de1313 100644 --- a/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift @@ -28,8 +28,9 @@ struct MeetingFragment: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @State private var orientation = UIDevice.current.orientation - @ObservedObject var meetingViewModel: MeetingViewModel - @ObservedObject var meetingsListViewModel: MeetingsListViewModel + @EnvironmentObject var meetingsListViewModel: MeetingsListViewModel + + @StateObject private var meetingViewModel = MeetingViewModel() @State private var showDatePicker = false @State private var showTimePicker = false @@ -63,21 +64,15 @@ struct MeetingFragment: View { } var body: some View { + let displayedMeetingUpdated = NotificationCenter.default + .publisher(for: NSNotification.Name("DisplayedMeetingUpdated")) NavigationView { ZStack(alignment: .bottomTrailing) { VStack(spacing: 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) - } + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 1) HStack { Image("caret-left") @@ -316,16 +311,10 @@ struct MeetingFragment: View { }) } .navigationViewStyle(StackNavigationViewStyle()) + .onReceive(displayedMeetingUpdated) { _ in + if let displayedMeeting = SharedMainViewModel.shared.displayedMeeting { + meetingViewModel.loadExistingMeeting(meeting: displayedMeeting) + } + } } } - -#Preview { - let model = MeetingViewModel() - model.subject = "Meeting subject" - model.conferenceUri = "linphone.com/lalalal.fr" - model.description = "" - return MeetingFragment(meetingViewModel: model - , meetingsListViewModel: MeetingsListViewModel() - , isShowScheduleMeetingFragment: .constant(true) - , isShowSendCancelMeetingNotificationPopup: .constant(false)) -} diff --git a/Linphone/UI/Main/Meetings/Fragments/MeetingsFragment.swift b/Linphone/UI/Main/Meetings/Fragments/MeetingsFragment.swift index 647ade66d..bc75f8252 100644 --- a/Linphone/UI/Main/Meetings/Fragments/MeetingsFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/MeetingsFragment.swift @@ -21,9 +21,8 @@ import SwiftUI import linphonesw struct MeetingsFragment: View { - - @ObservedObject var meetingsListViewModel: MeetingsListViewModel - @ObservedObject var meetingViewModel: MeetingViewModel + + @EnvironmentObject var meetingsListViewModel: MeetingsListViewModel private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @@ -86,7 +85,7 @@ struct MeetingsFragment: View { .onTapGesture { withAnimation { if let meetingModel = model.model, meetingModel.confInfo.state != ConferenceInfo.State.Cancelled { - meetingViewModel.loadExistingMeeting(meeting: meetingModel) + SharedMainViewModel.shared.displayedMeeting = meetingModel } } } @@ -191,7 +190,5 @@ struct MeetingsFragment: View { } #Preview { - MeetingsFragment(meetingsListViewModel: MeetingsListViewModel(), - meetingViewModel: MeetingViewModel(), - showingSheet: .constant(false), text: .constant("")) + MeetingsFragment(showingSheet: .constant(false), text: .constant("")) } diff --git a/Linphone/UI/Main/Meetings/Fragments/MeetingsListBottomSheet.swift b/Linphone/UI/Main/Meetings/Fragments/MeetingsListBottomSheet.swift index 7be733ffe..1c64e87b6 100644 --- a/Linphone/UI/Main/Meetings/Fragments/MeetingsListBottomSheet.swift +++ b/Linphone/UI/Main/Meetings/Fragments/MeetingsListBottomSheet.swift @@ -28,7 +28,8 @@ struct MeetingsListBottomSheet: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @State private var orientation = UIDevice.current.orientation - @ObservedObject var meetingsListViewModel: MeetingsListViewModel + @EnvironmentObject var meetingsListViewModel: MeetingsListViewModel + @Binding var showingSheet: Bool @Binding var isShowSendCancelMeetingNotificationPopup: Bool diff --git a/Linphone/UI/Main/Meetings/Fragments/ScheduleMeetingFragment.swift b/Linphone/UI/Main/Meetings/Fragments/ScheduleMeetingFragment.swift index c225deb03..5d21a8520 100644 --- a/Linphone/UI/Main/Meetings/Fragments/ScheduleMeetingFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/ScheduleMeetingFragment.swift @@ -27,8 +27,9 @@ struct ScheduleMeetingFragment: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @State private var orientation = UIDevice.current.orientation - @ObservedObject var meetingViewModel: MeetingViewModel - @ObservedObject var meetingsListViewModel: MeetingsListViewModel + @EnvironmentObject var meetingsListViewModel: MeetingsListViewModel + + @StateObject private var meetingViewModel = MeetingViewModel() @State private var delayedColor = Color.white @State private var showDatePicker = false @@ -75,10 +76,6 @@ struct ScheduleMeetingFragment: View { .padding(.leading, -10) .onTapGesture { withAnimation { - if let meeting = SharedMainViewModel.shared.displayedMeeting { - // reload meeting to cancel change from edit - meetingViewModel.loadExistingMeeting(meeting: meeting) - } isShowScheduleMeetingFragment.toggle() } } @@ -506,9 +503,7 @@ struct ScheduleMeetingFragment: View { } #Preview { - ScheduleMeetingFragment(meetingViewModel: MeetingViewModel() - , meetingsListViewModel: MeetingsListViewModel() - , isShowScheduleMeetingFragment: .constant(true)) + ScheduleMeetingFragment(isShowScheduleMeetingFragment: .constant(true)) } // swiftlint:enable type_body_length diff --git a/Linphone/UI/Main/Meetings/MeetingsView.swift b/Linphone/UI/Main/Meetings/MeetingsView.swift index a4f37074e..1fd60f657 100644 --- a/Linphone/UI/Main/Meetings/MeetingsView.swift +++ b/Linphone/UI/Main/Meetings/MeetingsView.swift @@ -21,9 +21,8 @@ import SwiftUI struct MeetingsView: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } - - @ObservedObject var meetingsListViewModel: MeetingsListViewModel - @ObservedObject var meetingViewModel: MeetingViewModel + + @EnvironmentObject var meetingsListViewModel: MeetingsListViewModel @Binding var isShowScheduleMeetingFragment: Bool @Binding var isShowSendCancelMeetingNotificationPopup: Bool @@ -36,29 +35,28 @@ struct MeetingsView: View { ZStack(alignment: .bottomTrailing) { if #available(iOS 16.0, *), idiom != .pad { - MeetingsFragment(meetingsListViewModel: meetingsListViewModel, meetingViewModel: meetingViewModel, showingSheet: $showingSheet, text: $text) + MeetingsFragment(showingSheet: $showingSheet, text: $text) .sheet(isPresented: $showingSheet) { MeetingsListBottomSheet( - meetingsListViewModel: meetingsListViewModel, showingSheet: $showingSheet, isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup ) + .environmentObject(meetingsListViewModel) .presentationDetents([.fraction(0.1)]) } } else { - MeetingsFragment(meetingsListViewModel: meetingsListViewModel, meetingViewModel: meetingViewModel, showingSheet: $showingSheet, text: $text) + MeetingsFragment(showingSheet: $showingSheet, text: $text) .halfSheet(showSheet: $showingSheet) { MeetingsListBottomSheet( - meetingsListViewModel: meetingsListViewModel, showingSheet: $showingSheet, isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup ) + .environmentObject(meetingsListViewModel) } onDismiss: {} } Button { withAnimation { - meetingViewModel.resetViewModelData() isShowScheduleMeetingFragment.toggle() } } label: { @@ -80,8 +78,6 @@ struct MeetingsView: View { #Preview { MeetingsView( - meetingsListViewModel: MeetingsListViewModel(), - meetingViewModel: MeetingViewModel(), isShowScheduleMeetingFragment: .constant(false), isShowSendCancelMeetingNotificationPopup: .constant(false), text: .constant("") diff --git a/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift b/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift index 304a5595d..c201a9761 100644 --- a/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift +++ b/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift @@ -47,7 +47,6 @@ class MeetingViewModel: ObservableObject { var conferenceScheduler: ConferenceScheduler? private var mSchedulerDelegate: ConferenceSchedulerDelegate? - var conferenceInfoToEdit: ConferenceInfo? @Published var myself: SelectedAddressModel? @Published var fromDate: Date @Published var toDate: Date @@ -71,6 +70,10 @@ class MeetingViewModel: ObservableObject { selectedTimezoneIdx = knownTimezones.firstIndex(where: {$0 == selectedTimezone.identifier}) ?? 0 computeDateLabels() computeTimeLabels() + + if let displayedMeeting = SharedMainViewModel.shared.displayedMeeting { + self.loadExistingMeeting(meeting: displayedMeeting) + } } func resetViewModelData() { @@ -185,17 +188,18 @@ class MeetingViewModel: ObservableObject { } } else if state == ConferenceScheduler.State.Ready { let conferenceAddress = self.conferenceScheduler?.info?.uri - if let confInfoToEdit = self.conferenceInfoToEdit { - Log.info("\(MeetingViewModel.TAG) Conference info \(confInfoToEdit.uri?.asStringUriOnly() ?? "'nil'") has been updated") + Log.info("\(MeetingViewModel.TAG) Conference info created, address will be \(conferenceAddress?.asStringUriOnly() ?? "'nil'")") + DispatchQueue.main.async { + ToastViewModel.shared.toastMessage = "Success_meeting_info_created_toast" + ToastViewModel.shared.displayToast = true + } + + if SharedMainViewModel.shared.displayedMeeting != nil { DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_meeting_info_updated_toast" - ToastViewModel.shared.displayToast = true - } - } else { - Log.info("\(MeetingViewModel.TAG) Conference info created, address will be \(conferenceAddress?.asStringUriOnly() ?? "'nil'")") - DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_meeting_info_created_toast" - ToastViewModel.shared.displayToast = true + NotificationCenter.default.post( + name: NSNotification.Name("DisplayedMeetingUpdated"), + object: nil + ) } } @@ -210,9 +214,6 @@ class MeetingViewModel: ObservableObject { } } } else if state == ConferenceScheduler.State.Updating { - DispatchQueue.main.async { - ToastViewModel.shared.displayToast = true - } self.sendIcsInvitation(core: core) } }, onInvitationsSent: { (_: ConferenceScheduler, failedInvitations: [Address]) in @@ -295,73 +296,54 @@ class MeetingViewModel: ObservableObject { } } - func update() { - self.operationInProgress = true - CoreContext.shared.doOnCoreQueue { core in - Log.info("\(MeetingViewModel.TAG) Updating \(self.isBroadcastSelected ? "broadcast" : "meeting")") - - if let conferenceInfo = self.conferenceInfoToEdit { - self.fillConferenceInfo(confInfo: conferenceInfo) - self.resetConferenceSchedulerAndListeners(core: core) - - // Will trigger the conference update automatically - self.conferenceScheduler?.info = conferenceInfo - } else { - Log.error("No conference info to edit found!") - return - } - } - } - // Warning: must be called from core queue. Removed the dispatchQueue.main.async in order to have the animation properly trigger. func loadExistingMeeting(meeting: MeetingModel) { - self.resetViewModelData() - self.subject = meeting.confInfo.subject ?? "" - self.description = meeting.confInfo.description ?? "" - self.fromDate = meeting.meetingDate - self.toDate = meeting.endDate - self.participants = [] - - CoreContext.shared.doOnCoreQueue { core in - let organizer = meeting.confInfo.organizer - var organizerFound = false - - if let myAddr = core.defaultAccount?.contactAddress { - let isOrganizer = (organizer != nil) ? myAddr.weakEqual(address2: organizer!) : false + self.resetViewModelData() + self.subject = meeting.confInfo.subject ?? "" + self.description = meeting.confInfo.description ?? "" + self.fromDate = meeting.meetingDate + self.toDate = meeting.endDate + self.participants = [] + + CoreContext.shared.doOnCoreQueue { core in + let organizer = meeting.confInfo.organizer + var organizerFound = false + + if let myAddr = core.defaultAccount?.contactAddress { + let isOrganizer = (organizer != nil) ? myAddr.weakEqual(address2: organizer!) : false + organizerFound = organizerFound || isOrganizer + ContactAvatarModel.getAvatarModelFromAddress(address: myAddr) { avatarResult in + DispatchQueue.main.async { + self.myself = SelectedAddressModel(addr: myAddr, avModel: avatarResult, isOrg: isOrganizer) + } + } + } + + for pInfo in meeting.confInfo.participantInfos { + if let addr = pInfo.address { + let isOrganizer = (organizer != nil) ? addr.weakEqual(address2: organizer!) : false organizerFound = organizerFound || isOrganizer - ContactAvatarModel.getAvatarModelFromAddress(address: myAddr) { avatarResult in + ContactAvatarModel.getAvatarModelFromAddress(address: addr) { avatarResult in DispatchQueue.main.async { - self.myself = SelectedAddressModel(addr: myAddr, avModel: avatarResult, isOrg: isOrganizer) - } - } - } - - for pInfo in meeting.confInfo.participantInfos { - if let addr = pInfo.address { - let isOrganizer = (organizer != nil) ? addr.weakEqual(address2: organizer!) : false - organizerFound = organizerFound || isOrganizer - ContactAvatarModel.getAvatarModelFromAddress(address: addr) { avatarResult in - DispatchQueue.main.async { - self.participants.append(SelectedAddressModel(addr: addr, avModel: avatarResult, isOrg: isOrganizer)) - } - } - } - } - - // if didn't find organizer, add him - if !organizerFound, let org = organizer { - ContactAvatarModel.getAvatarModelFromAddress(address: org) { avatarResult in - DispatchQueue.main.async { - self.participants.append(SelectedAddressModel(addr: org, avModel: avatarResult, isOrg: true)) + self.participants.append(SelectedAddressModel(addr: addr, avModel: avatarResult, isOrg: isOrganizer)) } } } } - self.conferenceUri = meeting.confInfo.uri?.asStringUriOnly() ?? "" - self.computeDateLabels() - self.computeTimeLabels() - SharedMainViewModel.shared.displayedMeeting = meeting + // if didn't find organizer, add him + if !organizerFound, let org = organizer { + ContactAvatarModel.getAvatarModelFromAddress(address: org) { avatarResult in + DispatchQueue.main.async { + self.participants.append(SelectedAddressModel(addr: org, avModel: avatarResult, isOrg: true)) + } + } + } + } + + self.conferenceUri = meeting.confInfo.uri?.asStringUriOnly() ?? "" + self.computeDateLabels() + self.computeTimeLabels() } func cancelMeetingWithNotifications(meeting: MeetingModel) { diff --git a/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift b/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift index 2c01b93aa..c51fea139 100644 --- a/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift +++ b/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift @@ -141,8 +141,61 @@ class MeetingsListViewModel: ObservableObject { // Only remaining meeting is the fake TodayMeeting, remove it too meetingsList.removeAll() } - ToastViewModel.shared.toastMessage = "Success_toast_meeting_deleted" - ToastViewModel.shared.displayToast = true + + DispatchQueue.main.async { + ToastViewModel.shared.toastMessage = "Success_toast_meeting_deleted" + ToastViewModel.shared.displayToast = true + } + } + } + + func cancelMeetingWithNotifications() { + CoreContext.shared.doOnCoreQueue { core in + if let meeting = self.selectedMeetingToDelete { + let conferenceScheduler = try? core.createConferenceScheduler() + + //self.mSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (_: ConferenceScheduler, state: ConferenceScheduler.State) in + let mSchedulerDelegate = ConferenceSchedulerDelegateStub(onStateChanged: { (_: ConferenceScheduler, state: ConferenceScheduler.State) in + Log.info("\(MeetingViewModel.TAG) Conference state changed \(state)") + if state == ConferenceScheduler.State.Ready { + self.sendIcsInvitation(core: core, conferenceScheduler: conferenceScheduler) + self.deleteSelectedMeeting() + } + }, onInvitationsSent: { (_: ConferenceScheduler, failedInvitations: [Address]) in + + if failedInvitations.isEmpty { + Log.info("\(MeetingViewModel.TAG) All invitations have been sent") + } else { + var failInvList = "" + for failInv in failedInvitations { + if !failInvList.isEmpty { + failInvList += ", " + } + failInvList.append(failInv.asStringUriOnly()) + } + Log.warn("\(MeetingViewModel.TAG) \(failedInvitations.count) invitations couldn't have been sent to: \(failInvList)") + DispatchQueue.main.async { + ToastViewModel.shared.toastMessage = "meeting_failed_to_send_part_of_invites_toast" + ToastViewModel.shared.displayToast = true + } + } + }) + + conferenceScheduler?.addDelegate(delegate: mSchedulerDelegate) + conferenceScheduler?.cancelConference(conferenceInfo: meeting.confInfo) + } + } + } + + private func sendIcsInvitation(core: Core, conferenceScheduler: ConferenceScheduler?) { + if let chatRoomParams = try? core.createDefaultChatRoomParams() { + chatRoomParams.groupEnabled = false + chatRoomParams.backend = ChatRoom.Backend.FlexisipChat + chatRoomParams.encryptionEnabled = true + chatRoomParams.subject = "Meeting ics" + conferenceScheduler?.sendInvitations(chatRoomParams: chatRoomParams) + } else { + Log.error("\(MeetingViewModel.TAG) Failed to create default chatroom parameters. This should not happen") } } } diff --git a/LinphoneApp.xcodeproj/project.pbxproj b/LinphoneApp.xcodeproj/project.pbxproj index 864729a4b..a10a7806c 100644 --- a/LinphoneApp.xcodeproj/project.pbxproj +++ b/LinphoneApp.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; }; 6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */; }; - 6613A0B02BAEB7F4008923A4 /* MeetingsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */; }; 6613A0B42BAEBE3F008923A4 /* MeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0B32BAEBE3F008923A4 /* MeetingViewModel.swift */; }; 66162A202BDFC2F900DCE913 /* AddParticipantsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66162A1F2BDFC2F900DCE913 /* AddParticipantsViewModel.swift */; }; 66246C6A2C622AE900973E97 /* TimeZoneExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66246C692C622AE900973E97 /* TimeZoneExtension.swift */; }; @@ -226,7 +225,6 @@ 660AAF842B8392E0004C0FA6 /* msgNotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = msgNotificationService.entitlements; sourceTree = ""; }; 660D8A702B517D260092694D /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingFragment.swift; sourceTree = ""; }; - 6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListFragment.swift; sourceTree = ""; }; 6613A0B32BAEBE3F008923A4 /* MeetingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingViewModel.swift; sourceTree = ""; }; 66162A1F2BDFC2F900DCE913 /* AddParticipantsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddParticipantsViewModel.swift; sourceTree = ""; }; 66246C692C622AE900973E97 /* TimeZoneExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeZoneExtension.swift; sourceTree = ""; }; @@ -491,7 +489,6 @@ children = ( 66E50A4A2BD12B7800AD61CA /* MeetingsFragment.swift */, 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */, - 6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */, 66F08C882C2AFEF700D9AE2F /* MeetingsListBottomSheet.swift */, 6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */, 66F626B12BCEBB86003E2DEC /* AddParticipantsFragment.swift */, @@ -1242,7 +1239,6 @@ 66E56BC92BA4A6D7006CE56F /* MeetingsListViewModel.swift in Sources */, D726E4392B16440C0083C415 /* ContactAvatarModel.swift in Sources */, C67586AE2C09F23C002E77BF /* URLExtension.swift in Sources */, - 6613A0B02BAEB7F4008923A4 /* MeetingsListFragment.swift in Sources */, D76005F62B0798B00054B79A /* IntExtension.swift in Sources */, D79F2D0A2C47F4BF0038FA07 /* TouchFeedback.swift in Sources */, D78E06282BE3811D00CE3783 /* CallStatsModel.swift in Sources */,