Start group call

This commit is contained in:
Benoit Martins 2024-06-25 14:55:16 +02:00
parent 9befe0695a
commit b5d98cc45a
9 changed files with 557 additions and 183 deletions

View file

@ -59,6 +59,7 @@
D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */; };
D714DE602C1B3B34006C1F1D /* RegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */; };
D714DE622C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */; };
D71556362C297DB1009A8CEF /* StartGroupCallFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71556352C297DB1009A8CEF /* StartGroupCallFragment.swift */; };
D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071D2AC5922E0037746F /* ColorExtension.swift */; };
D71707202AC5989C0037746F /* TextExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071F2AC5989C0037746F /* TextExtension.swift */; };
D7173EBE2B7A5C0A00BCC481 /* LinphoneUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */; };
@ -233,6 +234,7 @@
D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMediaEncryptionModel.swift; sourceTree = "<group>"; };
D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterViewModel.swift; sourceTree = "<group>"; };
D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterCodeConfirmationFragment.swift; sourceTree = "<group>"; };
D71556352C297DB1009A8CEF /* StartGroupCallFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartGroupCallFragment.swift; sourceTree = "<group>"; };
D717071D2AC5922E0037746F /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = "<group>"; };
D717071F2AC5989C0037746F /* TextExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextExtension.swift; sourceTree = "<group>"; };
D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinphoneUtils.swift; sourceTree = "<group>"; };
@ -624,6 +626,7 @@
D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */,
D726E43C2B19E4FE0083C415 /* StartCallFragment.swift */,
D79622332B1DFE600037EACD /* DialerBottomSheet.swift */,
D71556352C297DB1009A8CEF /* StartGroupCallFragment.swift */,
);
path = Fragments;
sourceTree = "<group>";
@ -1051,6 +1054,7 @@
66E50A492BD12B2300AD61CA /* MeetingsView.swift in Sources */,
D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */,
D717630D2BD7BD0E00464097 /* ParticipantsListFragment.swift in Sources */,
D71556362C297DB1009A8CEF /* StartGroupCallFragment.swift in Sources */,
C6A5A9452C10B6270070FEA4 /* OIDAuthStateExtension.swift in Sources */,
D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */,
D79622342B1DFE600037EACD /* DialerBottomSheet.swift in Sources */,

View file

