Implement meetings bottom sheet and meeting details delete action

This commit is contained in:
QuentinArguillere 2024-06-27 16:00:55 +02:00
parent 5b5a5d88fa
commit 5b3f412bb7
9 changed files with 236 additions and 15 deletions

View file

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
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 */; };
@ -29,6 +30,7 @@
66E56BC92BA4A6D7006CE56F /* MeetingsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E56BC82BA4A6D7006CE56F /* MeetingsListViewModel.swift */; };
66E56BCC2BA9A1E0006CE56F /* MeetingsListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E56BCB2BA9A1E0006CE56F /* MeetingsListItemModel.swift */; };
66E56BCE2BA9A1F8006CE56F /* MeetingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E56BCD2BA9A1F8006CE56F /* MeetingModel.swift */; };
66F08C892C2AFEF700D9AE2F /* MeetingsListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F08C882C2AFEF700D9AE2F /* MeetingsListBottomSheet.swift */; };
66F626B22BCEBB86003E2DEC /* AddParticipantsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F626B12BCEBB86003E2DEC /* AddParticipantsFragment.swift */; };
66FBFC482B83B8CC00BC6AB1 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C492002B24DB6900CEA16D /* Log.swift */; };
66FBFC492B83BD2400BC6AB1 /* ConfigExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C491F82B24D25A00CEA16D /* ConfigExtension.swift */; };
@ -207,6 +209,7 @@
66E56BC82BA4A6D7006CE56F /* MeetingsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListViewModel.swift; sourceTree = "<group>"; };
66E56BCB2BA9A1E0006CE56F /* MeetingsListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListItemModel.swift; sourceTree = "<group>"; };
66E56BCD2BA9A1F8006CE56F /* MeetingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingModel.swift; sourceTree = "<group>"; };
66F08C882C2AFEF700D9AE2F /* MeetingsListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListBottomSheet.swift; sourceTree = "<group>"; };
66F626B12BCEBB86003E2DEC /* AddParticipantsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddParticipantsFragment.swift; sourceTree = "<group>"; };
C60E8F182C0F649200A06DB8 /* UIApplicationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIApplicationExtension.swift; sourceTree = "<group>"; };
C62817272C1B389700DBA646 /* SideMenuAccountRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuAccountRow.swift; sourceTree = "<group>"; };
@ -349,6 +352,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -410,6 +414,7 @@
66E50A4A2BD12B7800AD61CA /* MeetingsFragment.swift */,
6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */,
6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */,
66F08C882C2AFEF700D9AE2F /* MeetingsListBottomSheet.swift */,
6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */,
66F626B12BCEBB86003E2DEC /* AddParticipantsFragment.swift */,
);
@ -1126,6 +1131,7 @@
D7B99E9B2B29F7C300BE7BF2 /* ActivityIndicator.swift in Sources */,
66F626B22BCEBB86003E2DEC /* AddParticipantsFragment.swift in Sources */,
D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */,
66F08C892C2AFEF700D9AE2F /* MeetingsListBottomSheet.swift in Sources */,
D726E43F2B19E56F0083C415 /* StartCallViewModel.swift in Sources */,
D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */,
D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */,

View file

