mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
refactor
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<AnyView>? = 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<AnyView>? = nil
|
||||
|
||||
|
||||
var presetTags: [PresetTag] {
|
||||
var matches = Set<PresetTag>()
|
||||
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<PresetTag> {
|
||||
var matchedPresets = Set<PresetTag>()
|
||||
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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user