@ -461,12 +461,45 @@
},
"Conditions de service" : {
},
"conference_failed_to_create_group_call_toast" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Failed to create a group call!"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "L'appel de groupe n'a pas pu être créé!"
}
}
}
},
"Configuration failed" : {
},
"Configuration successfully applied" : {
},
"Confirm" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Confirm"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Confirmer"
}
}
}
},
"Connexion à la réunion" : {
@ -758,6 +791,54 @@
},
"History has been deleted" : {
},
"history_call_start_create_group_call" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Create a group call"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Démarrer un appel de groupe"
}
}
}
},
"history_group_call_start_dialog_set_subject" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Set group call subject"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nommer l'appel de groupe"
}
}
}
},
"history_group_call_start_dialog_subject_hint" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Group call subject"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Nom de l'appel de groupe"
}
}
}
},
"I prefere create an account" : {

View file

@ -570,12 +570,6 @@ struct ScrollOffsetPreferenceKey: PreferenceKey {
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct ImagePicker: UIViewControllerRepresentable {
@ObservedObject var conversationViewModel: ConversationViewModel
@Binding var selectedMedia: [Attachment]

View file

@ -192,7 +192,15 @@ struct ToastView: View {
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
case "Failed_to_create_group_call_error":
Text("conference_failed_to_create_group_call_toast")
.multilineTextAlignment(.center)
.foregroundStyle(Color.redDanger500)
.default_text_style(styleSize: 15)
.padding(8)
default:
Text("Error")
.multilineTextAlignment(.center)

View file

@ -22,6 +22,8 @@ import linphonesw
struct StartCallFragment: View {
@ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared
@ObservedObject var contactsManager = ContactsManager.shared
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@ObservedObject private var telecomManager = TelecomManager.shared
@ -35,213 +37,271 @@ struct StartCallFragment: View {
@FocusState var isSearchFieldFocused: Bool
@State private var delayedColor = Color.white
@FocusState var isMessageTextFocused: Bool
var resetCallView: () -> Void
var body: some View {
ZStack {
VStack(spacing: 1) {
Rectangle()
.foregroundColor(delayedColor)
.edgesIgnoringSafeArea(.top)
.frame(height: 0)
.task(delayColor)
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, 2)
.padding(.leading, -10)
.onTapGesture {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
NavigationView {
ZStack {
VStack(spacing: 1) {
Rectangle()
.foregroundColor(delayedColor)
.edgesIgnoringSafeArea(.top)
.frame(height: 0)
.task(delayColor)
HStack {
Image("caret-left")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
.padding(.top, 2)
.padding(.leading, -10)
.onTapGesture {
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
}
resetCallView()
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
}
}
Text(!callViewModel.isTransferInsteadCall ? "New call" : "Transfer call to")
.multilineTextAlignment(.leading)
.default_text_style_orange_800(styleSize: 16)
Spacer()
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 4)
.background(.white)
VStack(spacing: 0) {
ZStack(alignment: .trailing) {
TextField("Search contact or history call", text: $startCallViewModel.searchField)
.default_text_style(styleSize: 15)
.frame(height: 25)
.focused($isSearchFieldFocused)
.padding(.horizontal, 30)
.onChange(of: startCallViewModel.searchField) { newValue in
magicSearch.currentFilterSuggestions = newValue
magicSearch.searchForSuggestions()
}
.simultaneousGesture(TapGesture().onEnded {
showingDialer = false
})
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
HStack {
Button(action: {
}, label: {
Image("magnifying-glass")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
})
Spacer()
if startCallViewModel.searchField.isEmpty {
Button(action: {
if !showingDialer {
isSearchFieldFocused = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
showingDialer = true
}
} else {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
isSearchFieldFocused = true
}
}
}, label: {
Image(!showingDialer ? "dialer" : "keyboard")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
})
} else {
Button(action: {
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
magicSearch.searchForSuggestions()
}, label: {
Image("x")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
})
}
}
}
Text(!callViewModel.isTransferInsteadCall ? "New call" : "Transfer call to")
.multilineTextAlignment(.leading)
.default_text_style_orange_800(styleSize: 16)
Spacer()
}
.frame(maxWidth: .infinity)
.frame(height: 50)
.padding(.horizontal)
.padding(.bottom, 4)
.background(.white)
VStack(spacing: 0) {
ZStack(alignment: .trailing) {
TextField("Search contact or history call", text: $startCallViewModel.searchField)
.default_text_style(styleSize: 15)
.frame(height: 25)
.focused($isSearchFieldFocused)
.padding(.horizontal, 30)
.onChange(of: startCallViewModel.searchField) { newValue in
magicSearch.currentFilterSuggestions = newValue
magicSearch.searchForSuggestions()
}
.simultaneousGesture(TapGesture().onEnded {
showingDialer = false
})
.padding(.horizontal, 15)
.padding(.vertical, 10)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isSearchFieldFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.padding(.vertical)
.padding(.horizontal)
HStack {
Button(action: {
}, label: {
Image("magnifying-glass")
NavigationLink(destination: {
StartGroupCallFragment(startCallViewModel: startCallViewModel)
}, label: {
HStack {
HStack(alignment: .center) {
Image("meetings")
.renderingMode(.template)
.resizable()
.foregroundStyle(.white)
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(16)
.background(Color.orangeMain500)
.cornerRadius(40)
Text("history_call_start_create_group_call")
.foregroundStyle(.black)
.default_text_style_800(styleSize: 16)
Spacer()
Image("caret-right")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
})
.frame(width: 25, height: 25, alignment: .leading)
}
})
.padding(.vertical, 10)
.padding(.horizontal, 20)
.background(
LinearGradient(gradient: Gradient(colors: [.grayMain2c100, .white]), startPoint: .leading, endPoint: .trailing)
.padding(.vertical, 10)
.padding(.horizontal, 40)
)
ScrollView {
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("All contacts")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
}
Spacer()
if startCallViewModel.searchField.isEmpty {
Button(action: {
if !showingDialer {
isSearchFieldFocused = false
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false), startCallFunc: { addr in
if callViewModel.isTransferInsteadCall {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
showingDialer = true
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
} else {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
isSearchFieldFocused = true
}
resetCallView()
}
}, label: {
Image(!showingDialer ? "dialer" : "keyboard")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
})
} else {
Button(action: {
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
magicSearch.searchForSuggestions()
}, label: {
Image("x")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 25, height: 25)
})
}
}
}
.padding(.horizontal, 15)
.padding(.vertical, 10)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isSearchFieldFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.padding(.vertical)
.padding(.horizontal)
ScrollView {
if !ContactsManager.shared.lastSearch.isEmpty {
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
callViewModel.blindTransferCallTo(toAddress: addr)
}
} else {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
}
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
telecomManager.doCallOrJoinConf(address: addr)
}
}
})
.padding(.horizontal, 16)
HStack(alignment: .center) {
Text("All contacts")
Text("Suggestions")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
}
ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false), startCallFunc: { addr in
if callViewModel.isTransferInsteadCall {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
}
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
callViewModel.blindTransferCallTo(toAddress: addr)
}
} else {
showingDialer = false
DispatchQueue.global().asyncAfter(deadline: .now() + 0.2) {
magicSearch.searchForContacts(
sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue)
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
}
startCallViewModel.searchField = ""
magicSearch.currentFilterSuggestions = ""
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
telecomManager.doCallOrJoinConf(address: addr)
}
}
})
.padding(.horizontal, 16)
HStack(alignment: .center) {
Text("Suggestions")
.default_text_style_800(styleSize: 16)
Spacer()
suggestionsList
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
}
.frame(maxWidth: .infinity)
}
.background(.white)
if !startCallViewModel.participants.isEmpty {
startCallPopup
.background(.black.opacity(0.65))
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
isMessageTextFocused = true
}
}
}
if startCallViewModel.operationInProgress {
PopupLoadingView()
.background(.black.opacity(0.65))
.onDisappear {
isShowStartCallFragment.toggle()
}
}
.frame(maxWidth: .infinity)
}
.background(.white)
.navigationBarHidden(true)
}
.navigationBarHidden(true)
}
@Sendable private func delayColor() async {
@ -343,6 +403,72 @@ struct StartCallFragment: View {
.listRowSeparator(.hidden)
}
}
var startCallPopup: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
Text("history_group_call_start_dialog_set_subject")
.default_text_style_800(styleSize: 16)
.frame(alignment: .leading)
.padding(.bottom, 2)
TextField("history_group_call_start_dialog_subject_hint", text: $startCallViewModel.messageText)
.default_text_style(styleSize: 15)
.frame(height: 25)
.padding(.horizontal, 20)
.padding(.vertical, 15)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(isMessageTextFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
)
.padding(.bottom)
.focused($isMessageTextFocused)
Button(action: {
startCallViewModel.participants.removeAll()
}, label: {
Text("Cancel")
.default_text_style_orange_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20)
.padding(.vertical, 10)
.cornerRadius(60)
.overlay(
RoundedRectangle(cornerRadius: 60)
.inset(by: 0.5)
.stroke(Color.orangeMain500, lineWidth: 1)
)
.padding(.bottom, 10)
Button(action: {
startCallViewModel.createGroupCall()
}, label: {
Text("Confirm")
.default_text_style_white_600(styleSize: 20)
.frame(height: 35)
.frame(maxWidth: .infinity)
})
.padding(.horizontal, 20)
.padding(.vertical, 10)
.background(startCallViewModel.messageText.isEmpty ? Color.orangeMain100 : Color.orangeMain500)
.cornerRadius(60)
.disabled(startCallViewModel.messageText.isEmpty)
}
.padding(.horizontal, 20)
.padding(.vertical, 20)
.background(.white)
.cornerRadius(20)
.padding(.horizontal)
.frame(maxHeight: .infinity)
.shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
.frame(maxWidth: sharedMainViewModel.maxWidth)
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
}
}
}
#Preview {

