Fix SIP contacts filter

This commit is contained in:
Benoit Martins 2025-11-17 13:44:59 +01:00
parent bcee4439f5
commit 4fbb43f38c
9 changed files with 305 additions and 254 deletions

View file

@ -188,6 +188,7 @@
"contacts_list_favourites_title" = "Favourites";
"contacts_list_filter_popup_see_all" = "See all";
"contacts_list_filter_popup_see_linphone_only" = "See %@ contacts";
"contacts_list_filter_popup_see_sip_only" = "See SIP contacts";
"conversation_action_call" = "Call";
"conversation_action_configure_ephemeral_messages" = "Configure ephemeral messages";
"conversation_action_delete" = "Delete conversation";

View file

@ -188,6 +188,7 @@
"contacts_list_favourites_title" = "Favoris";
"contacts_list_filter_popup_see_all" = "Tous les contacts";
"contacts_list_filter_popup_see_linphone_only" = "Contacts %@";
"contacts_list_filter_popup_see_sip_only" = "Contacts SIP";
"conversation_action_call" = "Appeler";
"conversation_action_configure_ephemeral_messages" = "Configurer les messages éphémères";
"conversation_action_delete" = "Supprimer la conversation";

View file

@ -24,6 +24,7 @@ struct ContactsInnerFragment: View {
@ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
@ObservedObject var contactsManager = ContactsManager.shared
@ObservedObject var magicSearch = MagicSearchSingleton.shared
@EnvironmentObject var contactsListViewModel: ContactsListViewModel
@ -33,76 +34,84 @@ struct ContactsInnerFragment: View {
@Binding var text: String
var body: some View {
VStack(alignment: .leading) {
if contactsManager.avatarListModel.contains(where: { $0.starred }) {
HStack(alignment: .center) {
Text("contacts_list_favourites_title")
.default_text_style_800(styleSize: 16)
ZStack {
VStack(alignment: .leading) {
if contactsManager.avatarListModel.contains(where: { $0.starred }) {
HStack(alignment: .center) {
Text("contacts_list_favourites_title")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
Image(isFavoriteOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
.padding(.top, 10)
.padding(.horizontal, 16)
.background(.white)
.onTapGesture {
withAnimation {
isFavoriteOpen.toggle()
Image(isFavoriteOpen ? "caret-up" : "caret-down")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 25, height: 25, alignment: .leading)
.padding(.all, 10)
}
}
if isFavoriteOpen {
FavoriteContactsListFragment(showingSheet: $showingSheet)
.zIndex(-1)
.transition(.move(edge: .top))
}
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.top, 10)
.padding(.horizontal, 16)
}
VStack {
List {
ContactsListFragment(showingSheet: $showingSheet, startCallFunc: {_ in })}
.safeAreaInset(edge: .top, content: {
Spacer()
.frame(height: 12)
})
.listStyle(.plain)
.if(sharedMainViewModel.cardDavFriendsListsCount > 0) { view in
view.refreshable {
contactsManager.refreshCardDavContacts()
}
}
.overlay(
VStack {
if contactsManager.avatarListModel.isEmpty {
Spacer()
Image("illus-belledonne")
.resizable()
.scaledToFit()
.clipped()
.padding(.all)
Text(!text.isEmpty ? "list_filter_no_result_found" : "contacts_list_empty")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
.padding(.top, 10)
.padding(.horizontal, 16)
.background(.white)
.onTapGesture {
withAnimation {
isFavoriteOpen.toggle()
}
}
.padding(.all)
)
if isFavoriteOpen {
FavoriteContactsListFragment(showingSheet: $showingSheet)
.zIndex(-1)
.transition(.move(edge: .top))
}
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.top, 10)
.padding(.horizontal, 16)
}
VStack {
List {
ContactsListFragment(showingSheet: $showingSheet, startCallFunc: {_ in })}
.safeAreaInset(edge: .top, content: {
Spacer()
.frame(height: 12)
})
.listStyle(.plain)
.if(sharedMainViewModel.cardDavFriendsListsCount > 0) { view in
view.refreshable {
contactsManager.refreshCardDavContacts()
}
}
.overlay(
VStack {
if contactsManager.avatarListModel.isEmpty {
Spacer()
Image("illus-belledonne")
.resizable()
.scaledToFit()
.clipped()
.padding(.all)
Text(!text.isEmpty ? "list_filter_no_result_found" : "contacts_list_empty")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
}
.padding(.all)
)
}
}
if magicSearch.isLoading {
ProgressView()
.controlSize(.large)
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
}
}
.navigationBarHidden(true)

View file

@ -544,7 +544,7 @@ struct ContentView: View {
magicSearch.searchForContacts()
} label: {
HStack {
Text(String(format: String(localized: "contacts_list_filter_popup_see_linphone_only"), Bundle.main.displayName))
Text(magicSearch.domainDefaultAccount == "*" ? String(localized: "contacts_list_filter_popup_see_sip_only") : String(format: String(localized: "contacts_list_filter_popup_see_linphone_only"), Bundle.main.displayName))
Spacer()
if !magicSearch.allContact {
Image("green-check")

View file

@ -141,50 +141,58 @@ struct ConversationForwardMessageFragment: View {
.padding(.vertical)
.padding(.horizontal)
ScrollView {
if !conversationForwardMessageViewModel.conversationsList.isEmpty {
HStack(alignment: .center) {
Text("bottom_navigation_conversations_label")
.default_text_style_800(styleSize: 16)
ZStack {
ScrollView {
if !conversationForwardMessageViewModel.conversationsList.isEmpty {
HStack(alignment: .center) {
Text("bottom_navigation_conversations_label")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
conversationsList
}
.padding(.vertical, 10)
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
}
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
withAnimation {
conversationForwardMessageViewModel.createOneToOneChatRoomWith(remote: addr)
}
})
.padding(.horizontal, 16)
conversationsList
if !contactsManager.lastSearchSuggestions.isEmpty {
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
}
}
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
}
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
withAnimation {
conversationForwardMessageViewModel.createOneToOneChatRoomWith(remote: addr)
}
})
.padding(.horizontal, 16)
if !contactsManager.lastSearchSuggestions.isEmpty {
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
if magicSearch.isLoading {
ProgressView()
.controlSize(.large)
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
}
}
}

View file

@ -170,34 +170,42 @@ struct StartConversationFragment: View {
)
}
ScrollView {
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
ZStack {
ScrollView {
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
}
.padding(.vertical, 10)
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
startConversationViewModel.createOneToOneChatRoomWith(remote: addr)
})
.padding(.horizontal, 16)
if !contactsManager.lastSearchSuggestions.isEmpty {
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
}
}
ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in
startConversationViewModel.createOneToOneChatRoomWith(remote: addr)
})
.padding(.horizontal, 16)
if !contactsManager.lastSearchSuggestions.isEmpty {
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
if magicSearch.isLoading {
ProgressView()
.controlSize(.large)
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
}
}
}

View file

@ -235,75 +235,83 @@ struct StartCallFragment: View {
)
}
ScrollView {
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
ZStack {
ScrollView {
if !ContactsManager.shared.lastSearch.isEmpty {
HStack(alignment: .center) {
Text("contacts_list_all_contacts_title")
.default_text_style_800(styleSize: 16)
Spacer()
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
}
.padding(.vertical, 10)
ContactsListFragment(showingSheet: .constant(false)
, startCallFunc: { addr in
if callViewModel.isTransferInsteadCall {
showingDialer = false
startCallViewModel.searchField = ""
magicSearch.currentFilter = ""
magicSearch.searchForContacts()
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
callViewModel.blindTransferCallTo(toAddress: addr)
}
} else {
showingDialer = false
startCallViewModel.searchField = ""
magicSearch.currentFilter = ""
magicSearch.searchForContacts()
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
telecomManager.doCallOrJoinConf(address: addr)
}
}
})
.padding(.horizontal, 16)
if !contactsManager.lastSearchSuggestions.isEmpty {
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
}
}
ContactsListFragment(showingSheet: .constant(false)
, startCallFunc: { addr in
if callViewModel.isTransferInsteadCall {
showingDialer = false
startCallViewModel.searchField = ""
magicSearch.currentFilter = ""
magicSearch.searchForContacts()
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
callViewModel.blindTransferCallTo(toAddress: addr)
}
} else {
showingDialer = false
startCallViewModel.searchField = ""
magicSearch.currentFilter = ""
magicSearch.searchForContacts()
if callViewModel.isTransferInsteadCall == true {
callViewModel.isTransferInsteadCall = false
}
resetCallView()
delayColorDismiss()
withAnimation {
isShowStartCallFragment.toggle()
telecomManager.doCallOrJoinConf(address: addr)
}
}
})
.padding(.horizontal, 16)
if !contactsManager.lastSearchSuggestions.isEmpty {
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
if magicSearch.isLoading {
ProgressView()
.controlSize(.large)
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
}
}
}

View file

@ -167,76 +167,84 @@ struct AddParticipantsFragment: View {
.padding(.bottom)
.padding(.horizontal)
ScrollView {
ForEach(0..<contactsManager.avatarListModel.count, id: \.self) { index in
HStack {
ZStack {
ScrollView {
ForEach(0..<contactsManager.avatarListModel.count, id: \.self) { index in
HStack {
if index == 0
|| contactsManager.avatarListModel[index].name.lowercased().folding(
options: .diacriticInsensitive,
locale: .current
).first
!= contactsManager.avatarListModel[index-1].name.lowercased().folding(
options: .diacriticInsensitive,
locale: .current
).first {
Text(
String(
(contactsManager.avatarListModel[index].name.uppercased().folding(
options: .diacriticInsensitive,
locale: .current
).first)!))
.contact_text_style_500(styleSize: 20)
.frame(width: 18)
.padding(.leading, 5)
.padding(.trailing, 5)
} else {
Text("")
HStack {
if index == 0
|| contactsManager.avatarListModel[index].name.lowercased().folding(
options: .diacriticInsensitive,
locale: .current
).first
!= contactsManager.avatarListModel[index-1].name.lowercased().folding(
options: .diacriticInsensitive,
locale: .current
).first {
Text(
String(
(contactsManager.avatarListModel[index].name.uppercased().folding(
options: .diacriticInsensitive,
locale: .current
).first)!))
.contact_text_style_500(styleSize: 20)
.frame(width: 18)
.padding(.leading, 5)
.padding(.trailing, 5)
}
} else {
Text("")
.contact_text_style_500(styleSize: 20)
.frame(width: 18)
.padding(.leading, 5)
.padding(.trailing, 5)
}
Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 50)
Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 50)
Text(contactsManager.avatarListModel[index].name)
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.orangeMain500)
if addParticipantsViewModel.participantsToAdd.contains(where: {
$0.address.asStringUriOnly() == contactsManager.avatarListModel[index].address
}) {
Image("check")
.renderingMode(.template)
.resizable()
Text(contactsManager.avatarListModel[index].name)
.default_text_style(styleSize: 16)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25)
.padding(.horizontal)
if addParticipantsViewModel.participantsToAdd.contains(where: {
$0.address.asStringUriOnly() == contactsManager.avatarListModel[index].address
}) {
Image("check")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 25, height: 25)
.padding(.horizontal)
}
}
}
}
.background(.white)
.onTapGesture {
if let addr = try? Factory.Instance.createAddress(addr: contactsManager.avatarListModel[index].address) {
addParticipantsViewModel.selectParticipant(addr: addr)
.background(.white)
.onTapGesture {
if let addr = try? Factory.Instance.createAddress(addr: contactsManager.avatarListModel[index].address) {
addParticipantsViewModel.selectParticipant(addr: addr)
}
}
.buttonStyle(.borderless)
.listRowSeparator(.hidden)
}
.buttonStyle(.borderless)
.listRowSeparator(.hidden)
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
}
HStack(alignment: .center) {
Text("generic_address_picker_suggestions_list_title")
.default_text_style_800(styleSize: 16)
Spacer()
if magicSearch.isLoading {
ProgressView()
.controlSize(.large)
.progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500))
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
suggestionsList
}
}
Button {

View file

@ -39,7 +39,7 @@ final class MagicSearchSingleton: ObservableObject {
@Published var allContact = false
let allContactKey = "all_contact"
private var domainDefaultAccount = ""
var domainDefaultAccount = ""
var searchDelegate: MagicSearchDelegate?
@ -49,6 +49,8 @@ final class MagicSearchSingleton: ObservableObject {
let linphoneAddressBookFriendList = "Linphone address-book"
let tempRemoteAddressBookFriendList = "TempRemoteDirectoryContacts address-book"
@Published var isLoading = false
func destroyMagicSearch() {
magicSearch = nil
}
@ -62,7 +64,7 @@ final class MagicSearchSingleton: ObservableObject {
}
coreContext.doOnCoreQueue { core in
self.domainDefaultAccount = core.defaultAccount?.params?.domain ?? ""
self.domainDefaultAccount = (core.defaultAccount?.params?.domain?.contains("sip.linphone.org") == true) ? (core.defaultAccount?.params?.domain ?? "") : "*"
self.magicSearch = try? core.createMagicSearch()
@ -185,6 +187,8 @@ final class MagicSearchSingleton: ObservableObject {
NotificationCenter.default.post(name: NSNotification.Name("ContactLoaded"), object: nil)
}
self.isLoading = false
self.contactLoadedDebounceWorkItem = workItem
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: workItem)
}
@ -192,6 +196,10 @@ final class MagicSearchSingleton: ObservableObject {
func searchForContacts() {
coreContext.doOnCoreQueue { _ in
DispatchQueue.main.async {
self.isLoading = true
}
var needResetCache = false
if let oldFilter = self.previousFilter {