Extract AddParticipantsViewModel from the ScheduleMeetingViewModel to be used for other future views

This commit is contained in:
QuentinArguillere 2024-04-29 17:34:53 +02:00
parent 1f0c3fa5f7
commit 19da4e0d64
5 changed files with 71 additions and 41 deletions

View file

@ -13,6 +13,7 @@
6613A0B02BAEB7F4008923A4 /* MeetingsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */; }; 6613A0B02BAEB7F4008923A4 /* MeetingsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */; };
6613A0B42BAEBE3F008923A4 /* MeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0B32BAEBE3F008923A4 /* MeetingViewModel.swift */; }; 6613A0B42BAEBE3F008923A4 /* MeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0B32BAEBE3F008923A4 /* MeetingViewModel.swift */; };
6613A0B62BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0B52BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift */; }; 6613A0B62BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0B52BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift */; };
66162A202BDFC2F900DCE913 /* AddParticipantsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66162A1F2BDFC2F900DCE913 /* AddParticipantsViewModel.swift */; };
662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69D82B25DE18007118BF /* TelecomManager.swift */; }; 662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69D82B25DE18007118BF /* TelecomManager.swift */; };
662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */; }; 662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */; };
6646A7A32BB2E224006B842A /* ScheduleMeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */; }; 6646A7A32BB2E224006B842A /* ScheduleMeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */; };
@ -167,6 +168,7 @@
6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListFragment.swift; sourceTree = "<group>"; }; 6613A0AF2BAEB7F4008923A4 /* MeetingsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeetingsListFragment.swift; sourceTree = "<group>"; };
6613A0B32BAEBE3F008923A4 /* MeetingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingViewModel.swift; sourceTree = "<group>"; }; 6613A0B32BAEBE3F008923A4 /* MeetingViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MeetingViewModel.swift; sourceTree = "<group>"; };
6613A0B52BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingViewModel.swift; sourceTree = "<group>"; }; 6613A0B52BAEBE5C008923A4 /* ScheduleMeetingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingViewModel.swift; sourceTree = "<group>"; };
66162A1F2BDFC2F900DCE913 /* AddParticipantsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddParticipantsViewModel.swift; sourceTree = "<group>"; };
662B69D82B25DE18007118BF /* TelecomManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelecomManager.swift; sourceTree = "<group>"; }; 662B69D82B25DE18007118BF /* TelecomManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TelecomManager.swift; sourceTree = "<group>"; };
662B69DA2B25DE25007118BF /* ProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = "<group>"; }; 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderDelegate.swift; sourceTree = "<group>"; };
6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingFragment.swift; sourceTree = "<group>"; }; 6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleMeetingFragment.swift; sourceTree = "<group>"; };
@ -658,6 +660,7 @@
D7A2EDD42AC180FE005D90FC /* Viewmodel */ = { D7A2EDD42AC180FE005D90FC /* Viewmodel */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
66162A1F2BDFC2F900DCE913 /* AddParticipantsViewModel.swift */,
D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */, D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */,
D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */, D796F1FF2B0BB61A0041115F /* ToastViewModel.swift */,
); );
@ -980,6 +983,7 @@
D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */, D734499B2BC694C900778C56 /* MeetingWaitingRoomViewModel.swift in Sources */,
D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */, D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */,
66E50A4B2BD12B7800AD61CA /* MeetingsFragment.swift in Sources */, 66E50A4B2BD12B7800AD61CA /* MeetingsFragment.swift in Sources */,
66162A202BDFC2F900DCE913 /* AddParticipantsViewModel.swift in Sources */,
D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */, D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */,
D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */, D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */,
66E56BC92BA4A6D7006CE56F /* MeetingsListViewModel.swift in Sources */, 66E56BC92BA4A6D7006CE56F /* MeetingsListViewModel.swift in Sources */,
@ -1252,7 +1256,7 @@
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4; CURRENT_PROJECT_VERSION = 10;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\"";

View file

@ -18,7 +18,8 @@ struct AddParticipantsFragment: View {
@ObservedObject var contactsManager = ContactsManager.shared @ObservedObject var contactsManager = ContactsManager.shared
@ObservedObject var magicSearch = MagicSearchSingleton.shared @ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject var scheduleMeetingViewModel: ScheduleMeetingViewModel @ObservedObject var addParticipantsViewModel: AddParticipantsViewModel
var confirmAddParticipantsFunc: ([SelectedAddressModel]) -> Void
@State private var delayedColor = Color.white @State private var delayedColor = Color.white
@FocusState var isSearchFieldFocused: Bool @FocusState var isSearchFieldFocused: Bool
@ -50,7 +51,7 @@ struct AddParticipantsFragment: View {
.padding(.top, 2) .padding(.top, 2)
.padding(.leading, -10) .padding(.leading, -10)
.onTapGesture { .onTapGesture {
scheduleMeetingViewModel.participantsToAdd = [] addParticipantsViewModel.reset()
dismiss() dismiss()
} }
@ -59,7 +60,7 @@ struct AddParticipantsFragment: View {
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.default_text_style_orange_800(styleSize: 16) .default_text_style_orange_800(styleSize: 16)
.padding(.top, 20) .padding(.top, 20)
Text("\($scheduleMeetingViewModel.participants.count) selected participants") Text("\($addParticipantsViewModel.participantsToAdd.count) selected participants")
.default_text_style_300(styleSize: 12) .default_text_style_300(styleSize: 12)
} }
Spacer() Spacer()
@ -72,12 +73,12 @@ struct AddParticipantsFragment: View {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack { HStack {
ForEach(0..<scheduleMeetingViewModel.participantsToAdd.count, id: \.self) { index in ForEach(0..<addParticipantsViewModel.participantsToAdd.count, id: \.self) { index in
ZStack(alignment: .topTrailing) { ZStack(alignment: .topTrailing) {
VStack { VStack {
Avatar(contactAvatarModel: scheduleMeetingViewModel.participantsToAdd[index].avatarModel, avatarSize: 50) Avatar(contactAvatarModel: addParticipantsViewModel.participantsToAdd[index].avatarModel, avatarSize: 50)
Text(scheduleMeetingViewModel.participantsToAdd[index].avatarModel.name) Text(addParticipantsViewModel.participantsToAdd[index].avatarModel.name)
.default_text_style(styleSize: 12) .default_text_style(styleSize: 12)
.frame(minWidth: 60, maxWidth:80) .frame(minWidth: 60, maxWidth:80)
} }
@ -87,7 +88,7 @@ struct AddParticipantsFragment: View {
.foregroundStyle(Color.grayMain2c500) .foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25) .frame(width: 25, height: 25)
.onTapGesture { .onTapGesture {
scheduleMeetingViewModel.participantsToAdd.remove(at: index) addParticipantsViewModel.participantsToAdd.remove(at: index)
} }
} }
} }
@ -96,12 +97,12 @@ struct AddParticipantsFragment: View {
.padding(.leading, 16) .padding(.leading, 16)
ZStack(alignment: .trailing) { ZStack(alignment: .trailing) {
TextField("Search contact", text: $scheduleMeetingViewModel.searchField) TextField("Search contact", text: $addParticipantsViewModel.searchField)
.default_text_style(styleSize: 15) .default_text_style(styleSize: 15)
.frame(height: 25) .frame(height: 25)
.focused($isSearchFieldFocused) .focused($isSearchFieldFocused)
.padding(.horizontal, 30) .padding(.horizontal, 30)
.onChange(of: scheduleMeetingViewModel.searchField) { newValue in .onChange(of: addParticipantsViewModel.searchField) { newValue in
magicSearch.currentFilterSuggestions = newValue magicSearch.currentFilterSuggestions = newValue
magicSearch.searchForSuggestions() magicSearch.searchForSuggestions()
}.onAppear { }.onAppear {
@ -120,9 +121,9 @@ struct AddParticipantsFragment: View {
Spacer() Spacer()
if !scheduleMeetingViewModel.searchField.isEmpty { if !addParticipantsViewModel.searchField.isEmpty {
Button(action: { Button(action: {
scheduleMeetingViewModel.searchField = "" addParticipantsViewModel.searchField = ""
magicSearch.currentFilterSuggestions = "" magicSearch.currentFilterSuggestions = ""
magicSearch.searchForSuggestions() magicSearch.searchForSuggestions()
isSearchFieldFocused = false isSearchFieldFocused = false
@ -199,7 +200,7 @@ struct AddParticipantsFragment: View {
.background(.white) .background(.white)
.onTapGesture { .onTapGesture {
if let addr = contactsManager.lastSearch[index].address { if let addr = contactsManager.lastSearch[index].address {
scheduleMeetingViewModel.selectParticipant(addr: addr) addParticipantsViewModel.selectParticipant(addr: addr)
} }
} }
.buttonStyle(.borderless) .buttonStyle(.borderless)
@ -220,7 +221,7 @@ struct AddParticipantsFragment: View {
} }
Button { Button {
withAnimation { withAnimation {
scheduleMeetingViewModel.addParticipants() confirmAddParticipantsFunc(addParticipantsViewModel.participantsToAdd)
dismiss() dismiss()
} }
} label: { } label: {
@ -248,7 +249,7 @@ struct AddParticipantsFragment: View {
ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in
Button { Button {
if let addr = contactsManager.lastSearchSuggestions[index].address { if let addr = contactsManager.lastSearchSuggestions[index].address {
scheduleMeetingViewModel.selectParticipant(addr: addr) addParticipantsViewModel.selectParticipant(addr: addr)
} }
} label: { } label: {
HStack { HStack {
@ -288,5 +289,5 @@ struct AddParticipantsFragment: View {
} }
#Preview { #Preview {
AddParticipantsFragment(scheduleMeetingViewModel: ScheduleMeetingViewModel()) AddParticipantsFragment(addParticipantsViewModel: AddParticipantsViewModel(), confirmAddParticipantsFunc: {_ in } )
} }

View file

@ -42,6 +42,8 @@ struct ScheduleMeetingFragment: View {
@State var selectedHours: Int = 0 @State var selectedHours: Int = 0
@State var selectedMinutes: Int = 0 @State var selectedMinutes: Int = 0
@State var addParticipantsViewModel = AddParticipantsViewModel()
var body: some View { var body: some View {
NavigationView { NavigationView {
ZStack(alignment: .bottomTrailing) { ZStack(alignment: .bottomTrailing) {
@ -284,7 +286,10 @@ struct ScheduleMeetingFragment: View {
VStack { VStack {
NavigationLink(destination: { NavigationLink(destination: {
AddParticipantsFragment(scheduleMeetingViewModel: scheduleMeetingViewModel) AddParticipantsFragment(addParticipantsViewModel: addParticipantsViewModel, confirmAddParticipantsFunc: scheduleMeetingViewModel.addParticipants)
.onAppear {
addParticipantsViewModel.participantsToAdd = scheduleMeetingViewModel.participants
}
}, label: { }, label: {
HStack(alignment: .center, spacing: 8) { HStack(alignment: .center, spacing: 8) {
Image("users") Image("users")
@ -300,6 +305,7 @@ struct ScheduleMeetingFragment: View {
Spacer() Spacer()
} }
}) })
if !scheduleMeetingViewModel.participants.isEmpty { if !scheduleMeetingViewModel.participants.isEmpty {
ScrollView { ScrollView {
ForEach(0..<scheduleMeetingViewModel.participants.count, id: \.self) { index in ForEach(0..<scheduleMeetingViewModel.participants.count, id: \.self) { index in

View file

@ -21,16 +21,6 @@ import Foundation
import linphonesw import linphonesw
import Combine import Combine
class SelectedAddressModel: ObservableObject {
var address: Address
var avatarModel: ContactAvatarModel
init (addr: Address, avModel: ContactAvatarModel) {
address = addr
avatarModel = avModel
}
}
class ScheduleMeetingViewModel: ObservableObject { class ScheduleMeetingViewModel: ObservableObject {
static let TAG = "[ScheduleMeetingViewModel]" static let TAG = "[ScheduleMeetingViewModel]"
@ -45,13 +35,10 @@ class ScheduleMeetingViewModel: ObservableObject {
@Published var toTime: String = "" @Published var toTime: String = ""
@Published var timezone: String = "" @Published var timezone: String = ""
@Published var sendInvitations: Bool = true @Published var sendInvitations: Bool = true
@Published var participantsToAdd: [SelectedAddressModel] = []
@Published var participants: [SelectedAddressModel] = [] @Published var participants: [SelectedAddressModel] = []
@Published var operationInProgress: Bool = false @Published var operationInProgress: Bool = false
@Published var conferenceCreatedEvent: Bool = false @Published var conferenceCreatedEvent: Bool = false
@Published var searchField: String = ""
var conferenceScheduler: ConferenceScheduler? var conferenceScheduler: ConferenceScheduler?
private var mSchedulerSubscriptions = Set<AnyCancellable?>() private var mSchedulerSubscriptions = Set<AnyCancellable?>()
var conferenceInfoToEdit: ConferenceInfo? var conferenceInfoToEdit: ConferenceInfo?
@ -75,11 +62,9 @@ class ScheduleMeetingViewModel: ObservableObject {
allDayMeeting = false allDayMeeting = false
timezone = "" timezone = ""
sendInvitations = true sendInvitations = true
participantsToAdd = []
participants = [] participants = []
operationInProgress = false operationInProgress = false
conferenceCreatedEvent = false conferenceCreatedEvent = false
searchField = ""
fromDate = Calendar.current.date(byAdding: .hour, value: 1, to: Date.now)! fromDate = Calendar.current.date(byAdding: .hour, value: 1, to: Date.now)!
toDate = Calendar.current.date(byAdding: .hour, value: 2, to: Date.now)! toDate = Calendar.current.date(byAdding: .hour, value: 2, to: Date.now)!
@ -113,14 +98,7 @@ class ScheduleMeetingViewModel: ObservableObject {
// TODO // TODO
} }
func selectParticipant(addr: Address) { func addParticipants(participantsToAdd: [SelectedAddressModel]) {
if let idx = participantsToAdd.firstIndex(where: {$0.address.weakEqual(address2: addr)}) {
participantsToAdd.remove(at: idx)
} else {
participantsToAdd.append(SelectedAddressModel(addr: addr, avModel: ContactAvatarModel.getAvatarModelFromAddress(address: addr)))
}
}
func addParticipants() {
var list = participants var list = participants
for selectedAddr in participantsToAdd { for selectedAddr in participantsToAdd {
if let found = list.first(where: { $0.address.weakEqual(address2: selectedAddr.address) }) { if let found = list.first(where: { $0.address.weakEqual(address2: selectedAddr.address) }) {
@ -134,7 +112,6 @@ class ScheduleMeetingViewModel: ObservableObject {
Log.info("\(ScheduleMeetingViewModel.TAG) [\(list.count - participants.count) participants added, now there are \(list.count) participants in list") Log.info("\(ScheduleMeetingViewModel.TAG) [\(list.count - participants.count) participants added, now there are \(list.count) participants in list")
participants = list participants = list
participantsToAdd = []
} }
private func fillConferenceInfo(confInfo: ConferenceInfo) { private func fillConferenceInfo(confInfo: ConferenceInfo) {

View file

@ -0,0 +1,42 @@
//
// AddParticipantsViewModel.swift
// Linphone
//
// Created by QuentinArguillere on 29/04/2024.
//
import Foundation
import linphonesw
import Combine
class SelectedAddressModel: ObservableObject {
var address: Address
var avatarModel: ContactAvatarModel
init (addr: Address, avModel: ContactAvatarModel) {
address = addr
avatarModel = avModel
}
}
class AddParticipantsViewModel: ObservableObject {
static let TAG = "[AddParticipantsViewModel]"
@Published var participantsToAdd: [SelectedAddressModel] = []
@Published var searchField: String = ""
func selectParticipant(addr: Address) {
if let idx = participantsToAdd.firstIndex(where: {$0.address.weakEqual(address2: addr)}) {
Log.info("[\(AddParticipantsViewModel.TAG)] Removing participant \(addr.asStringUriOnly()) from selection")
participantsToAdd.remove(at: idx)
} else {
Log.info("[\(AddParticipantsViewModel.TAG)] Adding participant \(addr.asStringUriOnly()) to selection")
participantsToAdd.append(SelectedAddressModel(addr: addr, avModel: ContactAvatarModel.getAvatarModelFromAddress(address: addr)))
}
}
func reset() {
participantsToAdd = []
searchField = ""
}
}