From 498c60740e8d7052113d7995f16435c744ef5dad Mon Sep 17 00:00:00 2001 From: Diogo Date: Mon, 16 Dec 2024 22:39:09 +0000 Subject: [PATCH] refactor --- apps/ios/Shared/Model/ChatModel.swift | 14 +- .../Views/ChatList/ChatListNavLink.swift | 162 ++++---- .../Shared/Views/ChatList/ChatListView.swift | 361 ++++++++++-------- apps/ios/SimpleXChat/ChatTypes.swift | 8 + 4 files changed, 276 insertions(+), 269 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index ed2f1a6d1a..e3295a6af6 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -103,18 +103,8 @@ class ItemsModel: ObservableObject { class ChatTagsModel: ObservableObject { static let shared = ChatTagsModel() - static let defaultTag: ChatTagFilter = .presetTag(icon: "list.bullet", activeIcon: "list.bullet", text: NSLocalizedString("All", comment: "preset chat list"), enabled: true, filter: { c in true }) - - let presetTags: [ChatTagFilter] = [ - defaultTag, - .presetTag(icon: "star", activeIcon: "star.fill", text: NSLocalizedString("Favorite", comment: "preset chat list"), enabled: false, filter: { c in c.chatSettings?.favorite ?? false }), - .presetTag(icon: "person", activeIcon: "person.fill", text: NSLocalizedString("Contacts", comment: "preset chat list"), enabled: false, filter: { if case .direct = $0 { true } else { false }}), - .presetTag(icon: "person.2", activeIcon: "person.2.fill", text: NSLocalizedString("Groups", comment: "preset chat list"), enabled: false, filter: { filterGroupChat($0) }), - .presetTag(icon: "briefcase", activeIcon: "briefcase.fill", text: NSLocalizedString("Business", comment: "preset chat list"), enabled: false, filter: { filterBusinessChat($0) }) - ] - - @Published var tags: [ChatTagFilter] = [] - @Published var selectedTag: ChatTagFilter = defaultTag + @Published var userTags: [ChatTag] = [] + @Published var activeFilter: ActiveFilter? = nil } private func filterBusinessChat(_ cInfo: ChatInfo) -> Bool { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index ac730e1ab5..a01da4b218 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -335,7 +335,7 @@ struct ChatListNavLink: View { private func setTagChatSheet(_ chat: Chat) { let fraction: Double - switch chatTagsModel.tags.count { + switch chatTagsModel.userTags.count { case 0..<4: fraction = 0.35 case 4..<9: @@ -348,7 +348,7 @@ struct ChatListNavLink: View { content: { AnyView( NavigationView { - if chatTagsModel.tags.isEmpty { + if chatTagsModel.userTags.isEmpty { ChatListTagEditor(chat: chat) } else { ChatListTag(chat: chat) @@ -585,70 +585,67 @@ struct ChatListTag: View { var body: some View { List { - if !chatTagsModel.tags.isEmpty { - ForEach(chatTagsModel.tags) { tag in - if case let .chatTag(emoji, text, tagId) = tag { - let selected = chatTagsIds.contains(tagId) - - HStack { - Text(emoji) - Text(text) - .padding(.leading, 12) - Spacer() - if chat != nil { - radioButton(selected: selected) - } - } - .onTapGesture { - if let c = chat { - if selected { - untagChat(tagId: tagId, chat: c) - } else { - // TODO: Temporary until API is final. - chatTagsIds.forEach { t in - untagChat(tagId: t, chat: c) - } - tagChat(tagId: tagId, chat: c) - } - } - } - .swipeActions(edge: .trailing, allowsFullSwipe: true) { - Button { - showAlert( - NSLocalizedString("Delete list?", comment: "alert title"), - message: NSLocalizedString("\(text) will be deleted", comment: "alert message"), - actions: {[ - UIAlertAction( - title: NSLocalizedString("Cancel", comment: "alert action"), - style: .default - ), - UIAlertAction( - title: NSLocalizedString("Delete", comment: "alert action"), - style: .destructive, - handler: { _ in - deleteTag(tagId) - } - ) - ]} - ) - } label: { - SwipeLabel(NSLocalizedString("Delete", comment: "swipe action"), systemImage: "trash.fill", inverted: false) - } - .tint(.red) - } - .swipeActions(edge: .leading, allowsFullSwipe: true) { - Button { - navigateToTagEditor = true - tagEditorNavParams = TagEditorNavParams(chat: nil, chatListTag: ChatTagData(emoji: emoji, text: text), tagId: tagId) - } label: { - SwipeLabel(NSLocalizedString("Edit", comment: "swipe action"), systemImage: "pencil", inverted: false) - } - .tint(theme.colors.primary) - } - } else { - EmptyView() + ForEach(chatTagsModel.userTags, id: \.id) { tag in + let text = tag.chatTagText + let emoji = tag.chatTagEmoji + let tagId = tag.chatTagId + let selected = chatTagsIds.contains(tagId) + + HStack { + Text(emoji) + Text(text) + .padding(.leading, 12) + Spacer() + if chat != nil { + radioButton(selected: selected) } } + .onTapGesture { + if let c = chat { + if selected { + untagChat(tagId: tagId, chat: c) + } else { + // TODO: Temporary until API is final. + chatTagsIds.forEach { t in + untagChat(tagId: t, chat: c) + } + tagChat(tagId: tagId, chat: c) + } + } + } + .swipeActions(edge: .trailing, allowsFullSwipe: true) { + Button { + showAlert( + NSLocalizedString("Delete list?", comment: "alert title"), + message: NSLocalizedString("\(text) will be deleted", comment: "alert message"), + actions: {[ + UIAlertAction( + title: NSLocalizedString("Cancel", comment: "alert action"), + style: .default + ), + UIAlertAction( + title: NSLocalizedString("Delete", comment: "alert action"), + style: .destructive, + handler: { _ in + deleteTag(tagId) + } + ) + ]} + ) + } label: { + SwipeLabel(NSLocalizedString("Delete", comment: "swipe action"), systemImage: "trash.fill", inverted: false) + } + .tint(.red) + } + .swipeActions(edge: .leading, allowsFullSwipe: true) { + Button { + navigateToTagEditor = true + tagEditorNavParams = TagEditorNavParams(chat: nil, chatListTag: ChatTagData(emoji: emoji, text: text), tagId: tagId) + } label: { + SwipeLabel(NSLocalizedString("Edit", comment: "swipe action"), systemImage: "pencil", inverted: false) + } + .tint(theme.colors.primary) + } } // isActive required since there are 2 possible ways to navigate to editor view. 1 by click, 2 by swipe in any existing tag NavigationLink(isActive: $navigateToTagEditor) { @@ -687,9 +684,7 @@ struct ChatListTag: View { ) await MainActor.run { - chatTagsModel.tags = userTags.map { - .chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId) - } + chatTagsModel.userTags = userTags updateChatTags(chat: chat, chatTags: chatTags) dismiss() } @@ -712,12 +707,10 @@ struct ChatListTag: View { ) await MainActor.run { - chatTagsModel.tags = userTags.map { - .chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId) - } - if case let .chatTag(_, _, tagId) = chatTagsModel.selectedTag, - !userTags.contains(where: { $0.chatTagId == tagId }) { - chatTagsModel.selectedTag = ChatTagsModel.defaultTag + chatTagsModel.userTags = userTags + if case let .userTag(tag) = chatTagsModel.activeFilter, + !userTags.contains(where: { $0.chatTagId == tag.chatTagId }) { + chatTagsModel.activeFilter = nil } updateChatTags(chat: chat, chatTags: chatTags) dismiss() @@ -736,17 +729,10 @@ struct ChatListTag: View { // TODO: Delete tag API Task { await MainActor.run { - chatTagsModel.tags = chatTagsModel.tags.filter({ tag in - if case let .chatTag(_, _, id) = tag { - return id != tagId - } else { - return true - } - }) - if case let .chatTag(_, _, stId) = chatTagsModel.selectedTag, stId == tagId { - chatTagsModel.selectedTag = ChatTagsModel.defaultTag + chatTagsModel.userTags = chatTagsModel.userTags.filter { $0.chatTagId != tagId } + if case let .userTag(tag) = chatTagsModel.activeFilter, tagId == tag.chatTagId { + chatTagsModel.activeFilter = nil } - m.chats.forEach { c in if var contact = c.chatInfo.contact, contact.chatTags.contains(tagId) { contact.chatTags = contact.chatTags.filter({ $0 != tagId }) @@ -882,9 +868,7 @@ struct ChatListTagEditor: View { ) await MainActor.run { - chatTagsModel.tags = userTags.map { - .chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId) - } + chatTagsModel.userTags = userTags if var contact = chat.chatInfo.contact { contact.chatTags = chatTags m.updateContact(contact) @@ -909,9 +893,9 @@ struct ChatListTagEditor: View { Task { do { await MainActor.run { - chatTagsModel.tags = chatTagsModel.tags.map { tag in - if case let .chatTag(_, _, tId) = tag, tId == tagId { - .chatTag(emoji: chatTagData.emoji, text: chatTagData.text, tagId: tId) + chatTagsModel.userTags = chatTagsModel.userTags.map { tag in + if tag.chatTagId == tagId { + ChatTag(chatTagId: tag.chatTagId, chatTagText: chatTagData.text, chatTagEmoji: chatTagData.emoji) } else { tag } diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index f8e8f1843a..f4dbb1523b 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -31,6 +31,38 @@ enum UserPickerSheet: Identifiable { } } +enum PresetTag: Identifiable, Equatable { + case contact + case group + case business + case favorite + + var id: PresetTag { self } + + var order: Int { + switch self { + case .contact: return 0 + case .group: return 1 + case .business: return 2 + case .favorite: return 3 + } + } +} + +enum ActiveFilter: Identifiable, Equatable { + case presetTag(tag: PresetTag) + case userTag(tag: ChatTag) + case unread + + var id: String { + switch self { + case let .presetTag(tag): return "preset \(tag.id)" + case let .userTag(tag): return "user \(tag.chatTagId)" + case .unread: return "unread" + } + } +} + class SaveableSettings: ObservableObject { @Published var servers: ServerSettings = ServerSettings(currUserServers: [], userServers: [], serverErrors: []) } @@ -118,6 +150,7 @@ struct ChatListView: View { @State private var scrollToSearchBar = false @State private var userPickerShown: Bool = false @State private var sheet: SomeSheet? = nil + @State private var presetTags: [PresetTag] = [] @StateObject private var chatTagsModel = ChatTagsModel.shared @AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false @@ -369,11 +402,6 @@ struct ChatListView: View { .onChange(of: chatModel.currentUser?.userId) { _ in stopAudioPlayer() } - .onChange(of: showUnreadAndFavorites) { show in - if show { - chatTagsModel.selectedTag = ChatTagsModel.defaultTag - } - } .onChange(of: scrollToSearchBar) { scrollToSearchBar in if scrollToSearchBar { Task { self.scrollToSearchBar = false } @@ -405,15 +433,14 @@ struct ChatListView: View { VoiceItemState.smallView.values.forEach { $0.audioPlayer?.stop() } VoiceItemState.smallView = [:] } - private func filteredChats() -> [Chat] { if let linkChatId = searchChatFilteredBySimplexLink { - return chatModel.chats.filter { $0.id == linkChatId } + return chatModel.chats.filter { $0.id == linkChatId} } else { let s = searchString() - return s == "" && !showUnreadAndFavorites + let chats = s == "" && !showUnreadAndFavorites ? chatModel.chats.filter { chat in - filterByTag(chat) && !chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card + return !chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card && filtered(chat) } : chatModel.chats.filter { chat in let cInfo = chat.chatInfo @@ -421,25 +448,27 @@ struct ChatListView: View { case let .direct(contact): return !contact.chatDeleted && chatContactType(chat: chat) != ContactType.card && ( s == "" - ? filterByTag(chat) && filtered(chat) + ? filtered(chat) : (viewNameContains(cInfo, s) || contact.profile.displayName.localizedLowercase.contains(s) || contact.fullName.localizedLowercase.contains(s)) ) case let .group(gInfo): return s == "" - ? filterByTag(chat) && (filtered(chat) || gInfo.membership.memberStatus == .memInvited) + ? (filtered(chat) || gInfo.membership.memberStatus == .memInvited) : viewNameContains(cInfo, s) case .local: - return s == "" ? filterByTag(chat) : viewNameContains(cInfo, s) + return s == "" || viewNameContains(cInfo, s) case .contactRequest: - return s == "" ? filterByTag(chat) : viewNameContains(cInfo, s) + return s == "" || viewNameContains(cInfo, s) case let .contactConnection(conn): - return s == "" ? filterByTag(chat) : conn.localAlias.localizedLowercase.contains(s) + return s != "" && conn.localAlias.localizedLowercase.contains(s) case .invalidJSON: return false } } + + return chats } func searchString() -> String { @@ -447,13 +476,24 @@ struct ChatListView: View { } func filtered(_ chat: Chat) -> Bool { - (chat.chatInfo.chatSettings?.favorite ?? false) || - chat.chatStats.unreadChat || - (chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0) - } - - func filterByTag(_ chat: Chat) -> Bool { - chatTagsModel.selectedTag.filter(chat.chatInfo) + switch chatTagsModel.activeFilter { + case let .presetTag(tag): + let presetTagMatches = chatPresetTagMatches(chat) + return presetTagMatches.contains(tag) + case let .userTag(tag): + switch chat.chatInfo { + case let .direct(contact): + return contact.chatTags.contains(tag.chatTagId) + case let .group(gInfo): + return gInfo.chatTags.contains(tag.chatTagId) + default: + return false + } + case .unread: + return chat.chatStats.unreadChat || chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0 + case .none: + return true + } } func viewNameContains(_ cInfo: ChatInfo, _ s: String) -> Bool { @@ -630,115 +670,66 @@ struct ChatListSearchBar: View { } } -enum ChatTagFilter: Identifiable, Equatable { - static func == (lhs: ChatTagFilter, rhs: ChatTagFilter) -> Bool { - switch (lhs, rhs) { - case let (.presetTag(icon1, activeIcon1, text1, enabled1, _), .presetTag(icon2, activeIcon2, text2, enabled2, _)): - return icon1 == icon2 && activeIcon1 == activeIcon2 && text1 == text2 && enabled1 == enabled2 - case let (.chatTag(emoji1, text1, tagId1), .chatTag(emoji2, text2, tagId2)): - return emoji1 == emoji2 && text1 == text2 && tagId1 == tagId2 - default: - return false - } - } - - case presetTag(icon: String, activeIcon: String, text: String, enabled: Bool, filter: (_ cInfo: ChatInfo) -> Bool) - case chatTag(emoji: String, text: String, tagId: Int64) - - public var id: String { - switch self { - case let .presetTag(icon, _, _, _, _): "preset \(icon)" - case let .chatTag(emoji, _, _): "chatTag \(emoji)" - } - } - - public var text: String { - switch self { - case let .presetTag(_, _, text, _, _): return text - case let .chatTag(_, text, _): return text - } - } - - public var enabled: Bool { - switch self { - case let .presetTag(_, _, _, enabled, _): return enabled - case .chatTag: return true - } - } - - public func getIcon(active: Bool) -> String { - switch self { - case let .presetTag(icon, activeIcon, _, _, _): return active ? activeIcon : icon - case let .chatTag(emoji, _, _): return emoji - } - } - - public func filter(_ cInfo: ChatInfo) -> Bool { - switch self { - case let .presetTag(_, _, _, _, filter): return filter(cInfo) - case let .chatTag(_, _, tagId): - switch cInfo { - case let .direct(contact): - return contact.chatTags.contains(tagId) - case let .group(gInfo): - return gInfo.chatTags.contains(tagId) - default: - return false - } - } - } -} - struct ChatTagsView: View { @EnvironmentObject var chatTagsModel: ChatTagsModel @EnvironmentObject var chatModel: ChatModel @State private var sheet: SomeSheet? = nil - + + var presetTags: [PresetTag] { + var matches = Set() + for chat in chatModel.chats { + matches.formUnion(chatPresetTagMatches(chat)) + + if matches.count == 4 { + break + } + } + + return Array(matches).sorted(by: { $0.order < $1.order }) + } + var body: some View { HStack { - let presetFilters = presetTagFilters() - - let enabledPresetFilters = presetFilters.filter { $0.enabled } - if enabledPresetFilters.count + chatTagsModel.tags.count <= 5 { - expandedPresetTagsFiltersView(presetFilters) + if presetTags.count + chatTagsModel.userTags.count <= 5 { + expandedPresetTagsFiltersView() } else { - collapsedTagsFilterView(enabledPresetFilters) + collapsedTagsFilterView() } - ForEach(chatTagsModel.tags, id: \.id) { tag in - let current = chatTagsModel.selectedTag == tag - let color: Color? = current ? .accentColor : nil + ForEach(chatTagsModel.userTags, id: \.id) { tag in + let current = if case let .userTag(t) = chatTagsModel.activeFilter, t == tag { + true + } else { + false + } + + let color: Color = current ? .accentColor : .secondary ZStack { - switch tag { - case .presetTag: - EmptyView() - case let .chatTag(emoji, text, _): - HStack(spacing: 4) { - Text(emoji) - ZStack { - Text(text).fontWeight(.semibold).foregroundColor(.clear) - Text(text).fontWeight(current ? .semibold : .regular).foregroundColor(color) - } - } - .onTapGesture { - setSelectedTag(tag) - } - .onLongPressGesture { - sheet = SomeSheet( - content: { - AnyView( - NavigationView { - ChatListTag() - } - ) - }, - id: "tag list" - ) + HStack(spacing: 4) { + Text(tag.chatTagEmoji) + ZStack { + Text(tag.chatTagText).fontWeight(.semibold).foregroundColor(.clear) + Text(tag.chatTagText).fontWeight(current ? .semibold : .regular).foregroundColor(color) } } + .onTapGesture { + setActiveFilter(filter: .userTag(tag: tag)) + } + .onLongPressGesture { + sheet = SomeSheet( + content: { + AnyView( + NavigationView { + ChatListTag() + } + ) + }, + id: "tag list" + ) + } } } - if chatTagsModel.tags.isEmpty { + if chatTagsModel.userTags.isEmpty { Button { sheet = SomeSheet( content: { @@ -777,76 +768,88 @@ struct ChatTagsView: View { } } - @ViewBuilder private func expandedPresetTagsFiltersView(_ filters: [ChatTagFilter]) -> some View { - ForEach(filters, id: \.id) { tag in - let current = chatTagsModel.selectedTag == tag - let color: Color? = current ? .accentColor : (tag.enabled ? nil : .secondary) - if chatTagsModel.tags.isEmpty || tag.enabled { + @ViewBuilder private func expandedPresetTagsFiltersView() -> some View { + let selectedPresetTag: PresetTag? = if case let .presetTag(tag) = chatTagsModel.activeFilter { + tag + } else { + nil + } + + ForEach(presetTags, id: \.id) { tag in + let active = tag == selectedPresetTag + let (systemName, text) = presetTagLabel(tag: tag, active: active) + let color: Color = active ? .accentColor : .secondary + + HStack { + Image(systemName: systemName) + .foregroundColor(color) ZStack { - Image(systemName: tag.getIcon(active: chatTagsModel.selectedTag == tag)) - .foregroundColor(color) + Text(text).fontWeight(.semibold).foregroundColor(.clear) + Text(text).fontWeight(active ? .semibold : .regular).foregroundColor(color) } - .onTapGesture { - if tag.enabled { - setSelectedTag(tag) - } - } - } else { - EmptyView() + } + .onTapGesture { + setActiveFilter(filter: .presetTag(tag: tag)) } } } - @ViewBuilder private func collapsedTagsFilterView(_ filters: [ChatTagFilter]) -> some View { - let selectedPresetTag = filters.first(where: { $0 == chatTagsModel.selectedTag }) - let presetFilterIsSelected = selectedPresetTag != nil - let color: Color = presetFilterIsSelected ? .accentColor : .secondary + @ViewBuilder private func collapsedTagsFilterView() -> some View { + let selectedPresetTag: PresetTag? = if case let .presetTag(tag) = chatTagsModel.activeFilter { + tag + } else { + nil + } Menu { - ForEach(filters, id: \.id) { tag in + ForEach(presetTags, id: \.id) { tag in Button { - setSelectedTag(tag) + setActiveFilter(filter: .presetTag(tag: tag)) } label: { + let (systemName, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag) HStack { - Image(systemName: tag.getIcon(active: chatTagsModel.selectedTag == tag)) - Text(tag.text) + Image(systemName: systemName) + Text(text) } } } + Button { + chatTagsModel.activeFilter = nil + } label: { + let (systemName, text) = presetTagLabel(tag: nil, active: false) + HStack { + Image(systemName: systemName) + Text(text) + } + } } label: { - Image(systemName: selectedPresetTag?.getIcon(active: true) ?? "list.bullet") - .foregroundColor(color) + let active = selectedPresetTag != nil + let (systemName, _) = presetTagLabel(tag: selectedPresetTag, active: active) + Image(systemName: systemName) + .foregroundColor(active ? .accentColor : .secondary) } .frame(minWidth: 28) } - private func presetTagFilters() -> [ChatTagFilter] { - var tags: [ChatTagFilter] = chatTagsModel.presetTags - var filtersLeft = chatTagsModel.presetTags - for chat in chatModel.chats { - for filter in filtersLeft { - if case let .presetTag(icon, activeIcon, text, _, filterFunction) = filter { - if let i = tags.firstIndex(where: { $0.id == filter.id }), filterFunction(chat.chatInfo) { - tags[i] = .presetTag(icon: icon, activeIcon: activeIcon, text: text, enabled: true, filter: filterFunction) - } - } - } - - filtersLeft.removeAll { fl in - tags.contains(where: { $0.id == fl.id && $0.enabled } ) - } - if filtersLeft.isEmpty { - break - } + private func presetTagLabel(tag: PresetTag?, active: Bool) -> (String, LocalizedStringKey) { + switch tag { + case .contact: + return (active ? "person.fill" : "person", LocalizedStringKey("Contacts")) + case .group: + return (active ? "person.2.fill" : "person.2", LocalizedStringKey("Groups")) + case .business: + return (active ? "briefcase.fill" : "briefcase", LocalizedStringKey("Businesses")) + case .favorite: + return (active ? "star.fill" : "star", LocalizedStringKey("Favorites")) + case .none: + return ("list.bullet", LocalizedStringKey("All")) } - - return tags } - private func setSelectedTag(_ tag: ChatTagFilter) { - if (chatTagsModel.selectedTag == tag) { - chatTagsModel.selectedTag = ChatTagsModel.defaultTag + private func setActiveFilter(filter: ActiveFilter) { + if filter != chatTagsModel.activeFilter { + chatTagsModel.activeFilter = filter } else { - chatTagsModel.selectedTag = tag + chatTagsModel.activeFilter = nil } } @@ -856,10 +859,8 @@ struct ChatTagsView: View { let chatTags = try await apiGetChatTags() await MainActor.run { - self.chatTagsModel.tags = chatTags.map { - .chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId) - } - self.chatTagsModel.selectedTag = ChatTagsModel.defaultTag + self.chatTagsModel.userTags = chatTags + self.chatTagsModel.activeFilter = nil } } catch let error { AlertManager.shared.showAlertMsg(title: "Error", message: "\(responseError(error))") @@ -879,6 +880,30 @@ func chatStoppedIcon() -> some View { } } +private func chatPresetTagMatches(_ chat: Chat) -> Set { + var matchedPresets = Set() + switch chat.chatInfo { + case .direct: + matchedPresets.insert(.contact) + if chat.chatInfo.chatSettings?.favorite ?? false { + matchedPresets.insert(.favorite) + } + case let .group(groupInfo): + if groupInfo.businessChat?.chatType == .business { + matchedPresets.insert(.business) + } else { + matchedPresets.insert(.group) + } + if chat.chatInfo.chatSettings?.favorite ?? false { + matchedPresets.insert(.favorite) + } + default: + break + } + + return matchedPresets +} + struct ChatListView_Previews: PreviewProvider { @State static var userPickerSheet: UserPickerSheet? = .none diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 537e28dfa3..9f1fccc4ba 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -4218,6 +4218,14 @@ public struct ChatTag: Decodable, Hashable { public var chatTagId: Int64 public var chatTagText: String public var chatTagEmoji: String + + public var id: Int64 { chatTagId } + + public init(chatTagId: Int64, chatTagText: String, chatTagEmoji: String) { + self.chatTagId = chatTagId + self.chatTagText = chatTagText + self.chatTagEmoji = chatTagEmoji + } } public struct ChatItemInfo: Decodable, Hashable {