Access to conversation from the contact

This commit is contained in:
Benoit Martins 2024-07-15 13:36:32 +02:00
parent e0d5254648
commit c78a77268e
6 changed files with 275 additions and 84 deletions

View file

@ -26,10 +26,12 @@ struct ContactFragment: View {
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@ObservedObject var conversationViewModel: ConversationViewModel
@Binding var isShowDeletePopup: Bool
@Binding var isShowDismissPopup: Bool
@Binding var isShowSipAddressesPopup: Bool
@Binding var isShowSipAddressesPopupType: Int
@State private var showingSheet = false
@State private var showShareSheet = false
@ -42,12 +44,14 @@ struct ContactFragment: View {
contactAvatarModel: ContactsManager.shared.avatarListModel[indexDisplayed],
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
conversationViewModel: conversationViewModel,
cnContact: CNContact(),
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet,
isShowDismissPopup: $isShowDismissPopup,
isShowSipAddressesPopup: $isShowSipAddressesPopup
isShowSipAddressesPopup: $isShowSipAddressesPopup,
isShowSipAddressesPopupType: $isShowSipAddressesPopupType
)
.sheet(isPresented: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
@ -63,12 +67,14 @@ struct ContactFragment: View {
contactAvatarModel: ContactsManager.shared.avatarListModel[indexDisplayed],
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
conversationViewModel: conversationViewModel,
cnContact: CNContact(),
isShowDeletePopup: $isShowDeletePopup,
showingSheet: $showingSheet,
showShareSheet: $showShareSheet,
isShowDismissPopup: $isShowDismissPopup,
isShowSipAddressesPopup: $isShowSipAddressesPopup
isShowSipAddressesPopup: $isShowSipAddressesPopup,
isShowSipAddressesPopupType: $isShowSipAddressesPopupType
)
.halfSheet(showSheet: $showingSheet) {
ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet)
@ -86,8 +92,10 @@ struct ContactFragment: View {
ContactFragment(
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
conversationViewModel: ConversationViewModel(),
isShowDeletePopup: .constant(false),
isShowDismissPopup: .constant(false),
isShowSipAddressesPopup: .constant(false)
isShowSipAddressesPopup: .constant(false),
isShowSipAddressesPopupType: .constant(0)
)
}

View file

@ -31,6 +31,7 @@ struct ContactInnerFragment: View {
@ObservedObject var contactAvatarModel: ContactAvatarModel
@ObservedObject var contactViewModel: ContactViewModel
@ObservedObject var editContactViewModel: EditContactViewModel
@ObservedObject var conversationViewModel: ConversationViewModel
@State private var orientation = UIDevice.current.orientation
@ -42,6 +43,7 @@ struct ContactInnerFragment: View {
@Binding var showShareSheet: Bool
@Binding var isShowDismissPopup: Bool
@Binding var isShowSipAddressesPopup: Bool
@Binding var isShowSipAddressesPopupType: Int
var body: some View {
NavigationView {
@ -161,6 +163,7 @@ struct ContactInnerFragment: View {
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
}
} else {
isShowSipAddressesPopupType = 0
isShowSipAddressesPopup = true
}
}, label: {
@ -184,21 +187,25 @@ struct ContactInnerFragment: View {
Spacer()
Button(action: {
if contactAvatarModel.addresses.count <= 1 {
do {
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address)
contactViewModel.createOneToOneChatRoomWith(remote: address)
} catch {
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
}
} else {
isShowSipAddressesPopupType = 1
isShowSipAddressesPopup = true
}
}, label: {
VStack {
HStack(alignment: .center) {
Image("chat-teardrop-text")
.renderingMode(.template)
.resizable()
//.foregroundStyle(Color.grayMain2c600)
.foregroundStyle(Color.grayMain2c300)
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
.onTapGesture {
withAnimation {
}
}
}
.padding(16)
.background(Color.grayMain2c200)
@ -220,6 +227,7 @@ struct ContactInnerFragment: View {
Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ")
}
} else {
isShowSipAddressesPopupType = 2
isShowSipAddressesPopup = true
}
}, label: {
@ -296,69 +304,6 @@ struct ContactInnerFragment: View {
print(error)
}
}
var sipAddressesPopup: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
HStack {
Text("contact_dialog_pick_phone_number_or_sip_address_title")
.default_text_style_800(styleSize: 16)
.background(.red)
.padding(.bottom, 2)
Spacer()
Image("x")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25)
.padding(.all, 10)
}
.frame(maxWidth: .infinity)
ForEach(0..<contactAvatarModel.addresses.count, id: \.self) { index in
HStack {
HStack {
VStack {
Text("SIP address :")
.default_text_style_700(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
Text(contactAvatarModel.addresses[index].dropFirst(4))
.default_text_style(styleSize: 14)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
.fixedSize(horizontal: false, vertical: true)
}
Spacer()
}
.padding(.vertical, 15)
.padding(.horizontal, 10)
}
.background(.white)
.onTapGesture {
do {
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.addresses[index])
withAnimation {
isShowSipAddressesPopup.toggle()
telecomManager.doCallOrJoinConf(address: address)
}
} catch {
Log.error("[ContactInnerActionsFragment] unable to create address for a new outgoing call : \(contactAvatarModel.addresses[index]) \(error) ")
}
}
}
}
.padding(.horizontal, 20)
.padding(.vertical, 20)
.background(.white)
.cornerRadius(20)
.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 {
@ -366,10 +311,12 @@ struct ContactInnerFragment: View {
contactAvatarModel: ContactAvatarModel(friend: nil, name: "", address: "", withPresence: true),
contactViewModel: ContactViewModel(),
editContactViewModel: EditContactViewModel(),
conversationViewModel: ConversationViewModel(),
isShowDeletePopup: .constant(false),
showingSheet: .constant(false),
showShareSheet: .constant(false),
isShowDismissPopup: .constant(false),
isShowSipAddressesPopup: .constant(false)
isShowSipAddressesPopup: .constant(false),
isShowSipAddressesPopupType: .constant(0)
)
}

View file

@ -29,6 +29,7 @@ struct SipAddressesPopup: View {
@ObservedObject var contactViewModel: ContactViewModel
@Binding var isShowSipAddressesPopup: Bool
@Binding var isShowSipAddressesPopupType: Int
var body: some View {
GeometryReader { geometry in
@ -71,9 +72,18 @@ struct SipAddressesPopup: View {
.onTapGesture {
do {
let address = try Factory.Instance.createAddress(addr: contactAvatarModel.addresses[index])
withAnimation {
isShowSipAddressesPopup.toggle()
telecomManager.doCallOrJoinConf(address: address)
if isShowSipAddressesPopupType != 1 {
withAnimation {
isShowSipAddressesPopup = false
telecomManager.doCallOrJoinConf(address: address, isVideo: isShowSipAddressesPopupType == 2)
isShowSipAddressesPopupType = 0
}
} else {
withAnimation {
isShowSipAddressesPopup = false
contactViewModel.createOneToOneChatRoomWith(remote: address)
isShowSipAddressesPopupType = 0
}
}
} catch {
Log.error("[ContactInnerActionsFragment] unable to create address for a new outgoing call : \(contactAvatarModel.addresses[index]) \(error) ")
@ -94,5 +104,10 @@ struct SipAddressesPopup: View {
}
#Preview {
SipAddressesPopup(contactAvatarModel: ContactAvatarModel(friend: nil, name: "", address: "", withPresence: false), contactViewModel: ContactViewModel(), isShowSipAddressesPopup: .constant(true))
SipAddressesPopup(
contactAvatarModel: ContactAvatarModel(friend: nil, name: "", address: "", withPresence: false),
contactViewModel: ContactViewModel(),
isShowSipAddressesPopup: .constant(true),
isShowSipAddressesPopupType: .constant(0)
)
}

View file

@ -18,6 +18,7 @@
*/
import linphonesw
import Combine
class ContactViewModel: ObservableObject {
@ -29,5 +30,202 @@ class ContactViewModel: ObservableObject {
var selectedFriendToShare: Friend?
var selectedFriendToDelete: Friend?
@Published var operationInProgress: Bool = false
@Published var displayedConversation: ConversationModel?
private var chatRoomSuscriptions = Set<AnyCancellable?>()
init() {}
func createOneToOneChatRoomWith(remote: Address) {
CoreContext.shared.doOnCoreQueue { core in
let account = core.defaultAccount
if account == nil {
Log.error(
"\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!"
)
return
}
DispatchQueue.main.async {
self.operationInProgress = true
}
do {
let params: ChatRoomParams = try core.createDefaultChatRoomParams()
params.groupEnabled = false
params.subject = "Dummy subject"
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
let sameDomain = remote.domain == account?.params?.domain ?? ""
if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain {
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation")
params.backend = ChatRoom.Backend.FlexisipChat
params.encryptionEnabled = true
} else if !StartConversationViewModel.isEndToEndEncryptionMandatory() {
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
Log.info(
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation"
)
params.backend = ChatRoom.Backend.FlexisipChat
params.encryptionEnabled = true
} else {
Log.info(
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
)
params.backend = ChatRoom.Backend.Basic
params.encryptionEnabled = false
}
} else {
Log.error(
"\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
)
DispatchQueue.main.async {
self.operationInProgress = false
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error"
ToastViewModel.shared.displayToast = true
}
return
}
let participants = [remote]
let localAddress = account?.params?.identityAddress
let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants)
if existingChatRoom == nil {
Log.info(
"\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account "
+ "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
)
let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants)
if params.backend == ChatRoom.Backend.FlexisipChat {
if chatRoom.state == ChatRoom.State.Created {
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created")
let model = ConversationModel(chatRoom: chatRoom)
if self.operationInProgress == false {
DispatchQueue.main.async {
self.operationInProgress = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.operationInProgress = false
self.displayedConversation = model
}
} else {
DispatchQueue.main.async {
self.operationInProgress = false
self.displayedConversation = model
}
}
} else {
Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it")
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
}
} else {
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)")
let model = ConversationModel(chatRoom: chatRoom)
if self.operationInProgress == false {
DispatchQueue.main.async {
self.operationInProgress = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.operationInProgress = false
self.displayedConversation = model
}
} else {
DispatchQueue.main.async {
self.operationInProgress = false
self.displayedConversation = model
}
}
}
} else {
Log.warn(
"\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
)
let model = ConversationModel(chatRoom: existingChatRoom!)
if self.operationInProgress == false {
DispatchQueue.main.async {
self.operationInProgress = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.operationInProgress = false
self.displayedConversation = model
}
} else {
DispatchQueue.main.async {
self.operationInProgress = false
self.displayedConversation = model
}
}
}
} catch {
DispatchQueue.main.async {
self.operationInProgress = false
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
ToastViewModel.shared.displayToast = true
}
Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!")
}
}
}
func chatRoomAddDelegate(core: Core, chatRoom: ChatRoom) {
self.chatRoomSuscriptions.insert(chatRoom.publisher?.onConferenceJoined?.postOnCoreQueue {
(chatRoom: ChatRoom, eventLog: EventLog) in
let state = chatRoom.state
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
Log.info("\(StartConversationViewModel.TAG) Conversation \(id) \(chatRoom.subject ?? "") state changed: \(state)")
if state == ChatRoom.State.Created {
Log.info("\(StartConversationViewModel.TAG) Conversation \(id) successfully created")
self.chatRoomSuscriptions.removeAll()
let model = ConversationModel(chatRoom: chatRoom)
if self.operationInProgress == false {
DispatchQueue.main.async {
self.operationInProgress = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.operationInProgress = false
self.displayedConversation = model
}
} else {
DispatchQueue.main.async {
self.operationInProgress = false
self.displayedConversation = model
}
}
} else if state == ChatRoom.State.CreationFailed {
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
self.chatRoomSuscriptions.removeAll()
DispatchQueue.main.async {
self.operationInProgress = false
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
ToastViewModel.shared.displayToast = true
}
}
})
self.chatRoomSuscriptions.insert(chatRoom.publisher?.onStateChanged?.postOnCoreQueue {
(chatRoom: ChatRoom, state: ChatRoom.State) in
let state = chatRoom.state
let id = LinphoneUtils.getChatRoomId(room: chatRoom)
if state == ChatRoom.State.CreationFailed {
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
self.chatRoomSuscriptions.removeAll()
DispatchQueue.main.async {
self.operationInProgress = false
ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error"
ToastViewModel.shared.displayToast = true
}
}
})
}
}

View file

@ -64,6 +64,7 @@ struct ContentView: View {
@State var isShowDismissPopup = false
@State var isShowSendCancelMeetingNotificationPopup = false
@State var isShowSipAddressesPopup = false
@State var isShowSipAddressesPopupType = 0 //0 to call, 1 to message, 2 to video call
@State var fullscreenVideo = false
@ -760,9 +761,11 @@ struct ContentView: View {
ContactFragment(
contactViewModel: contactViewModel,
editContactViewModel: editContactViewModel,
conversationViewModel: conversationViewModel,
isShowDeletePopup: $isShowDeleteContactPopup,
isShowDismissPopup: $isShowDismissPopup,
isShowSipAddressesPopup: $isShowSipAddressesPopup
isShowSipAddressesPopup: $isShowSipAddressesPopup,
isShowSipAddressesPopupType: $isShowSipAddressesPopupType
)
.frame(maxWidth: .infinity)
.background(Color.gray100)
@ -1014,7 +1017,8 @@ struct ContentView: View {
SipAddressesPopup(
contactAvatarModel: ContactsManager.shared.avatarListModel[contactViewModel.indexDisplayedFriend != nil ? contactViewModel.indexDisplayedFriend! : 0],
contactViewModel: contactViewModel,
isShowSipAddressesPopup: $isShowSipAddressesPopup
isShowSipAddressesPopup: $isShowSipAddressesPopup,
isShowSipAddressesPopupType: $isShowSipAddressesPopupType
)
.background(.black.opacity(0.65))
.zIndex(3)
@ -1023,6 +1027,25 @@ struct ContentView: View {
}
}
if contactViewModel.operationInProgress {
PopupLoadingView()
.background(.black.opacity(0.65))
.zIndex(3)
.onDisappear {
if contactViewModel.displayedConversation != nil {
contactViewModel.indexDisplayedFriend = nil
index = 2
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
withAnimation {
self.conversationViewModel.changeDisplayedChatRoom(conversationModel: contactViewModel.displayedConversation!)
}
contactViewModel.displayedConversation = nil
}
}
}
}
if isShowScheduleMeetingFragment {
ScheduleMeetingFragment(
meetingViewModel: meetingViewModel,

View file

@ -182,11 +182,11 @@ class StartConversationViewModel: ObservableObject {
params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
let sameDomain = remote.domain == account?.params?.domain ?? ""
if self.isEndToEndEncryptionMandatory() && sameDomain {
if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain {
Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation")
params.backend = ChatRoom.Backend.FlexisipChat
params.encryptionEnabled = true
} else if !self.isEndToEndEncryptionMandatory() {
} else if !StartConversationViewModel.isEndToEndEncryptionMandatory() {
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
Log.info(
"\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation"
@ -353,7 +353,7 @@ class StartConversationViewModel: ObservableObject {
})
}
func isEndToEndEncryptionMandatory() -> Bool {
public static func isEndToEndEncryptionMandatory() -> Bool {
return false // TODO: Will be done later in SDK
}
}