View file

@ -0,0 +1,24 @@
//
// StartGroupCallFragment.swift
// Linphone
//
// Created by Benoît Martins on 24/06/2024.
//
import SwiftUI
struct StartGroupCallFragment: View {
@ObservedObject var startCallViewModel: StartCallViewModel
@State var addParticipantsViewModel = AddParticipantsViewModel()
var body: some View {
AddParticipantsFragment(addParticipantsViewModel: addParticipantsViewModel, confirmAddParticipantsFunc: startCallViewModel.addParticipants)
.onAppear {
addParticipantsViewModel.participantsToAdd = startCallViewModel.participants
}
}
}
#Preview {
StartGroupCallFragment(startCallViewModel: StartCallViewModel())
}

View file

@ -18,16 +18,147 @@
*/
import linphonesw
import Combine
class StartCallViewModel: ObservableObject {
static let TAG = "[StartCallViewModel]"
private var coreContext = CoreContext.shared
@Published var searchField: String = ""
var domain: String = ""
@Published var messageText: String = ""
@Published var participants: [SelectedAddressModel] = []
@Published var operationInProgress: Bool = false
private var conferenceSuscriptions = Set<AnyCancellable?>()
init() {
CoreContext.shared.doOnCoreQueue { core in
coreContext.doOnCoreQueue { core in
self.domain = core.defaultAccount?.params?.domain ?? ""
}
}
func addParticipants(participantsToAdd: [SelectedAddressModel]) {
var list = participants
for selectedAddr in participantsToAdd {
if let found = list.first(where: { $0.address.weakEqual(address2: selectedAddr.address) }) {
Log.info("\(ScheduleMeetingViewModel.TAG) Participant \(found.address.asStringUriOnly()) already in list, skipping")
continue
}
list.append(selectedAddr)
Log.info("\(ScheduleMeetingViewModel.TAG) Added participant \(selectedAddr.address.asStringUriOnly())")
}
Log.info("\(ScheduleMeetingViewModel.TAG) [\(list.count - participants.count) participants added, now there are \(list.count) participants in list")
participants = list
}
func createGroupCall() {
coreContext.doOnCoreQueue { core in
let account = core.defaultAccount
if account == nil {
Log.error(
"\(StartCallViewModel.TAG) No default account found, can't create group call!"
)
return
}
DispatchQueue.main.async {
self.operationInProgress = true
}
do {
let conferenceInfo = try Factory.Instance.createConferenceInfo()
conferenceInfo.organizer = account!.params?.identityAddress
conferenceInfo.subject = self.messageText
var participantsList: [ParticipantInfo] = []
self.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(
"\(StartCallViewModel.TAG) Can't create ParticipantInfo: \(error)"
)
}
}
self.participants.removeAll()
conferenceInfo.addParticipantInfos(participantInfos: participantsList)
Log.info(
"\(StartCallViewModel.TAG) Creating group call with subject \(self.messageText) and \(participantsList.count) participant(s)"
)
let conferenceScheduler = try core.createConferenceScheduler()
self.conferenceAddDelegate(core: core, conferenceScheduler: conferenceScheduler)
conferenceScheduler.account = account
// Will trigger the conference creation/update automatically
conferenceScheduler.info = conferenceInfo
} catch let error {
Log.error(
"\(StartCallViewModel.TAG) createGroupCall: \(error)"
)
}
}
}
func conferenceAddDelegate(core: Core, conferenceScheduler: ConferenceScheduler) {
self.conferenceSuscriptions.insert(conferenceScheduler.publisher?.onStateChanged?.postOnCoreQueue {
(conferenceScheduler: ConferenceScheduler, state: ConferenceScheduler.State) in
Log.info("\(StartCallViewModel.TAG) Conference scheduler state is \(state)")
if state == ConferenceScheduler.State.Ready {
self.conferenceSuscriptions.removeAll()
let conferenceAddress = conferenceScheduler.info?.uri
if conferenceAddress != nil {
Log.info(
"\(StartCallViewModel.TAG) Conference info created, address is \(conferenceAddress?.asStringUriOnly() ?? "Error conference address")"
)
self.startVideoCall(core: core, conferenceAddress: conferenceAddress!)
} else {
Log.error("\(StartCallViewModel.TAG) Conference info URI is null!")
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
ToastViewModel.shared.displayToast = true
}
DispatchQueue.main.async {
self.operationInProgress = false
}
} else if state == ConferenceScheduler.State.Error {
self.conferenceSuscriptions.removeAll()
Log.error("\(StartCallViewModel.TAG) Failed to create group call!")
ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error"
ToastViewModel.shared.displayToast = true
DispatchQueue.main.async {
self.operationInProgress = false
}
}
})
}
func startVideoCall(core: Core, conferenceAddress: Address) {
do {
TelecomManager.shared.doCallWithCore(addr: conferenceAddress, isVideo: true, isConference: true)
} catch let error {
Log.error(
"\(StartCallViewModel.TAG) StartVideoCall: \(error)"
)
}
}
}

View file

@ -32,7 +32,9 @@ class AddParticipantsViewModel: ObservableObject {
} else {
Log.info("[\(AddParticipantsViewModel.TAG)] Adding participant \(addr.asStringUriOnly()) to selection")
ContactAvatarModel.getAvatarModelFromAddress(address: addr) { avatarResult in
self.participantsToAdd.append(SelectedAddressModel(addr: addr, avModel: avatarResult))
DispatchQueue.main.async {
self.participantsToAdd.append(SelectedAddressModel(addr: addr, avModel: avatarResult))
}
}
}
}

View file

@ -35,4 +35,8 @@ extension UIApplication {
}
return nil
}
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}