ios: chat tags poc

This commit is contained in:
Diogo
2024-12-11 22:02:30 +00:00
parent 12c2a09c85
commit 0c007f8db0
8 changed files with 418 additions and 12 deletions
+38
View File
@@ -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> = [:]
+31
View File
@@ -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 {
+31
View File
@@ -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?
+11 -1
View File
@@ -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]?