@ -60,6 +60,7 @@ struct ContentView: View {
@State var isShowEditContactFragment = false
@State var isShowStartCallFragment = false
@State var isShowDismissPopup = false
@State var isShowSendCancelMeetingNotificationPopup = false
@State var fullscreenVideo = false
@ -530,7 +531,8 @@ struct ContentView: View {
MeetingsView(
meetingsListViewModel: meetingsListViewModel,
meetingViewModel: meetingViewModel,
isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment
isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment,
isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup
)
}
}
@ -762,7 +764,7 @@ struct ContentView: View {
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
} else if self.index == 3 {
MeetingFragment(meetingViewModel: meetingViewModel, meetingsListViewModel: meetingsListViewModel, isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment)
MeetingFragment(meetingViewModel: meetingViewModel, meetingsListViewModel: meetingsListViewModel, isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment, isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup)
.frame(maxWidth: .infinity)
.background(Color.gray100)
.ignoresSafeArea(.keyboard)
@ -980,6 +982,28 @@ struct ContentView: View {
.onAppear {
}
}
if isShowSendCancelMeetingNotificationPopup {
PopupView(isShowPopup: $isShowSendCancelMeetingNotificationPopup,
title: Text("The meeting has been cancelled"),
content: Text("Send notification to participants ?"),
titleFirstButton: Text("Cancel"),
actionFirstButton: { self.isShowSendCancelMeetingNotificationPopup.toggle() },
titleSecondButton: Text("Ok"),
actionSecondButton: {
if let meetingToDelete = self.meetingsListViewModel.selectedMeetingToDelete {
// We're in the meeting list view
self.meetingViewModel.sendMeetingCancelledNotifications(meeting: meetingToDelete)
self.isShowSendCancelMeetingNotificationPopup.toggle()
}
})
.background(.black.opacity(0.65))
.zIndex(3)
.onTapGesture {
self.isShowSendCancelMeetingNotificationPopup.toggle()
}
}
if telecomManager.meetingWaitingRoomDisplayed {
MeetingWaitingRoomFragment(meetingWaitingRoomViewModel: meetingWaitingRoomViewModel)
.zIndex(3)

View file

@ -130,6 +130,13 @@ struct ToastView: View {
.default_text_style(styleSize: 15)
.padding(8)
case "Success_toast_meeting_deleted":
Text("Successfully removed meeting")
.multilineTextAlignment(.center)
.foregroundStyle(Color.greenSuccess500)
.default_text_style(styleSize: 15)
.padding(8)
case "Failed_toast_call_transfer_failed":
Text("Call transfer failed!")
.multilineTextAlignment(.center)

View file

@ -42,6 +42,7 @@ struct MeetingFragment: View {
@State var addParticipantsViewModel = AddParticipantsViewModel()
@Binding var isShowScheduleMeetingFragment: Bool
@Binding var isShowSendCancelMeetingNotificationPopup: Bool
@ViewBuilder
func getParticipantLine(participant: SelectedAddressModel) -> some View {
@ -105,13 +106,34 @@ struct MeetingFragment: View {
}
}
}
Image("dots-three-vertical")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.onTapGesture {
Menu {
Button(role: .destructive) {
withAnimation {
meetingsListViewModel.selectedMeetingToDelete = meetingViewModel.displayedMeeting
meetingViewModel.displayedMeeting = nil
meetingsListViewModel.deleteSelectedMeeting()
isShowSendCancelMeetingNotificationPopup.toggle()
}
} label: {
HStack {
Image("trash-simple")
.renderingMode(.template)
.resizable()
.frame(width: 25, height: 25, alignment: .leading)
Text("Delete this meeting")
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 16)
Spacer()
}
}
} label: {
Image("dots-three-vertical")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
}
}
.frame(maxWidth: .infinity)
.frame(height: 50)
@ -279,7 +301,8 @@ struct MeetingFragment: View {
model.description = "description du meeting ça va être la bringue wesh wesh gros bien ou bien ça roule"
return MeetingFragment(meetingViewModel: model
, meetingsListViewModel: MeetingsListViewModel()
, isShowScheduleMeetingFragment: .constant(true))
, isShowScheduleMeetingFragment: .constant(true)
, isShowSendCancelMeetingNotificationPopup: .constant(false))
}
// swiftlint:enable line_length

View file

@ -27,8 +27,7 @@ struct MeetingsFragment: View {
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
@State var showingSheet: Bool = false
@State var reader : ScrollViewProxy?
@Binding var showingSheet: Bool
@ViewBuilder
func createMonthLine(model: MeetingsListItemModel) -> some View {
@ -84,6 +83,10 @@ struct MeetingsFragment: View {
}
}
}
.onLongPressGesture(minimumDuration: 0.2) {
meetingViewModel.displayedMeeting = model.model
showingSheet.toggle()
}
}
var body: some View {
@ -173,5 +176,7 @@ struct MeetingsFragment: View {
}
#Preview {
MeetingsFragment(meetingsListViewModel: MeetingsListViewModel(), meetingViewModel: MeetingViewModel())
MeetingsFragment(meetingsListViewModel: MeetingsListViewModel(),
meetingViewModel: MeetingViewModel(),
showingSheet: .constant(false))
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2010-2023 Belledonne Communications SARL.
*
* This file is part of linphone-iphone
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SwiftUI
import linphonesw
import Contacts
struct MeetingsListBottomSheet: View {
@Environment(\.dismiss) var dismiss
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
@State private var orientation = UIDevice.current.orientation
@ObservedObject var meetingsListViewModel: MeetingsListViewModel
@Binding var showingSheet: Bool
@Binding var isShowSendCancelMeetingNotificationPopup: Bool
var body: some View {
VStack(alignment: .leading) {
if idiom != .pad && (orientation == .landscapeLeft
|| orientation == .landscapeRight
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
Spacer()
HStack {
Spacer()
Button("Close") {
if #available(iOS 16.0, *) {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
}
}
.padding(.trailing)
}
Button {
meetingsListViewModel.deleteSelectedMeeting()
CoreContext.shared.doOnCoreQueue { core in
if let organizerUri = self.meetingsListViewModel.selectedMeetingToDelete?.confInfo.organizer {
if core.defaultAccount?.contactAddress?.weakEqual(address2: organizerUri) ?? false {
DispatchQueue.main.async {
// If we are the organizer, display popup for sending
self.isShowSendCancelMeetingNotificationPopup = true
}
}
}
}
if #available(iOS 16.0, *) {
if idiom != .pad {
showingSheet.toggle()
} else {
showingSheet.toggle()
dismiss()
}
} else {
showingSheet.toggle()
dismiss()
}
} label: {
HStack {
Image("trash-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.redDanger500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
Text("Delete this meeting")
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 16)
Spacer()
}
.frame(maxHeight: .infinity)
}
.padding(.horizontal, 30)
.background(Color.gray100)
}
.background(Color.gray100)
.frame(maxWidth: .infinity)
.onRotate { newOrientation in
orientation = newOrientation
}
}
}

View file

@ -20,16 +20,40 @@
import SwiftUI
struct MeetingsView: View {
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
@ObservedObject var meetingsListViewModel: MeetingsListViewModel
@ObservedObject var meetingViewModel: MeetingViewModel
@Binding var isShowScheduleMeetingFragment: Bool
@Binding var isShowSendCancelMeetingNotificationPopup: Bool
@State private var showingSheet = false
var body: some View {
NavigationView {
ZStack(alignment: .bottomTrailing) {
MeetingsFragment(meetingsListViewModel: meetingsListViewModel, meetingViewModel: meetingViewModel)
if #available(iOS 16.0, *), idiom != .pad {
MeetingsFragment(meetingsListViewModel: meetingsListViewModel, meetingViewModel: meetingViewModel, showingSheet: $showingSheet)
.sheet(isPresented: $showingSheet) {
MeetingsListBottomSheet(
meetingsListViewModel: meetingsListViewModel,
showingSheet: $showingSheet,
isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup
)
.presentationDetents([.fraction(0.1)])
}
} else {
MeetingsFragment(meetingsListViewModel: meetingsListViewModel, meetingViewModel: meetingViewModel, showingSheet: $showingSheet)
.halfSheet(showSheet: $showingSheet) {
MeetingsListBottomSheet(
meetingsListViewModel: meetingsListViewModel,
showingSheet: $showingSheet,
isShowSendCancelMeetingNotificationPopup: $isShowSendCancelMeetingNotificationPopup
)
} onDismiss: {}
}
Button {
withAnimation {
@ -57,6 +81,7 @@ struct MeetingsView: View {
MeetingsView(
meetingsListViewModel: MeetingsListViewModel(),
meetingViewModel: MeetingViewModel(),
isShowScheduleMeetingFragment: .constant(false)
isShowScheduleMeetingFragment: .constant(false),
isShowSendCancelMeetingNotificationPopup: .constant(false)
)
}

View file

@ -348,4 +348,11 @@ class MeetingViewModel: ObservableObject {
}
}
func sendMeetingCancelledNotifications(meeting: MeetingModel) {
Log.error("\(MeetingViewModel.TAG) - sendMeetingCancelledNotifications TODO")
//CoreContext.shared.doOnCoreQueue { core in
// self.resetConferenceSchedulerAndListeners(core: core)
// self.conferenceScheduler?.cancelConference(conferenceInfo: meeting.confInfo)
//}
}
}

View file

@ -27,7 +27,7 @@ class MeetingsListViewModel: ObservableObject {
private var coreContext = CoreContext.shared
private var mCoreSuscriptions = Set<AnyCancellable?>()
var selectedMeeting: ConversationModel?
var selectedMeetingToDelete: MeetingModel?
@Published var meetingsList: [MeetingsListItemModel] = []
@Published var currentFilter = ""
@ -112,4 +112,25 @@ class MeetingsListViewModel: ObservableObject {
}
}
func deleteSelectedMeeting() {
guard let meetingToDelete = selectedMeetingToDelete else {
Log.error("\(MeetingsListViewModel.TAG) Could not delete meeting because none was selected")
return
}
coreContext.doOnCoreQueue { core in
core.deleteConferenceInformation(conferenceInfo: meetingToDelete.confInfo)
DispatchQueue.main.async {
if let index = self.meetingsList.firstIndex(where: { $0.model?.address == meetingToDelete.address }) {
if self.todayIdx > index {
// bump todayIdx one place up
self.todayIdx -= 1
}
self.meetingsList.remove(at: index)
ToastViewModel.shared.toastMessage = "Success_toast_meeting_deleted"
ToastViewModel.shared.displayToast = true
}
}
}
}
}