mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-05-07 05:53:06 +00:00
Access to conversation from a call
This commit is contained in:
parent
2e2dac6807
commit
76d4a8cdb3
4 changed files with 305 additions and 23 deletions
|
|
@ -33,6 +33,8 @@ struct CallView: View {
|
|||
@ObservedObject private var contactsManager = ContactsManager.shared
|
||||
|
||||
@ObservedObject var callViewModel: CallViewModel
|
||||
@ObservedObject var conversationViewModel: ConversationViewModel
|
||||
@ObservedObject var conversationsListViewModel: ConversationsListViewModel
|
||||
|
||||
@State private var addParticipantsViewModel: AddParticipantsViewModel?
|
||||
|
||||
|
|
@ -60,6 +62,7 @@ struct CallView: View {
|
|||
@State var isShowCallsListFragment: Bool = false
|
||||
@State var isShowParticipantsListFragment: Bool = false
|
||||
@Binding var isShowStartCallFragment: Bool
|
||||
@Binding var isShowConversationFragment: Bool
|
||||
|
||||
@State var buttonSize = 60.0
|
||||
|
||||
|
|
@ -187,6 +190,19 @@ struct CallView: View {
|
|||
}
|
||||
}
|
||||
|
||||
if isShowConversationFragment && conversationViewModel.displayedConversation != nil {
|
||||
ConversationFragment(conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, isShowConversationFragment: $isShowConversationFragment)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
.zIndex(4)
|
||||
.transition(.move(edge: .bottom))
|
||||
.onDisappear {
|
||||
conversationViewModel.displayedConversation = nil
|
||||
isShowConversationFragment = false
|
||||
}
|
||||
}
|
||||
|
||||
if callViewModel.zrtpPopupDisplayed == true {
|
||||
if idiom != .pad
|
||||
&& (orientation == .landscapeLeft
|
||||
|
|
@ -2156,20 +2172,49 @@ struct CallView: View {
|
|||
HStack(spacing: 0) {
|
||||
VStack {
|
||||
Button {
|
||||
if callViewModel.isOneOneCall && callViewModel.remoteAddress != nil {
|
||||
callViewModel.createOneToOneChatRoomWith(remote: callViewModel.remoteAddress!)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.gray500)
|
||||
.frame(width: 32, height: 32)
|
||||
if !callViewModel.operationInProgress {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(callViewModel.isOneOneCall ? .white : Color.gray500)
|
||||
.frame(width: 32, height: 32)
|
||||
} else {
|
||||
ProgressView()
|
||||
.controlSize(.mini)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.frame(width: 32, height: 32, alignment: .center)
|
||||
.onDisappear {
|
||||
if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil {
|
||||
if conversationViewModel.displayedConversation != nil {
|
||||
conversationViewModel.displayedConversation = nil
|
||||
conversationViewModel.resetMessage()
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
|
||||
conversationViewModel.getMessages()
|
||||
withAnimation {
|
||||
isShowConversationFragment = true
|
||||
}
|
||||
} else {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
withAnimation {
|
||||
isShowConversationFragment = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(.white)
|
||||
.background(callViewModel.isOneOneCall ? Color.gray500 : .white)
|
||||
.cornerRadius(40)
|
||||
.disabled(true)
|
||||
.disabled(!callViewModel.isOneOneCall)
|
||||
|
||||
Text("Messages")
|
||||
.foregroundStyle(.white)
|
||||
|
|
@ -2510,20 +2555,49 @@ struct CallView: View {
|
|||
|
||||
VStack {
|
||||
Button {
|
||||
if callViewModel.isOneOneCall && callViewModel.remoteAddress != nil {
|
||||
callViewModel.createOneToOneChatRoomWith(remote: callViewModel.remoteAddress!)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(Color.gray500)
|
||||
.frame(width: 32, height: 32)
|
||||
if !callViewModel.operationInProgress {
|
||||
Image("chat-teardrop-text")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
.foregroundStyle(callViewModel.isOneOneCall ? .white : Color.gray500)
|
||||
.frame(width: 32, height: 32)
|
||||
} else {
|
||||
ProgressView()
|
||||
.controlSize(.mini)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.frame(width: 32, height: 32, alignment: .center)
|
||||
.onDisappear {
|
||||
if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil {
|
||||
if conversationViewModel.displayedConversation != nil {
|
||||
conversationViewModel.displayedConversation = nil
|
||||
conversationViewModel.resetMessage()
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
|
||||
conversationViewModel.getMessages()
|
||||
withAnimation {
|
||||
isShowConversationFragment = true
|
||||
}
|
||||
} else {
|
||||
conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!)
|
||||
withAnimation {
|
||||
isShowConversationFragment = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(PressedButtonStyle(buttonSize: buttonSize))
|
||||
.frame(width: buttonSize, height: buttonSize)
|
||||
.background(.white)
|
||||
.background(callViewModel.isOneOneCall ? Color.gray500 : .white)
|
||||
.cornerRadius(40)
|
||||
.disabled(true)
|
||||
.disabled(!callViewModel.isOneOneCall)
|
||||
|
||||
Text("Messages")
|
||||
.foregroundStyle(.white)
|
||||
|
|
@ -2725,7 +2799,14 @@ struct PressedButtonStyle: ButtonStyle {
|
|||
}
|
||||
|
||||
#Preview {
|
||||
CallView(callViewModel: CallViewModel(), fullscreenVideo: .constant(false), isShowStartCallFragment: .constant(false))
|
||||
CallView(
|
||||
callViewModel: CallViewModel(),
|
||||
conversationViewModel: ConversationViewModel(),
|
||||
conversationsListViewModel: ConversationsListViewModel(),
|
||||
fullscreenVideo: .constant(false),
|
||||
isShowStartCallFragment: .constant(false),
|
||||
isShowConversationFragment: .constant(false)
|
||||
)
|
||||
}
|
||||
// swiftlint:enable type_body_length
|
||||
// swiftlint:enable line_length
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ class CallViewModel: ObservableObject {
|
|||
@Published var letters3: String = "CC"
|
||||
@Published var letters4: String = "DD"
|
||||
|
||||
@Published var operationInProgress: Bool = false
|
||||
@Published var displayedConversation: ConversationModel?
|
||||
|
||||
private var chatRoomSuscriptions = Set<AnyCancellable?>()
|
||||
|
||||
init() {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .voiceChat, options: .allowBluetooth)
|
||||
|
|
@ -1163,5 +1168,197 @@ class CallViewModel: ObservableObject {
|
|||
|
||||
Log.info("\(CallViewModel.TAG) \(list.count) participants added to conference")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// swiftlint:enable type_body_length
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ struct ContentView: View {
|
|||
@State var isShowSendCancelMeetingNotificationPopup = false
|
||||
@State var isShowSipAddressesPopup = false
|
||||
@State var isShowSipAddressesPopupType = 0 //0 to call, 1 to message, 2 to video call
|
||||
@State var isShowConversationFragment = false
|
||||
|
||||
@State var fullscreenVideo = false
|
||||
|
||||
|
|
@ -77,7 +78,7 @@ struct ContentView: View {
|
|||
|
||||
GeometryReader { geometry in
|
||||
VStack(spacing: 0) {
|
||||
if telecomManager.callInProgress && !fullscreenVideo && ((!telecomManager.callDisplayed && callViewModel.callsCounter == 1) || callViewModel.callsCounter > 1) {
|
||||
if (telecomManager.callInProgress && !fullscreenVideo && ((!telecomManager.callDisplayed && callViewModel.callsCounter == 1) || callViewModel.callsCounter > 1)) || isShowConversationFragment {
|
||||
HStack {
|
||||
Image("phone")
|
||||
.renderingMode(.template)
|
||||
|
|
@ -771,7 +772,7 @@ struct ContentView: View {
|
|||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
} else if self.index == 1 {
|
||||
if historyViewModel.displayedCall!.avatarModel != nil {
|
||||
if historyViewModel.displayedCall != nil && historyViewModel.displayedCall!.avatarModel != nil {
|
||||
HistoryContactFragment(
|
||||
contactAvatarModel: historyViewModel.displayedCall!.avatarModel!,
|
||||
historyViewModel: historyViewModel,
|
||||
|
|
@ -787,7 +788,7 @@ struct ContentView: View {
|
|||
.ignoresSafeArea(.keyboard)
|
||||
}
|
||||
} else if self.index == 2 {
|
||||
ConversationFragment(conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel)
|
||||
ConversationFragment(conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, isShowConversationFragment: $isShowConversationFragment)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.gray100)
|
||||
.ignoresSafeArea(.keyboard)
|
||||
|
|
@ -1034,14 +1035,12 @@ struct ContentView: View {
|
|||
.onDisappear {
|
||||
if contactViewModel.displayedConversation != nil {
|
||||
contactViewModel.indexDisplayedFriend = nil
|
||||
historyViewModel.displayedCall = nil
|
||||
index = 2
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
withAnimation {
|
||||
self.conversationViewModel.changeDisplayedChatRoom(conversationModel: contactViewModel.displayedConversation!)
|
||||
}
|
||||
contactViewModel.displayedConversation = nil
|
||||
historyViewModel.displayedConversation = nil
|
||||
}
|
||||
} else if historyViewModel.displayedConversation != nil {
|
||||
historyViewModel.displayedCall = nil
|
||||
|
|
@ -1105,7 +1104,7 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
if telecomManager.callDisplayed && ((telecomManager.callInProgress && telecomManager.outgoingCallStarted) || telecomManager.callConnected) && !telecomManager.meetingWaitingRoomDisplayed {
|
||||
CallView(callViewModel: callViewModel, fullscreenVideo: $fullscreenVideo, isShowStartCallFragment: $isShowStartCallFragment)
|
||||
CallView(callViewModel: callViewModel, conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, fullscreenVideo: $fullscreenVideo, isShowStartCallFragment: $isShowStartCallFragment, isShowConversationFragment: $isShowConversationFragment)
|
||||
.zIndex(5)
|
||||
.transition(.scale.combined(with: .move(edge: .top)))
|
||||
.onAppear {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ struct ConversationFragment: View {
|
|||
|
||||
@State private var mediasIsLoading = false
|
||||
|
||||
@Binding var isShowConversationFragment: Bool
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
|
|
@ -60,8 +62,8 @@ struct ConversationFragment: View {
|
|||
.frame(height: 0)
|
||||
|
||||
HStack {
|
||||
if !(orientation == .landscapeLeft || orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) {
|
||||
if (!(orientation == .landscapeLeft || orientation == .landscapeRight
|
||||
|| UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height)) || isShowConversationFragment {
|
||||
Image("caret-left")
|
||||
.renderingMode(.template)
|
||||
.resizable()
|
||||
|
|
@ -72,6 +74,9 @@ struct ConversationFragment: View {
|
|||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
if isShowConversationFragment {
|
||||
isShowConversationFragment = false
|
||||
}
|
||||
conversationViewModel.displayedConversation = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue