mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
ios: chat tags poc
This commit is contained in:
@@ -100,6 +100,44 @@ class ItemsModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
class ChatTagsModel: ObservableObject {
|
||||
static let shared = ChatTagsModel()
|
||||
|
||||
let presetTags: [ChatTagFilter] = [
|
||||
.presetTag(icon: "star", activeIcon: "star.fill", filter: { c in c.chatSettings?.favorite ?? false }),
|
||||
.presetTag(icon: "person", activeIcon: "person.fill", filter: { if case .direct = $0 { true } else { false }}),
|
||||
.presetTag(icon: "person.2", activeIcon: "person.2.fill", filter: { filterGroupChat($0) }),
|
||||
.presetTag(icon: "briefcase", activeIcon: "briefcase.fill", filter: { filterBusinessChat($0) })
|
||||
]
|
||||
|
||||
@Published var tags: [ChatTagFilter] = []
|
||||
@Published var selectedTag: ChatTagFilter?
|
||||
}
|
||||
|
||||
private func filterBusinessChat(_ cInfo: ChatInfo) -> Bool {
|
||||
if case let .group(gInfo) = cInfo {
|
||||
return switch gInfo.businessChat?.chatType {
|
||||
case .none: false
|
||||
case .business: true
|
||||
case .customer: true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func filterGroupChat(_ cInfo: ChatInfo) -> Bool {
|
||||
if case let .group(gInfo) = cInfo {
|
||||
return switch gInfo.businessChat?.chatType {
|
||||
case .none: true
|
||||
case .business: false
|
||||
case .customer: false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class NetworkModel: ObservableObject {
|
||||
// map of connections network statuses, key is agent connection id
|
||||
@Published var networkStatuses: Dictionary<String, NetworkStatus> = [:]
|
||||
|
||||
@@ -313,6 +313,13 @@ func apiGetChatsAsync() async throws -> [ChatData] {
|
||||
return try apiChatsResponse(await chatSendCmd(.apiGetChats(userId: userId)))
|
||||
}
|
||||
|
||||
func apiGetChatTags() async throws -> [ChatTag] {
|
||||
let userId = try currentUserId("apiGetChatTags")
|
||||
let r = await chatSendCmd(.apiGetChatTags(userId: userId))
|
||||
if case let .chatTags(_, chatTags) = r { return chatTags }
|
||||
throw r
|
||||
}
|
||||
|
||||
private func apiChatsResponse(_ r: ChatResponse) throws -> [ChatData] {
|
||||
if case let .apiChats(_, chats) = r { return chats }
|
||||
throw r
|
||||
@@ -368,6 +375,30 @@ func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: Ch
|
||||
return await processSendMessageCmd(toChatType: toChatType, cmd: cmd)
|
||||
}
|
||||
|
||||
func apiCreateChatTag(type: ChatType, id: Int64, tag: ChatTagData) async throws -> ([ChatTag], [Int64]) {
|
||||
let r = await chatSendCmd(.apiCreateChatTag(type: type, id: id, tag: tag))
|
||||
if case let .tagsUpdated(_, userTags, chatTags) = r {
|
||||
return (userTags, chatTags)
|
||||
}
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiTagChat(type: ChatType, id: Int64, tagId: Int64) async throws -> ([ChatTag], [Int64]) {
|
||||
let r = await chatSendCmd(.apiTagChat(type: type, id: id, tagId: tagId))
|
||||
if case let .tagsUpdated(_, userTags, chatTags) = r {
|
||||
return (userTags, chatTags)
|
||||
}
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiUntagChat(type: ChatType, id: Int64, tagId: Int64) async throws -> ([ChatTag], [Int64]) {
|
||||
let r = await chatSendCmd(.apiUntagChat(type: type, id: id, tagId: tagId))
|
||||
if case let .chatUntagged(_, userTags, chatTags) = r {
|
||||
return (userTags, chatTags)
|
||||
}
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? {
|
||||
let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages)
|
||||
return await processSendMessageCmd(toChatType: type, cmd: cmd)
|
||||
|
||||
@@ -332,7 +332,7 @@ struct ChatInfoView: View {
|
||||
.sheet(item: $sheet) {
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.content
|
||||
.presentationDetents([.fraction(0.4)])
|
||||
.presentationDetents([.fraction($0.fraction)])
|
||||
} else {
|
||||
$0.content
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ struct ChatListNavLink: View {
|
||||
markReadButton()
|
||||
toggleFavoriteButton()
|
||||
toggleNtfsButton(chat: chat)
|
||||
tagChatButton(chat)
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if !chat.chatItems.isEmpty {
|
||||
@@ -140,6 +141,7 @@ struct ChatListNavLink: View {
|
||||
deleteLabel
|
||||
}
|
||||
.tint(.red)
|
||||
untagChatButton(chat)
|
||||
}
|
||||
.frame(height: dynamicRowHeight)
|
||||
}
|
||||
@@ -149,7 +151,7 @@ struct ChatListNavLink: View {
|
||||
.sheet(item: $sheet) {
|
||||
if #available(iOS 16.0, *) {
|
||||
$0.content
|
||||
.presentationDetents([.fraction(0.4)])
|
||||
.presentationDetents([.fraction($0.fraction)])
|
||||
} else {
|
||||
$0.content
|
||||
}
|
||||
@@ -306,7 +308,41 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
.tint(Color.orange)
|
||||
}
|
||||
|
||||
private func tagChatButton(_ chat: Chat) -> some View {
|
||||
Button {
|
||||
sheet = SomeSheet(
|
||||
content: {
|
||||
AnyView(
|
||||
ChatListTag(chat: chat)
|
||||
)
|
||||
},
|
||||
id: "tag sheet",
|
||||
fraction: 0.7
|
||||
)
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Tag", comment: "swipe action"), systemImage: "tag.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.indigo)
|
||||
}
|
||||
|
||||
private func untagChatButton(_ chat: Chat) -> some View {
|
||||
Button {
|
||||
sheet = SomeSheet(
|
||||
content: {
|
||||
AnyView(
|
||||
ChatListTag(chat: chat, untag: true)
|
||||
)
|
||||
},
|
||||
id: "tag sheet",
|
||||
fraction: 0.7
|
||||
)
|
||||
} label: {
|
||||
SwipeLabel(NSLocalizedString("Untag", comment: "swipe action"), systemImage: "tag.fill", inverted: oneHandUI)
|
||||
}
|
||||
.tint(Color.yellow.opacity(0.8))
|
||||
}
|
||||
|
||||
private func clearNoteFolderButton() -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlert(clearNoteFolderAlert())
|
||||
@@ -484,6 +520,163 @@ struct ChatListNavLink: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListTag: View {
|
||||
var chat: Chat
|
||||
var untag: Bool = false
|
||||
@EnvironmentObject var chatTagsModel: ChatTagsModel
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State private var emoji: String = ""
|
||||
@State private var name: String = ""
|
||||
|
||||
var chatTagsIds: [Int64] { chat.chatInfo.contact?.chatTags ?? chat.chatInfo.groupInfo?.chatTags ?? [] }
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if untag {
|
||||
untagView()
|
||||
} else {
|
||||
tagView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func tagView() -> some View {
|
||||
Section {
|
||||
TextField("Emoji..", text: $emoji)
|
||||
TextField("Tag name...", text: $name)
|
||||
Button {
|
||||
createChatTag()
|
||||
} label: {
|
||||
Text("Create tag")
|
||||
}
|
||||
}
|
||||
|
||||
let tagsToPick = chatTagsModel.tags.compactMap { tag in
|
||||
if case let .chatTag(emoji, text, tagId) = tag, !chatTagsIds.contains(tagId) {
|
||||
return (emoji, text, tagId)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !tagsToPick.isEmpty {
|
||||
Section {
|
||||
ForEach(tagsToPick, id: \.0) { tag in
|
||||
let (emoji, text, tagId) = tag
|
||||
Button {
|
||||
tagChat(tagId)
|
||||
} label: {
|
||||
Text("\(emoji) \(text)")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Choose existing")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func untagView() -> some View {
|
||||
Section {
|
||||
ForEach(chatTagsModel.tags) { tag in
|
||||
if case let .chatTag(emoji, text, tagId) = tag, chatTagsIds.contains(tagId) {
|
||||
Button {
|
||||
untagChat(tagId)
|
||||
} label: {
|
||||
Text("\(emoji) \(text)")
|
||||
}
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Choose existing")
|
||||
}
|
||||
}
|
||||
|
||||
private func createChatTag() {
|
||||
Task {
|
||||
do {
|
||||
let (userTags, chatTags) = try await apiCreateChatTag(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
tag: ChatTagData(emoji: emoji, text: name)
|
||||
)
|
||||
|
||||
await MainActor.run {
|
||||
chatTagsModel.tags = userTags.map {
|
||||
.chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId)
|
||||
}
|
||||
updateChatTags(chat: chat, chatTags: chatTags)
|
||||
}
|
||||
} catch let error {
|
||||
showAlert(
|
||||
NSLocalizedString("Error creating tag", comment: "alert title"),
|
||||
message: responseError(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func tagChat(_ tagId: Int64) {
|
||||
Task {
|
||||
do {
|
||||
let (userTags, chatTags) = try await apiTagChat(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
tagId: tagId
|
||||
)
|
||||
|
||||
await MainActor.run {
|
||||
chatTagsModel.tags = userTags.map {
|
||||
.chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId)
|
||||
}
|
||||
updateChatTags(chat: chat, chatTags: chatTags)
|
||||
}
|
||||
} catch let error {
|
||||
showAlert(
|
||||
NSLocalizedString("Error tagging chat", comment: "alert title"),
|
||||
message: responseError(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func untagChat(_ tagId: Int64) {
|
||||
Task {
|
||||
do {
|
||||
let (userTags, chatTags) = try await apiUntagChat(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
tagId: tagId
|
||||
)
|
||||
|
||||
await MainActor.run {
|
||||
chatTagsModel.tags = userTags.map {
|
||||
.chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId)
|
||||
}
|
||||
updateChatTags(chat: chat, chatTags: chatTags)
|
||||
}
|
||||
} catch let error {
|
||||
showAlert(
|
||||
NSLocalizedString("Error untagging chat", comment: "alert title"),
|
||||
message: responseError(error)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func updateChatTags(chat: Chat, chatTags: [Int64]) {
|
||||
if var contact = chat.chatInfo.contact {
|
||||
contact.chatTags = chatTags
|
||||
m.updateContact(contact)
|
||||
} else if var group = chat.chatInfo.groupInfo {
|
||||
group.chatTags = chatTags
|
||||
m.updateGroup(group)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rejectContactRequestAlert(_ contactRequest: UserContactRequest) -> Alert {
|
||||
Alert(
|
||||
title: Text("Reject contact request"),
|
||||
|
||||
@@ -117,6 +117,7 @@ struct ChatListView: View {
|
||||
@State private var searchChatFilteredBySimplexLink: String? = nil
|
||||
@State private var scrollToSearchBar = false
|
||||
@State private var userPickerShown: Bool = false
|
||||
@StateObject private var chatTagsModel = ChatTagsModel.shared
|
||||
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
@@ -144,6 +145,7 @@ struct ChatListView: View {
|
||||
destination: chatView
|
||||
) { chatListView }
|
||||
}
|
||||
.environmentObject(chatTagsModel)
|
||||
.modifier(
|
||||
Sheet(isPresented: $userPickerShown) {
|
||||
UserPicker(userPickerShown: $userPickerShown, activeSheet: $activeUserPickerSheet)
|
||||
@@ -394,13 +396,13 @@ struct ChatListView: View {
|
||||
let s = searchString()
|
||||
return s == "" && !showUnreadAndFavorites
|
||||
? chatModel.chats.filter { chat in
|
||||
!chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card
|
||||
filterByTag(chat) && !chat.chatInfo.chatDeleted && chatContactType(chat: chat) != ContactType.card
|
||||
}
|
||||
: chatModel.chats.filter { chat in
|
||||
let cInfo = chat.chatInfo
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
return !contact.chatDeleted && chatContactType(chat: chat) != ContactType.card && (
|
||||
return filterByTag(chat) && !contact.chatDeleted && chatContactType(chat: chat) != ContactType.card && (
|
||||
s == ""
|
||||
? filtered(chat)
|
||||
: (viewNameContains(cInfo, s) ||
|
||||
@@ -409,29 +411,50 @@ struct ChatListView: View {
|
||||
)
|
||||
case let .group(gInfo):
|
||||
return s == ""
|
||||
? (filtered(chat) || gInfo.membership.memberStatus == .memInvited)
|
||||
: viewNameContains(cInfo, s)
|
||||
? filterByTag(chat) && (filtered(chat) || gInfo.membership.memberStatus == .memInvited)
|
||||
: filterByTag(chat) && viewNameContains(cInfo, s)
|
||||
case .local:
|
||||
return s == "" || viewNameContains(cInfo, s)
|
||||
return filterByTag(chat) && (s == "" || viewNameContains(cInfo, s))
|
||||
case .contactRequest:
|
||||
return s == "" || viewNameContains(cInfo, s)
|
||||
return filterByTag(chat) && (s == "" || viewNameContains(cInfo, s))
|
||||
case let .contactConnection(conn):
|
||||
return s != "" && conn.localAlias.localizedLowercase.contains(s)
|
||||
return filterByTag(chat) && (s != "" && conn.localAlias.localizedLowercase.contains(s))
|
||||
case .invalidJSON:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func searchString() -> String {
|
||||
searchShowingSimplexLink ? "" : searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
if let tag = chatTagsModel.selectedTag {
|
||||
switch tag {
|
||||
case let .presetTag(_, _, filter):
|
||||
return filter(chat.chatInfo)
|
||||
case let .chatTag(_, _, tagId):
|
||||
let cInfo = chat.chatInfo
|
||||
switch cInfo {
|
||||
case let .direct(contact):
|
||||
return contact.chatTags.contains(tagId)
|
||||
case let .group(gInfo):
|
||||
return gInfo.chatTags.contains(tagId)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func viewNameContains(_ cInfo: ChatInfo, _ s: String) -> Bool {
|
||||
cInfo.chatViewName.localizedLowercase.contains(s)
|
||||
@@ -500,6 +523,7 @@ struct SubsStatusIndicator: View {
|
||||
struct ChatListSearchBar: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@EnvironmentObject var chatTagsModel: ChatTagsModel
|
||||
@Binding var searchMode: Bool
|
||||
@FocusState.Binding var searchFocussed: Bool
|
||||
@Binding var searchText: String
|
||||
@@ -512,6 +536,7 @@ struct ChatListSearchBar: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
ScrollView([.horizontal], showsIndicators: false) { ChatTagsView() }
|
||||
HStack(spacing: 12) {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
@@ -605,6 +630,83 @@ struct ChatListSearchBar: View {
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatTagFilter: Identifiable, Equatable {
|
||||
static func == (lhs: ChatTagFilter, rhs: ChatTagFilter) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case let (.presetTag(icon1, activeIcon1, _), .presetTag(icon2, activeIcon2, _)):
|
||||
return icon1 == icon2 && activeIcon1 == activeIcon2
|
||||
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, 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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatTagsView: View {
|
||||
@EnvironmentObject var chatTagsModel: ChatTagsModel
|
||||
|
||||
var body: some View {
|
||||
let tags = chatTagsModel.presetTags + chatTagsModel.tags
|
||||
HStack {
|
||||
ForEach(tags, id: \.id) { tag in
|
||||
let current = chatTagsModel.selectedTag == tag
|
||||
let color: Color = current ? .accentColor : .secondary
|
||||
ZStack {
|
||||
switch tag {
|
||||
case let .presetTag(icon, activeIcon, _):
|
||||
Image(systemName: current ? activeIcon : icon)
|
||||
.foregroundColor(color)
|
||||
case let .chatTag(emoji, text, _):
|
||||
HStack(spacing: 4) {
|
||||
Text(emoji)
|
||||
ZStack {
|
||||
Text(text).fontWeight(.medium).foregroundColor(.clear)
|
||||
Text(text).fontWeight(current ? .medium : .regular).foregroundColor(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
if (chatTagsModel.selectedTag == tag) {
|
||||
chatTagsModel.selectedTag = nil
|
||||
} else {
|
||||
chatTagsModel.selectedTag = tag
|
||||
}
|
||||
}
|
||||
}
|
||||
}.task {
|
||||
getChatTags()
|
||||
}
|
||||
}
|
||||
|
||||
private func getChatTags() {
|
||||
Task {
|
||||
do {
|
||||
let chatTags = try await apiGetChatTags()
|
||||
|
||||
await MainActor.run {
|
||||
self.chatTagsModel.tags = chatTags.map {
|
||||
.chatTag(emoji: $0.chatTagEmoji, text: $0.chatTagText, tagId: $0.chatTagId)
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
AlertManager.shared.showAlertMsg(title: "Error", message: "\(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chatStoppedIcon() -> some View {
|
||||
Button {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
|
||||
@@ -25,6 +25,7 @@ struct SomeActionSheet: Identifiable {
|
||||
struct SomeSheet<Content: View>: Identifiable {
|
||||
@ViewBuilder var content: Content
|
||||
var id: String
|
||||
var fraction = 0.4
|
||||
}
|
||||
|
||||
private enum NewChatViewAlert: Identifiable {
|
||||
|
||||
@@ -40,10 +40,14 @@ public enum ChatCommand {
|
||||
case testStorageEncryption(key: String)
|
||||
case apiSaveSettings(settings: AppSettings)
|
||||
case apiGetSettings(settings: AppSettings)
|
||||
case apiGetChatTags(userId: Int64)
|
||||
case apiGetChats(userId: Int64)
|
||||
case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String)
|
||||
case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
|
||||
case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
|
||||
case apiCreateChatTag(type: ChatType, id: Int64, tag: ChatTagData)
|
||||
case apiTagChat(type: ChatType, id: Int64, tagId: Int64)
|
||||
case apiUntagChat(type: ChatType, id: Int64, tagId: Int64)
|
||||
case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage])
|
||||
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool)
|
||||
case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode)
|
||||
@@ -198,6 +202,7 @@ public enum ChatCommand {
|
||||
case let .testStorageEncryption(key): return "/db test key \(key)"
|
||||
case let .apiSaveSettings(settings): return "/_save app settings \(encodeJSON(settings))"
|
||||
case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))"
|
||||
case let .apiGetChatTags(userId): return "/_get tags \(userId)"
|
||||
case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on"
|
||||
case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" +
|
||||
(search == "" ? "" : " search=\(search)")
|
||||
@@ -206,6 +211,9 @@ public enum ChatCommand {
|
||||
let msgs = encodeJSON(composedMessages)
|
||||
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
|
||||
return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
|
||||
case let .apiCreateChatTag(type, id, tag): return "/_create tag \(ref(type, id)) \(encodeJSON(tag))"
|
||||
case let .apiTagChat(type, id, tagId): return "/_tag \(ref(type, id)) \(tagId)"
|
||||
case let .apiUntagChat(type, id, tagId): return "/_untag \(ref(type, id)) \(tagId)"
|
||||
case let .apiCreateChatItems(noteFolderId, composedMessages):
|
||||
let msgs = encodeJSON(composedMessages)
|
||||
return "/_create *\(noteFolderId) json \(msgs)"
|
||||
@@ -367,10 +375,14 @@ public enum ChatCommand {
|
||||
case .testStorageEncryption: return "testStorageEncryption"
|
||||
case .apiSaveSettings: return "apiSaveSettings"
|
||||
case .apiGetSettings: return "apiGetSettings"
|
||||
case .apiGetChatTags: return "apiGetChatTags"
|
||||
case .apiGetChats: return "apiGetChats"
|
||||
case .apiGetChat: return "apiGetChat"
|
||||
case .apiGetChatItemInfo: return "apiGetChatItemInfo"
|
||||
case .apiSendMessages: return "apiSendMessages"
|
||||
case .apiCreateChatTag: return "apiCreateChatTag"
|
||||
case .apiTagChat: return "apiTagChat"
|
||||
case .apiUntagChat: return "apiUntagChat"
|
||||
case .apiCreateChatItems: return "apiCreateChatItems"
|
||||
case .apiUpdateChatItem: return "apiUpdateChatItem"
|
||||
case .apiDeleteChatItem: return "apiDeleteChatItem"
|
||||
@@ -564,6 +576,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case chatSuspended
|
||||
case apiChats(user: UserRef, chats: [ChatData])
|
||||
case apiChat(user: UserRef, chat: ChatData)
|
||||
case chatTags(user: UserRef, userTags: [ChatTag])
|
||||
case chatItemInfo(user: UserRef, chatItem: AChatItem, chatItemInfo: ChatItemInfo)
|
||||
case serverTestResult(user: UserRef, testServer: String, testFailure: ProtocolTestFailure?)
|
||||
case serverOperatorConditions(conditions: ServerOperatorConditions)
|
||||
@@ -590,6 +603,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case contactCode(user: UserRef, contact: Contact, connectionCode: String)
|
||||
case groupMemberCode(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionCode: String)
|
||||
case connectionVerified(user: UserRef, verified: Bool, expectedCode: String)
|
||||
case tagsUpdated(user: UserRef, userTags: [ChatTag], chatTags: [Int64])
|
||||
case chatUntagged(user: UserRef, userTags: [ChatTag], chatTags: [Int64])
|
||||
case invitation(user: UserRef, connReqInvitation: String, connection: PendingContactConnection)
|
||||
case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection)
|
||||
case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef)
|
||||
@@ -741,6 +756,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .chatSuspended: return "chatSuspended"
|
||||
case .apiChats: return "apiChats"
|
||||
case .apiChat: return "apiChat"
|
||||
case .chatTags: return "chatTags"
|
||||
case .chatItemInfo: return "chatItemInfo"
|
||||
case .serverTestResult: return "serverTestResult"
|
||||
case .serverOperatorConditions: return "serverOperators"
|
||||
@@ -767,6 +783,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactCode: return "contactCode"
|
||||
case .groupMemberCode: return "groupMemberCode"
|
||||
case .connectionVerified: return "connectionVerified"
|
||||
case .tagsUpdated: return "tagsUpdated"
|
||||
case .chatUntagged: return "chatUntagged"
|
||||
case .invitation: return "invitation"
|
||||
case .connectionIncognitoUpdated: return "connectionIncognitoUpdated"
|
||||
case .connectionUserChanged: return "connectionUserChanged"
|
||||
@@ -914,6 +932,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .chatSuspended: return noDetails
|
||||
case let .apiChats(u, chats): return withUser(u, String(describing: chats))
|
||||
case let .apiChat(u, chat): return withUser(u, String(describing: chat))
|
||||
case let .chatTags(u, userTags): return withUser(u, "userTags: \(String(describing: userTags))")
|
||||
case let .chatItemInfo(u, chatItem, chatItemInfo): return withUser(u, "chatItem: \(String(describing: chatItem))\nchatItemInfo: \(String(describing: chatItemInfo))")
|
||||
case let .serverTestResult(u, server, testFailure): return withUser(u, "server: \(server)\nresult: \(String(describing: testFailure))")
|
||||
case let .serverOperatorConditions(conditions): return "conditions: \(String(describing: conditions))"
|
||||
@@ -942,6 +961,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .contactCode(u, contact, connectionCode): return withUser(u, "contact: \(String(describing: contact))\nconnectionCode: \(connectionCode)")
|
||||
case let .groupMemberCode(u, groupInfo, member, connectionCode): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionCode: \(connectionCode)")
|
||||
case let .connectionVerified(u, verified, expectedCode): return withUser(u, "verified: \(verified)\nconnectionCode: \(expectedCode)")
|
||||
case let .tagsUpdated(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))")
|
||||
case let .chatUntagged(u, userTags, chatTags): return withUser(u, "userTags: \(String(describing: userTags))\nchatTags: \(String(describing: chatTags))")
|
||||
case let .invitation(u, connReqInvitation, connection): return withUser(u, "connReqInvitation: \(connReqInvitation)\nconnection: \(connection)")
|
||||
case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
|
||||
case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\newUserId: \(String(describing: newUser.userId))")
|
||||
@@ -1172,6 +1193,16 @@ public enum ChatPagination {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatTagData: Encodable {
|
||||
public var emoji: String
|
||||
public var text: String
|
||||
|
||||
public init(emoji: String, text: String) {
|
||||
self.emoji = emoji
|
||||
self.text = text
|
||||
}
|
||||
}
|
||||
|
||||
public struct ComposedMessage: Encodable {
|
||||
public var fileSource: CryptoFile?
|
||||
var quotedItemId: Int64?
|
||||
|
||||
@@ -1545,6 +1545,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
var chatTs: Date?
|
||||
var contactGroupMemberId: Int64?
|
||||
var contactGrpInvSent: Bool
|
||||
public var chatTags: [Int64]
|
||||
public var uiThemes: ThemeModeOverrides?
|
||||
public var chatDeleted: Bool
|
||||
|
||||
@@ -1615,6 +1616,7 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
contactGrpInvSent: false,
|
||||
chatTags: [],
|
||||
chatDeleted: false
|
||||
)
|
||||
}
|
||||
@@ -1910,6 +1912,7 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
public var fullName: String { get { groupProfile.fullName } }
|
||||
public var image: String? { get { groupProfile.image } }
|
||||
public var localAlias: String { "" }
|
||||
public var chatTags: [Int64]
|
||||
|
||||
public var isOwner: Bool {
|
||||
return membership.memberRole == .owner && membership.memberCurrent
|
||||
@@ -1932,7 +1935,8 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
hostConnCustomUserProfileId: nil,
|
||||
chatSettings: ChatSettings.defaults,
|
||||
createdAt: .now,
|
||||
updatedAt: .now
|
||||
updatedAt: .now,
|
||||
chatTags: []
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4210,6 +4214,12 @@ public enum ChatItemTTL: Identifiable, Comparable, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct ChatTag: Decodable, Hashable {
|
||||
public var chatTagId: Int64
|
||||
public var chatTagText: String
|
||||
public var chatTagEmoji: String
|
||||
}
|
||||
|
||||
public struct ChatItemInfo: Decodable, Hashable {
|
||||
public var itemVersions: [ChatItemVersion]
|
||||
public var memberDeliveryStatuses: [MemberDeliveryStatus]?
|
||||
|
||||
Reference in New Issue
Block a user