mirror of
https://gitlab.linphone.org/BC/public/linphone-iphone.git
synced 2026-04-17 11:48:27 +00:00
Add contacts and suggestions to history and conversation views
This commit is contained in:
parent
9364e7f196
commit
6c5bf43062
10 changed files with 537 additions and 54 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
|
||||
public enum AppGitInfo {
|
||||
public static let branch = "master"
|
||||
public static let commit = "cdde88e32"
|
||||
public static let branch = "feature/contacts_and_suggestions_list"
|
||||
public static let commit = "c3f95fe23"
|
||||
public static let tag = "6.1.0-alpha"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ struct ContentView: View {
|
|||
@State private var searchIsActive = false
|
||||
@State private var text = ""
|
||||
@FocusState private var focusedField: Bool
|
||||
|
||||
@State private var showingDialer = false
|
||||
@State var isMenuOpen = false
|
||||
@State var isShowDeleteContactPopup = false
|
||||
|
|
@ -320,6 +321,8 @@ struct ContentView: View {
|
|||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 0)
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
|
|
@ -341,7 +344,8 @@ struct ContentView: View {
|
|||
}
|
||||
})
|
||||
.padding(.top)
|
||||
.frame(height: geometry.size.height/4)
|
||||
|
||||
Spacer()
|
||||
|
||||
ZStack {
|
||||
if SharedMainViewModel.shared.missedCallsCount > 0 {
|
||||
|
|
@ -365,6 +369,8 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 1)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
|
|
@ -390,7 +396,8 @@ struct ContentView: View {
|
|||
})
|
||||
.padding(.top)
|
||||
}
|
||||
.frame(height: geometry.size.height/4)
|
||||
|
||||
Spacer()
|
||||
|
||||
if !sharedMainViewModel.disableChatFeature {
|
||||
ZStack {
|
||||
|
|
@ -415,6 +422,8 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 2)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
|
|
@ -438,11 +447,14 @@ struct ContentView: View {
|
|||
})
|
||||
.padding(.top)
|
||||
}
|
||||
.frame(height: geometry.size.height/4)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
if !sharedMainViewModel.disableMeetingFeature {
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
|
|
@ -464,13 +476,13 @@ struct ContentView: View {
|
|||
}
|
||||
})
|
||||
.padding(.top)
|
||||
.frame(height: geometry.size.height/4)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: 75, height: geometry.size.height)
|
||||
.padding(.bottom, geometry.safeAreaInsets.bottom)
|
||||
.padding(.leading,
|
||||
orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0
|
||||
? -geometry.safeAreaInsets.leading
|
||||
|
|
@ -690,10 +702,12 @@ struct ContentView: View {
|
|||
|
||||
text = ""
|
||||
|
||||
if sharedMainViewModel.indexView == 0 {
|
||||
if sharedMainViewModel.indexView != 3 {
|
||||
magicSearch.currentFilter = ""
|
||||
magicSearch.searchForContacts()
|
||||
} else if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
}
|
||||
|
||||
if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
historyListVM.resetFilterCallLogs()
|
||||
} else if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 {
|
||||
conversationsListVM.resetFilterConversations()
|
||||
|
|
@ -735,10 +749,12 @@ struct ContentView: View {
|
|||
self.focusedField = true
|
||||
}
|
||||
.onChange(of: text) { newValue in
|
||||
if sharedMainViewModel.indexView == 0 {
|
||||
if sharedMainViewModel.indexView != 3 {
|
||||
magicSearch.currentFilter = newValue
|
||||
magicSearch.searchForContacts()
|
||||
} else if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
}
|
||||
|
||||
if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
if text.isEmpty {
|
||||
historyListVM.resetFilterCallLogs()
|
||||
} else {
|
||||
|
|
@ -755,6 +771,34 @@ struct ContentView: View {
|
|||
meetingsListVM.computeMeetingsList()
|
||||
}
|
||||
}
|
||||
.onChange(of: isShowStartCallFragment) { isShowStartCallFragmentNewValue in
|
||||
if isShowStartCallFragmentNewValue == false && !text.isEmpty {
|
||||
if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
magicSearch.currentFilter = text
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if text.isEmpty {
|
||||
historyListVM.resetFilterCallLogs()
|
||||
} else {
|
||||
historyListVM.filterCallLogs(filter: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: isShowStartConversationFragment) { isShowStartConversationFragmentNewValue in
|
||||
if isShowStartConversationFragmentNewValue == false && !text.isEmpty {
|
||||
if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 {
|
||||
magicSearch.currentFilter = text
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if text.isEmpty {
|
||||
conversationsListVM.resetFilterConversations()
|
||||
} else {
|
||||
conversationsListVM.filterConversations(filter: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TextEditor(text: Binding(
|
||||
get: {
|
||||
|
|
@ -777,18 +821,56 @@ struct ContentView: View {
|
|||
self.focusedField = true
|
||||
}
|
||||
.onChange(of: text) { newValue in
|
||||
if sharedMainViewModel.indexView == 0 {
|
||||
if sharedMainViewModel.indexView != 3 {
|
||||
magicSearch.currentFilter = newValue
|
||||
magicSearch.searchForContacts()
|
||||
} else if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
historyListVM.filterCallLogs(filter: text)
|
||||
}
|
||||
|
||||
if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
if text.isEmpty {
|
||||
historyListVM.resetFilterCallLogs()
|
||||
} else {
|
||||
historyListVM.filterCallLogs(filter: text)
|
||||
}
|
||||
} else if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 {
|
||||
conversationsListVM.filterConversations(filter: text)
|
||||
if text.isEmpty {
|
||||
conversationsListVM.resetFilterConversations()
|
||||
} else {
|
||||
conversationsListVM.filterConversations(filter: text)
|
||||
}
|
||||
} else if let meetingsListVM = meetingsListViewModel, sharedMainViewModel.indexView == 3 {
|
||||
meetingsListVM.currentFilter = text
|
||||
meetingsListVM.computeMeetingsList()
|
||||
}
|
||||
}
|
||||
.onChange(of: isShowStartCallFragment) { isShowStartCallFragmentNewValue in
|
||||
if isShowStartCallFragmentNewValue == false && !text.isEmpty {
|
||||
if let historyListVM = historyListViewModel, sharedMainViewModel.indexView == 1 {
|
||||
magicSearch.currentFilter = text
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if text.isEmpty {
|
||||
historyListVM.resetFilterCallLogs()
|
||||
} else {
|
||||
historyListVM.filterCallLogs(filter: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: isShowStartConversationFragment) { isShowStartConversationFragmentNewValue in
|
||||
if isShowStartConversationFragmentNewValue == false && !text.isEmpty {
|
||||
if let conversationsListVM = conversationsListViewModel, sharedMainViewModel.indexView == 2 {
|
||||
magicSearch.currentFilter = text
|
||||
magicSearch.searchForContacts()
|
||||
|
||||
if text.isEmpty {
|
||||
conversationsListVM.resetFilterConversations()
|
||||
} else {
|
||||
conversationsListVM.filterConversations(filter: text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
|
|
@ -874,6 +956,8 @@ struct ContentView: View {
|
|||
Group {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 0)
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
|
|
@ -921,6 +1005,8 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 1)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedConversation = nil
|
||||
|
|
@ -973,6 +1059,8 @@ struct ContentView: View {
|
|||
}
|
||||
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 2)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
|
|
@ -1002,6 +1090,8 @@ struct ContentView: View {
|
|||
if !sharedMainViewModel.disableMeetingFeature {
|
||||
Spacer()
|
||||
Button(action: {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 3)
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
|
|
@ -1299,13 +1389,17 @@ struct ContentView: View {
|
|||
|
||||
if sharedMainViewModel.operationInProgress {
|
||||
PopupLoadingView()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad && (orientation == .landscapeLeft || orientation == .landscapeRight || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) ? geometry.safeAreaInsets.bottom : 0)
|
||||
.background(.black.opacity(0.65))
|
||||
.zIndex(3)
|
||||
.onDisappear {
|
||||
if let contactsListVM = contactsListViewModel, let displayedConversation = contactsListVM.displayedConversation {
|
||||
|
||||
if !sharedMainViewModel.disableChatFeature {
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 2)
|
||||
|
||||
|
|
@ -1330,6 +1424,8 @@ struct ContentView: View {
|
|||
} else if let historyListVM = historyListViewModel, let displayedConversation = historyListVM.displayedConversation {
|
||||
|
||||
if !sharedMainViewModel.disableChatFeature {
|
||||
resetFilter()
|
||||
|
||||
sharedMainViewModel.displayedFriend = nil
|
||||
sharedMainViewModel.displayedCall = nil
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 2)
|
||||
|
|
@ -1352,6 +1448,15 @@ struct ContentView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if let conversationsListVM = conversationsListViewModel {
|
||||
conversationsListVM.currentFilter = ""
|
||||
|
||||
self.resetFilter()
|
||||
|
||||
if let displayedConversation = conversationsListVM.displayedConversation {
|
||||
conversationsListVM.changeDisplayedChatRoom(conversationModel: displayedConversation)
|
||||
conversationsListVM.displayedConversation = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1404,7 +1509,7 @@ struct ContentView: View {
|
|||
.transition(.move(edge: .trailing))
|
||||
}
|
||||
|
||||
if let meetingsListVM = meetingsListViewModel, isShowSendCancelMeetingNotificationPopup {
|
||||
if let meetingsListVM = meetingsListViewModel, isShowSendCancelMeetingNotificationPopup {
|
||||
PopupView(
|
||||
isShowPopup: $isShowSendCancelMeetingNotificationPopup,
|
||||
title: Text("meeting_schedule_cancel_dialog_title"),
|
||||
|
|
@ -1668,6 +1773,7 @@ struct ContentView: View {
|
|||
.onChange(of: navigationManager.selectedCallId) { newCallId in
|
||||
if newCallId != nil {
|
||||
if !sharedMainViewModel.disableChatFeature {
|
||||
resetFilter()
|
||||
sharedMainViewModel.changeIndexView(indexViewInt: 2)
|
||||
}
|
||||
}
|
||||
|
|
@ -1760,6 +1866,17 @@ struct ContentView: View {
|
|||
self.sideMenuIsOpen.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
func resetFilter() {
|
||||
self.text = ""
|
||||
self.focusedField = false
|
||||
self.searchIsActive = false
|
||||
|
||||
if !magicSearch.currentFilter.isEmpty {
|
||||
magicSearch.currentFilter = ""
|
||||
magicSearch.searchForContacts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ContactsContainer: View {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ struct ConversationsView: View {
|
|||
|
||||
#Preview {
|
||||
ConversationsListFragment(
|
||||
showingSheet: .constant(false),
|
||||
text: .constant("")
|
||||
text: .constant(""),
|
||||
showingSheet: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,13 +25,14 @@ struct ConversationsFragment: View {
|
|||
|
||||
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
||||
|
||||
@State var showingSheet: Bool = false
|
||||
@Binding var text: String
|
||||
|
||||
@State var showingSheet: Bool = false
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if #available(iOS 16.0, *), idiom != .pad {
|
||||
ConversationsListFragment(showingSheet: $showingSheet, text: $text)
|
||||
ConversationsListFragment(text: $text, showingSheet: $showingSheet)
|
||||
.sheet(isPresented: $showingSheet) {
|
||||
ConversationsListBottomSheet(
|
||||
showingSheet: $showingSheet
|
||||
|
|
@ -43,7 +44,7 @@ struct ConversationsFragment: View {
|
|||
)
|
||||
}
|
||||
} else {
|
||||
ConversationsListFragment(showingSheet: $showingSheet, text: $text)
|
||||
ConversationsListFragment(text: $text, showingSheet: $showingSheet)
|
||||
.halfSheet(showSheet: $showingSheet) {
|
||||
ConversationsListBottomSheet(
|
||||
showingSheet: $showingSheet
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@ struct ConversationsListFragment: View {
|
|||
|
||||
@EnvironmentObject var navigationManager: NavigationManager
|
||||
|
||||
@ObservedObject var contactsManager = ContactsManager.shared
|
||||
|
||||
@EnvironmentObject var conversationsListViewModel: ConversationsListViewModel
|
||||
|
||||
@Binding var showingSheet: Bool
|
||||
@Binding var text: String
|
||||
@Binding var showingSheet: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -42,6 +44,34 @@ struct ConversationsListFragment: View {
|
|||
text: $text
|
||||
)
|
||||
}
|
||||
|
||||
if !conversationsListViewModel.currentFilter.isEmpty {
|
||||
if !contactsManager.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
|
||||
withAnimation {
|
||||
conversationsListViewModel.createOneToOneChatRoomWith(remote: addr)
|
||||
}
|
||||
})
|
||||
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .top, content: {
|
||||
Spacer()
|
||||
|
|
@ -50,7 +80,13 @@ struct ConversationsListFragment: View {
|
|||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if conversationsListViewModel.conversationsList.isEmpty {
|
||||
if conversationsListViewModel.conversationsList.isEmpty &&
|
||||
(
|
||||
conversationsListViewModel.currentFilter.isEmpty ||
|
||||
(!conversationsListViewModel.currentFilter.isEmpty &&
|
||||
contactsManager.lastSearch.isEmpty &&
|
||||
contactsManager.lastSearchSuggestions.isEmpty)
|
||||
) {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
|
|
@ -65,6 +101,11 @@ struct ConversationsListFragment: View {
|
|||
}
|
||||
.padding(.all)
|
||||
)
|
||||
.onDisappear {
|
||||
if !conversationsListViewModel.currentFilter.isEmpty {
|
||||
conversationsListViewModel.resetFilterConversations()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
|
|
@ -77,6 +118,69 @@ struct ConversationsListFragment: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
var suggestionsList: some View {
|
||||
ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in
|
||||
Button {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
withAnimation {
|
||||
conversationsListViewModel.createOneToOneChatRoomWith(remote: address)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
if contactsManager.lastSearchSuggestions[index].address!.domain != AppServices.corePreferences.defaultDomain {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
let nameTmp = address.displayName
|
||||
?? address.username
|
||||
?? String(address.asStringUriOnly().dropFirst(4))
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: nameTmp,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(nameTmp)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text("username_error")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ConversationRow: View {
|
||||
|
|
@ -244,7 +348,7 @@ struct ConversationRow: View {
|
|||
|
||||
#Preview {
|
||||
ConversationsListFragment(
|
||||
showingSheet: .constant(false),
|
||||
text: .constant("")
|
||||
text: .constant(""),
|
||||
showingSheet: .constant(false)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,13 @@ class ConversationsListViewModel: ObservableObject {
|
|||
|
||||
private var coreConversationDelegate: CoreDelegate?
|
||||
|
||||
@Published var currentFilter: String = ""
|
||||
@Published var displayedConversation: ConversationModel?
|
||||
@Published var conversationsList: [ConversationModel] = []
|
||||
|
||||
var selectedConversation: ConversationModel?
|
||||
|
||||
var currentFilter: String = ""
|
||||
private var chatRoomDelegate: ChatRoomDelegate?
|
||||
|
||||
init() {
|
||||
computeChatRoomsList()
|
||||
|
|
@ -428,6 +430,7 @@ class ConversationsListViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func resetFilterConversations() {
|
||||
currentFilter = ""
|
||||
filterConversations(filter: "")
|
||||
}
|
||||
|
||||
|
|
@ -479,5 +482,163 @@ class ConversationsListViewModel: ObservableObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createOneToOneChatRoomWith(remote: Address) {
|
||||
CoreContext.shared.doOnCoreQueue { core in
|
||||
let account = core.defaultAccount
|
||||
if account == nil {
|
||||
Log.error(
|
||||
"\(ConversationsListViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.operationInProgress = true
|
||||
}
|
||||
|
||||
do {
|
||||
let params = try core.createConferenceParams(conference: nil)
|
||||
params.chatEnabled = true
|
||||
params.groupEnabled = false
|
||||
params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "")
|
||||
params.account = account
|
||||
|
||||
guard let chatParams = params.chatParams else { return }
|
||||
chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default
|
||||
|
||||
let sameDomain = remote.domain == AppServices.corePreferences.defaultDomain && remote.domain == account!.params?.domain
|
||||
if account!.params != nil && (account!.params!.instantMessagingEncryptionMandatory && sameDomain) {
|
||||
Log.info("\(ConversationsListViewModel.TAG) Account is in secure mode & domain matches, creating an E2E encrypted conversation")
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else if account!.params != nil && (!account!.params!.instantMessagingEncryptionMandatory) {
|
||||
if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) {
|
||||
Log.info(
|
||||
"\(ConversationsListViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation"
|
||||
)
|
||||
chatParams.backend = ChatRoom.Backend.FlexisipChat
|
||||
params.securityLevel = Conference.SecurityLevel.EndToEnd
|
||||
} else {
|
||||
Log.info(
|
||||
"\(ConversationsListViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation"
|
||||
)
|
||||
chatParams.backend = ChatRoom.Backend.Basic
|
||||
params.securityLevel = Conference.SecurityLevel.None
|
||||
}
|
||||
} else {
|
||||
Log.error(
|
||||
"\(ConversationsListViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())"
|
||||
)
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
ToastViewModel.shared.show("Failed_to_create_conversation_error")
|
||||
}
|
||||
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(
|
||||
"\(ConversationsListViewModel.TAG) No existing 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it"
|
||||
)
|
||||
|
||||
do {
|
||||
let chatRoom = try core.createChatRoom(params: params, participants: participants)
|
||||
if chatParams.backend == ChatRoom.Backend.FlexisipChat {
|
||||
let state = chatRoom.state
|
||||
if state == ChatRoom.State.Created {
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(ConversationsListViewModel.TAG) 1-1 conversation \(chatRoomId) has been created")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.displayedConversation = model
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
}
|
||||
} else {
|
||||
Log.info("\(ConversationsListViewModel.TAG) Conversation isn't in Created state yet (state is \(state)), wait for it")
|
||||
self.chatRoomAddDelegate(core: core, chatRoom: chatRoom)
|
||||
}
|
||||
} else {
|
||||
let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom)
|
||||
Log.info("\(ConversationsListViewModel.TAG) Conversation successfully created \(chatRoomId)")
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.displayedConversation = model
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
Log.error("\(ConversationsListViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())")
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
ToastViewModel.shared.show("Failed_to_create_conversation_error")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.warn(
|
||||
"\(ConversationsListViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!"
|
||||
)
|
||||
let model = ConversationModel(chatRoom: existingChatRoom!)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
self.displayedConversation = model
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chatRoomAddDelegate(core: Core, chatRoom: ChatRoom) {
|
||||
self.chatRoomDelegate = ChatRoomDelegateStub(onStateChanged: { (chatRoom: ChatRoom, state: ChatRoom.State) in
|
||||
let state = chatRoom.state
|
||||
let chatRoomId = LinphoneUtils.getChatRoomId(room: chatRoom)
|
||||
if state == ChatRoom.State.CreationFailed {
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(chatRoomId) creation has failed!")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
self.chatRoomDelegate = nil
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
ToastViewModel.shared.show("Failed_to_create_conversation_error")
|
||||
}
|
||||
}
|
||||
}, onConferenceJoined: { (chatRoom: ChatRoom, _: 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")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
self.chatRoomDelegate = nil
|
||||
|
||||
let model = ConversationModel(chatRoom: chatRoom)
|
||||
DispatchQueue.main.async {
|
||||
self.displayedConversation = model
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
}
|
||||
} else if state == ChatRoom.State.CreationFailed {
|
||||
Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!")
|
||||
if let chatRoomDelegate = self.chatRoomDelegate {
|
||||
chatRoom.removeDelegate(delegate: chatRoomDelegate)
|
||||
}
|
||||
self.chatRoomDelegate = nil
|
||||
DispatchQueue.main.async {
|
||||
self.sharedMainViewModel.operationInProgress = false
|
||||
ToastViewModel.shared.show("Failed_to_create_conversation_error")
|
||||
}
|
||||
}
|
||||
})
|
||||
chatRoom.addDelegate(delegate: self.chatRoomDelegate!)
|
||||
}
|
||||
}
|
||||
// swiftlint:enable line_length
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ class StartConversationViewModel: ObservableObject {
|
|||
|
||||
@Published var participants: [SelectedAddressModel] = []
|
||||
|
||||
@Published var operationOneToOneInProgress: Bool = false
|
||||
@Published var hideGroupChatButton: Bool = false
|
||||
@Published var operationGroupInProgress: Bool = false
|
||||
@Published var operationOneToOneInProgress: Bool = false
|
||||
@Published var displayedConversation: ConversationModel?
|
||||
|
||||
@Published var hideGroupChatButton: Bool = false
|
||||
|
||||
private var chatRoomDelegate: ChatRoomDelegate?
|
||||
|
||||
|
|
|
|||
|
|
@ -22,32 +22,26 @@ import SwiftUI
|
|||
struct PopupLoadingView: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: Color.orangeMain500))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("operation_in_progress_overlay")
|
||||
.tint(Color.grayMain2c600)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 20)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
.padding(.horizontal)
|
||||
.frame(maxHeight: .infinity)
|
||||
.frame(maxWidth: .infinity)
|
||||
.shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
|
||||
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
|
||||
.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
|
||||
VStack {
|
||||
ProgressView()
|
||||
.controlSize(.large)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: Color.orangeMain500))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.top)
|
||||
.padding(.bottom)
|
||||
|
||||
Text("operation_in_progress_overlay")
|
||||
.tint(Color.grayMain2c600)
|
||||
.default_text_style(styleSize: 15)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 20)
|
||||
.background(.white)
|
||||
.cornerRadius(20)
|
||||
.padding(.horizontal)
|
||||
.shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
|
||||
.frame(maxWidth: SharedMainViewModel.shared.maxWidth)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,34 @@ struct HistoryListFragment: View {
|
|||
ForEach(historyListViewModel.callLogs) { historyModel in
|
||||
HistoryRow(historyModel: historyModel, showingSheet: $showingSheet)
|
||||
}
|
||||
|
||||
if !historyListViewModel.callLogsFilter.isEmpty {
|
||||
if !contactsManager.lastSearch.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("contacts_list_all_contacts_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
|
||||
withAnimation {
|
||||
telecomManager.doCallOrJoinConf(address: addr)
|
||||
}
|
||||
})
|
||||
|
||||
if !contactsManager.lastSearchSuggestions.isEmpty {
|
||||
HStack(alignment: .center) {
|
||||
Text("generic_address_picker_suggestions_list_title")
|
||||
.default_text_style_800(styleSize: 16)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
suggestionsList
|
||||
}
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .top, content: {
|
||||
Spacer()
|
||||
|
|
@ -46,7 +74,13 @@ struct HistoryListFragment: View {
|
|||
.listStyle(.plain)
|
||||
.overlay(
|
||||
VStack {
|
||||
if historyListViewModel.callLogs.isEmpty {
|
||||
if historyListViewModel.callLogs.isEmpty &&
|
||||
(
|
||||
historyListViewModel.callLogsFilter.isEmpty ||
|
||||
(!historyListViewModel.callLogsFilter.isEmpty &&
|
||||
contactsManager.lastSearch.isEmpty &&
|
||||
contactsManager.lastSearchSuggestions.isEmpty)
|
||||
) {
|
||||
Spacer()
|
||||
Image("illus-belledonne")
|
||||
.resizable()
|
||||
|
|
@ -62,10 +96,78 @@ struct HistoryListFragment: View {
|
|||
}
|
||||
.padding(.all)
|
||||
)
|
||||
.onDisappear {
|
||||
if !historyListViewModel.callLogsFilter.isEmpty {
|
||||
historyListViewModel.resetFilterCallLogs()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
var suggestionsList: some View {
|
||||
ForEach(0..<contactsManager.lastSearchSuggestions.count, id: \.self) { index in
|
||||
Button {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
withAnimation {
|
||||
telecomManager.doCallOrJoinConf(address: address)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if index < contactsManager.lastSearchSuggestions.count
|
||||
&& contactsManager.lastSearchSuggestions[index].address != nil {
|
||||
if contactsManager.lastSearchSuggestions[index].address!.domain != AppServices.corePreferences.defaultDomain {
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)),
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(String(contactsManager.lastSearchSuggestions[index].address!.asStringUriOnly().dropFirst(4)))
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
} else {
|
||||
if let address = contactsManager.lastSearchSuggestions[index].address {
|
||||
let nameTmp = address.displayName
|
||||
?? address.username
|
||||
?? String(address.asStringUriOnly().dropFirst(4))
|
||||
|
||||
Image(uiImage: contactsManager.textToImage(
|
||||
firstName: nameTmp,
|
||||
lastName: ""))
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(nameTmp)
|
||||
.default_text_style(styleSize: 16)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image("profil-picture-default")
|
||||
.resizable()
|
||||
.frame(width: 45, height: 45)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text("username_error")
|
||||
.default_text_style(styleSize: 16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.foregroundStyle(Color.orangeMain500)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderless)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HistoryRow: View {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ class HistoryListViewModel: ObservableObject {
|
|||
var callLogsAddressToDelete = ""
|
||||
var callLogCoreDelegate: CoreDelegate?
|
||||
|
||||
@Published var callLogsFilter = ""
|
||||
|
||||
@Published var selectedCall: HistoryModel?
|
||||
|
||||
@Published var displayedConversation: ConversationModel?
|
||||
|
|
@ -171,6 +173,7 @@ class HistoryListViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func filterCallLogs(filter: String) {
|
||||
callLogsFilter = filter
|
||||
callLogs.removeAll()
|
||||
callLogsTmp.forEach { callLog in
|
||||
if callLog.addressName.lowercased().contains(filter.lowercased()) {
|
||||
|
|
@ -180,6 +183,7 @@ class HistoryListViewModel: ObservableObject {
|
|||
}
|
||||
|
||||
func resetFilterCallLogs() {
|
||||
callLogsFilter = ""
|
||||
callLogs = callLogsTmp
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue