ios: update api for message info, refactor, localize (#2458)
* update api for message info, refactor, localize * refactor * change text, layout * show reactions on deleted revealed items * update * trim * refactor * corrections --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
a0c4726af3
commit
9e33ba46af
@@ -295,8 +295,8 @@ func loadChat(chat: Chat, search: String = "") {
|
||||
}
|
||||
}
|
||||
|
||||
func apiGetChatItemInfo(itemId: Int64) async throws -> ChatItemInfo {
|
||||
let r = await chatSendCmd(.apiGetChatItemInfo(itemId: itemId))
|
||||
func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo {
|
||||
let r = await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId))
|
||||
if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo }
|
||||
throw r
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,7 +19,7 @@ struct MarkedDeletedItemView: View {
|
||||
if showMember, let member = chatItem.memberDisplayName {
|
||||
Text(member).font(.caption).fontWeight(.medium) + Text(": ").font(.caption)
|
||||
}
|
||||
if case let .moderated(byGroupMember) = chatItem.meta.itemDeleted {
|
||||
if case let .moderated(_, byGroupMember) = chatItem.meta.itemDeleted {
|
||||
markedDeletedText("moderated by \(byGroupMember.chatViewName)")
|
||||
} else {
|
||||
markedDeletedText("marked deleted")
|
||||
@@ -46,7 +46,7 @@ struct MarkedDeletedItemView: View {
|
||||
struct MarkedDeletedItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted))
|
||||
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now)))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 200))
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import SimpleXChat
|
||||
|
||||
struct ChatItemInfoView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var chatItemSent: Bool
|
||||
var ci: ChatItem
|
||||
@Binding var chatItemInfo: ChatItemInfo?
|
||||
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||
|
||||
@@ -32,37 +32,55 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var title: String {
|
||||
ci.chatDir.sent
|
||||
? NSLocalizedString("Sent message", comment: "message info title")
|
||||
: NSLocalizedString("Received message", comment: "message info title")
|
||||
}
|
||||
|
||||
@ViewBuilder private func itemInfoView(_ chatItemInfo: ChatItemInfo) -> some View {
|
||||
let meta = ci.meta
|
||||
GeometryReader { g in
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Message details")
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text(title)
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
|
||||
let maxWidth = (g.size.width - 32) * 0.84
|
||||
infoRow("Sent at", localTimestamp(meta.itemTs))
|
||||
if !ci.chatDir.sent {
|
||||
infoRow("Received at", localTimestamp(meta.createdAt))
|
||||
}
|
||||
switch (meta.itemDeleted) {
|
||||
case let .deleted(deletedTs):
|
||||
if let deletedTs = deletedTs {
|
||||
infoRow("Deleted at", localTimestamp(deletedTs))
|
||||
}
|
||||
case let .moderated(deletedTs, _):
|
||||
if let deletedTs = deletedTs {
|
||||
infoRow("Moderated at", localTimestamp(deletedTs))
|
||||
}
|
||||
default: EmptyView()
|
||||
}
|
||||
if let deleteAt = meta.itemTimed?.deleteAt {
|
||||
infoRow("Disappears at", localTimestamp(deleteAt))
|
||||
}
|
||||
if developerTools {
|
||||
infoRow("Database ID", "\(chatItemInfo.chatItemId)")
|
||||
}
|
||||
infoRow("Sent at", localTimestamp(chatItemInfo.itemTs))
|
||||
if !chatItemSent {
|
||||
infoRow("Received at", localTimestamp(chatItemInfo.createdAt))
|
||||
}
|
||||
if let deleteAt = chatItemInfo.deleteAt {
|
||||
infoRow("To be deleted at", localTimestamp(deleteAt))
|
||||
infoRow("Database ID", "\(meta.itemId)")
|
||||
infoRow("Record updated at", localTimestamp(meta.updatedAt))
|
||||
}
|
||||
|
||||
if !chatItemInfo.itemVersions.isEmpty {
|
||||
Divider()
|
||||
.padding(.top)
|
||||
Divider().padding(.vertical)
|
||||
|
||||
Text("Edit history")
|
||||
.font(.title)
|
||||
Text("History")
|
||||
.font(.title2)
|
||||
.padding(.bottom, 4)
|
||||
LazyVStack(alignment: .leading, spacing: 12) {
|
||||
LazyVStack(alignment: .leading, spacing: 16) {
|
||||
ForEach(Array(chatItemInfo.itemVersions.enumerated()), id: \.element.chatItemVersionId) { index, itemVersion in
|
||||
itemVersionView(itemVersion, maxWidth, current: index == 0)
|
||||
itemVersionView(itemVersion, maxWidth, current: index == 0 && ci.meta.itemDeleted == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,68 +92,77 @@ struct ChatItemInfoView: View {
|
||||
}
|
||||
|
||||
@ViewBuilder private func itemVersionView(_ itemVersion: ChatItemVersion, _ maxWidth: CGFloat, current: Bool) -> some View {
|
||||
let uiMenu: Binding<UIMenu> = Binding(
|
||||
get: { UIMenu(title: "", children: itemVersionMenu(itemVersion)) },
|
||||
set: { _ in }
|
||||
)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
messageText(itemVersion.msgContent.text, parseSimpleXMarkdown(itemVersion.msgContent.text), nil)
|
||||
messageText(itemVersion.msgContent.text, itemVersion.formattedText, nil)
|
||||
.allowsHitTesting(false)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(ciDirFrameColor(chatItemSent: chatItemSent, colorScheme: colorScheme))
|
||||
.background(chatItemFrameColor(ci, colorScheme))
|
||||
.cornerRadius(18)
|
||||
.uiKitContextMenu(menu: uiMenu, allowMenu: Binding.constant(true))
|
||||
Text(
|
||||
localTimestamp(itemVersion.itemVersionTs)
|
||||
+ (current
|
||||
? (" (" + NSLocalizedString("Current", comment: "designation of the current version of the message") + ")")
|
||||
: "")
|
||||
)
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
.padding(.horizontal, 12)
|
||||
.contextMenu {
|
||||
Button {
|
||||
showShareSheet(items: [itemVersion.msgContent.text])
|
||||
} label: {
|
||||
Label("Share", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
Button {
|
||||
UIPasteboard.general.string = itemVersion.msgContent.text
|
||||
} label: {
|
||||
Label("Copy", systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
let ts = localTimestamp(itemVersion.itemVersionTs)
|
||||
(current ? Text("\(ts) (current)") : Text(ts))
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.caption)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.frame(maxWidth: maxWidth, alignment: .leading)
|
||||
}
|
||||
|
||||
func itemVersionMenu(_ itemVersion: ChatItemVersion) -> [UIAction] {[
|
||||
UIAction(
|
||||
title: NSLocalizedString("Share", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.up")
|
||||
) { _ in
|
||||
showShareSheet(items: [itemVersion.msgContent.text])
|
||||
},
|
||||
UIAction(
|
||||
title: NSLocalizedString("Copy", comment: "chat item action"),
|
||||
image: UIImage(systemName: "doc.on.doc")
|
||||
) { _ in
|
||||
UIPasteboard.general.string = itemVersion.msgContent.text
|
||||
}
|
||||
]}
|
||||
|
||||
func itemInfoShareText(_ chatItemInfo: ChatItemInfo) -> String {
|
||||
var shareText = ""
|
||||
let nl = "\n"
|
||||
shareText += "Message details" + nl + nl
|
||||
private func itemInfoShareText(_ chatItemInfo: ChatItemInfo) -> String {
|
||||
let meta = ci.meta
|
||||
var shareText: [String] = [title, ""]
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Sent at: %@", comment: "copied message info"), localTimestamp(meta.itemTs))]
|
||||
if !ci.chatDir.sent {
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Received at: %@", comment: "copied message info"), localTimestamp(meta.createdAt))]
|
||||
}
|
||||
switch (ci.meta.itemDeleted) {
|
||||
case let .deleted(deletedTs):
|
||||
if let deletedTs = deletedTs {
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Deleted at: %@", comment: "copied message info"), localTimestamp(deletedTs))]
|
||||
}
|
||||
case let .moderated(deletedTs, _):
|
||||
if let deletedTs = deletedTs {
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Moderated at: %@", comment: "copied message info"), localTimestamp(deletedTs))]
|
||||
}
|
||||
default: ()
|
||||
}
|
||||
if let deleteAt = meta.itemTimed?.deleteAt {
|
||||
shareText += [String.localizedStringWithFormat(NSLocalizedString("Disappears at: %@", comment: "copied message info"), localTimestamp(deleteAt))]
|
||||
}
|
||||
if developerTools {
|
||||
shareText += "Database ID: \(chatItemInfo.chatItemId)" + nl
|
||||
}
|
||||
shareText += "Sent at: \(localTimestamp(chatItemInfo.itemTs))" + nl
|
||||
if !chatItemSent {
|
||||
shareText += "Received at: \(localTimestamp(chatItemInfo.createdAt))" + nl
|
||||
}
|
||||
if let deleteAt = chatItemInfo.deleteAt {
|
||||
shareText += "To be deleted at: \(localTimestamp(deleteAt))" + nl
|
||||
shareText += [
|
||||
String.localizedStringWithFormat(NSLocalizedString("Database ID: %d", comment: "copied message info"), meta.itemId),
|
||||
String.localizedStringWithFormat(NSLocalizedString("Record updated at: %@", comment: "copied message info"), localTimestamp(meta.updatedAt))
|
||||
]
|
||||
}
|
||||
if !chatItemInfo.itemVersions.isEmpty {
|
||||
shareText += nl + "Edit history" + nl + nl
|
||||
shareText += ["", NSLocalizedString("History", comment: "copied message info")]
|
||||
for (index, itemVersion) in chatItemInfo.itemVersions.enumerated() {
|
||||
shareText += localTimestamp(itemVersion.itemVersionTs) + (index == 0 ? " (Current)" : "") + ":" + nl
|
||||
shareText += itemVersion.msgContent.text + nl + nl
|
||||
shareText += [
|
||||
"",
|
||||
String.localizedStringWithFormat(
|
||||
index == 0 && ci.meta.itemDeleted == nil
|
||||
? NSLocalizedString("%@ (current):", comment: "copied message info")
|
||||
: NSLocalizedString("%@:", comment: "copied message info"),
|
||||
localTimestamp(itemVersion.itemVersionTs)
|
||||
),
|
||||
itemVersion.msgContent.text
|
||||
]
|
||||
}
|
||||
}
|
||||
return shareText.trimmingCharacters(in: .newlines)
|
||||
return shareText.joined(separator: "\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +175,6 @@ func localTimestamp(_ date: Date) -> String {
|
||||
|
||||
struct ChatItemInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ChatItemInfoView(chatItemSent: true, chatItemInfo: Binding.constant(nil))
|
||||
ChatItemInfoView(ci: ChatItem.getSample(1, .directSnd, .now, "hello"), chatItemInfo: Binding.constant(nil))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ struct ChatItemView_Previews: PreviewProvider {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getDeletedContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now)), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent, itemLive: true), revealed: Binding.constant(true))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemLive: true), revealed: Binding.constant(true))
|
||||
}
|
||||
@@ -142,7 +142,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted),
|
||||
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
content: .rcvIntegrityError(msgError: .msgSkipped(fromMsgId: 1, toMsgId: 2)),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
@@ -164,7 +164,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted),
|
||||
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
content: .rcvGroupInvitation(groupInvitation: CIGroupInvitation.getSample(status: .pending), memberRole: .admin),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
@@ -175,7 +175,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted),
|
||||
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
content: .rcvGroupEvent(rcvGroupEvent: .memberAdded(groupMemberId: 1, profile: Profile.sampleData)),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
@@ -186,7 +186,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted),
|
||||
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
|
||||
content: ciFeatureContent,
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
|
||||
@@ -473,7 +473,7 @@ struct ChatView: View {
|
||||
VStack(alignment: alignment.horizontal, spacing: 3) {
|
||||
ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, showMember: showMember, maxWidth: maxWidth, scrollProxy: scrollProxy, revealed: $revealed, allowMenu: $allowMenu, audioPlayer: $audioPlayer, playbackState: $playbackState, playbackTime: $playbackTime)
|
||||
.uiKitContextMenu(menu: uiMenu, allowMenu: $allowMenu)
|
||||
if ci.content.msgContent != nil && ci.meta.itemDeleted == nil && ci.reactions.count > 0 {
|
||||
if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 {
|
||||
chatItemReactions()
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
@@ -501,7 +501,7 @@ struct ChatView: View {
|
||||
.sheet(isPresented: $showChatItemInfoSheet, onDismiss: {
|
||||
chatItemInfo = nil
|
||||
}) {
|
||||
ChatItemInfoView(chatItemSent: ci.chatDir.sent, chatItemInfo: $chatItemInfo)
|
||||
ChatItemInfoView(ci: ci, chatItemInfo: $chatItemInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,6 +579,7 @@ struct ChatView: View {
|
||||
if !ci.isDeletedContent {
|
||||
menu.append(revealUIAction())
|
||||
}
|
||||
menu.append(viewInfoUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
} else if ci.isDeletedContent {
|
||||
menu.append(deleteUIAction())
|
||||
@@ -620,9 +621,10 @@ struct ChatView: View {
|
||||
private func setReaction(add: Bool, reaction: MsgReaction) {
|
||||
Task {
|
||||
do {
|
||||
let cInfo = chat.chatInfo
|
||||
let chatItem = try await apiChatItemReaction(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
type: cInfo.chatType,
|
||||
id: cInfo.apiId,
|
||||
itemId: ci.id,
|
||||
add: add,
|
||||
reaction: reaction
|
||||
@@ -696,12 +698,13 @@ struct ChatView: View {
|
||||
|
||||
private func viewInfoUIAction() -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("View details", comment: "chat item action"),
|
||||
image: UIImage(systemName: "info")
|
||||
title: NSLocalizedString("Info", comment: "chat item action"),
|
||||
image: UIImage(systemName: "info.circle")
|
||||
) { _ in
|
||||
Task {
|
||||
do {
|
||||
let ciInfo = try await apiGetChatItemInfo(itemId: ci.id)
|
||||
let cInfo = chat.chatInfo
|
||||
let ciInfo = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, itemId: ci.id)
|
||||
await MainActor.run {
|
||||
chatItemInfo = ciInfo
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ struct ChatPreviewView_Previews: PreviewProvider {
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted)]
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, itemDeleted: .deleted(deletedTs: .now))]
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
|
||||
@@ -36,7 +36,7 @@ public enum ChatCommand {
|
||||
case apiStorageEncryption(config: DBEncryptionConfig)
|
||||
case apiGetChats(userId: Int64)
|
||||
case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String)
|
||||
case apiGetChatItemInfo(itemId: Int64)
|
||||
case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
|
||||
case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?)
|
||||
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool)
|
||||
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
||||
@@ -141,7 +141,7 @@ public enum ChatCommand {
|
||||
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)")
|
||||
case let .apiGetChatItemInfo(itemId): return "/_get item info \(itemId)"
|
||||
case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)"
|
||||
case let .apiSendMessage(type, id, file, quotedItemId, mc, live, ttl):
|
||||
let msg = encodeJSON(ComposedMessage(filePath: file, quotedItemId: quotedItemId, msgContent: mc))
|
||||
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
|
||||
|
||||
@@ -2212,10 +2212,10 @@ public enum CIDirection: Decodable {
|
||||
|
||||
public struct CIMeta: Decodable {
|
||||
public var itemId: Int64
|
||||
var itemTs: Date
|
||||
public var itemTs: Date
|
||||
var itemText: String
|
||||
public var itemStatus: CIStatus
|
||||
var createdAt: Date
|
||||
public var createdAt: Date
|
||||
public var updatedAt: Date
|
||||
public var itemDeleted: CIDeleted?
|
||||
public var itemEdited: Bool
|
||||
@@ -2310,8 +2310,8 @@ public enum CIStatus: Decodable {
|
||||
}
|
||||
|
||||
public enum CIDeleted: Decodable {
|
||||
case deleted
|
||||
case moderated(byGroupMember: GroupMember)
|
||||
case deleted(deletedTs: Date?)
|
||||
case moderated(deletedTs: Date?, byGroupMember: GroupMember)
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
@@ -3120,17 +3120,13 @@ public enum ChatItemTTL: Hashable, Identifiable, Comparable {
|
||||
}
|
||||
|
||||
public struct ChatItemInfo: Decodable {
|
||||
public var chatItemId: Int64
|
||||
public var itemTs: Date
|
||||
public var createdAt: Date
|
||||
public var updatedAt: Date
|
||||
public var deleteAt: Date?
|
||||
public var itemVersions: [ChatItemVersion]
|
||||
}
|
||||
|
||||
public struct ChatItemVersion: Decodable {
|
||||
public var chatItemVersionId: Int64
|
||||
public var msgContent: MsgContent
|
||||
public var formattedText: [FormattedText]?
|
||||
public var itemVersionTs: Date
|
||||
public var createdAt: Date
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user