ios: marked deleted chat items, full deletion preference; android: types (#1473)
* ios: marked deleted chat items; full deletion preference * text_, menu, backend * android types * more android types * fix * refactor ios * restore previews * box * refactor menu * revert unnecessary content.text changes * Update apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> * revert layered framed items * clever framed view * improve look * restore previews * restore previews * refactor * refactoring, almost looks good * look * add previews * more previews * remove preview of legacy item * ChatItemDeleted * flip if * remove text_ * refactor * abstract pref property * move marked deleted * revert pref change * undo menu * fix - change to constants * undo pref logic Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
@@ -261,9 +261,9 @@ func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> ChatItem {
|
||||
func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> (ChatItem, ChatItem?) {
|
||||
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemId: itemId, mode: mode), bgDelay: msgDelay)
|
||||
if case let .chatItemDeleted(_, toChatItem) = r { return toChatItem.chatItem }
|
||||
if case let .chatItemDeleted(deletedChatItem, toChatItem, _) = r { return (deletedChatItem.chatItem, toChatItem?.chatItem) }
|
||||
throw r
|
||||
}
|
||||
|
||||
@@ -1010,14 +1010,11 @@ func processReceivedMsg(_ res: ChatResponse) async {
|
||||
}
|
||||
case let .chatItemUpdated(aChatItem):
|
||||
chatItemSimpleUpdate(aChatItem)
|
||||
case let .chatItemDeleted(_, toChatItem):
|
||||
let cInfo = toChatItem.chatInfo
|
||||
let cItem = toChatItem.chatItem
|
||||
if cItem.meta.itemDeleted {
|
||||
m.removeChatItem(cInfo, cItem)
|
||||
case let .chatItemDeleted(deletedChatItem, toChatItem, _):
|
||||
if let toChatItem = toChatItem {
|
||||
_ = m.upsertChatItem(toChatItem.chatInfo, toChatItem.chatItem)
|
||||
} else {
|
||||
// currently only broadcast deletion of rcv message can be received, and only this case should happen
|
||||
_ = m.upsertChatItem(cInfo, cItem)
|
||||
m.removeChatItem(deletedChatItem.chatInfo, deletedChatItem.chatItem)
|
||||
}
|
||||
case let .receivedGroupInvitation(groupInfo, _, _):
|
||||
m.updateGroup(groupInfo) // update so that repeat group invitations are not duplicated
|
||||
|
||||
@@ -149,16 +149,16 @@ struct CIFileView_Previews: PreviewProvider {
|
||||
file: nil
|
||||
)
|
||||
Group {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentFile)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: fileChatItemWtFile)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentFile, revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: fileChatItemWtFile, revealed: Binding.constant(false))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ struct CIMetaView: View {
|
||||
|
||||
struct CIMetaView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
return Group {
|
||||
Group {
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent))
|
||||
CIMetaView(chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent, false, true))
|
||||
CIMetaView(chatItem: ChatItem.getDeletedContentSample())
|
||||
|
||||
@@ -232,13 +232,13 @@ struct CIVoiceView_Previews: PreviewProvider {
|
||||
playbackTime: TimeInterval(20)
|
||||
)
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWtFile)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWtFile, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
|
||||
@@ -60,15 +60,15 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
|
||||
file: CIFile.getSample(fileStatus: .sndComplete)
|
||||
)
|
||||
Group {
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: sentVoiceMessage, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWithQuote)
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: voiceMessageWithQuote, revealed: Binding.constant(false))
|
||||
.environmentObject(ChatModel())
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 360))
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,44 @@
|
||||
//
|
||||
// MarkedDeletedItemView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by JRoberts on 30.11.2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct MarkedDeletedItemView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
var chatItem: ChatItem
|
||||
var showMember = false
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .bottom, spacing: 0) {
|
||||
if showMember, let member = chatItem.memberDisplayName {
|
||||
Text(member).fontWeight(.medium) + Text(": ")
|
||||
}
|
||||
Text("marked deleted")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.italic()
|
||||
CIMetaView(chatItem: chatItem)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
.padding(.leading, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(chatItemFrameColor(chatItem, colorScheme))
|
||||
.cornerRadius(18)
|
||||
.textSelection(.disabled)
|
||||
}
|
||||
}
|
||||
|
||||
struct MarkedDeletedItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
MarkedDeletedItemView(chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, true, false))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 200))
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,42 @@ struct ChatItemView: View {
|
||||
var showMember = false
|
||||
var maxWidth: CGFloat = .infinity
|
||||
@State var scrollProxy: ScrollViewProxy? = nil
|
||||
@Binding var revealed: Bool
|
||||
|
||||
var body: some View {
|
||||
let ci = chatItem
|
||||
if chatItem.meta.itemDeleted && !revealed {
|
||||
MarkedDeletedItemView(chatItem: chatItem, showMember: showMember)
|
||||
} else if ci.quotedItem == nil && !ci.meta.itemDeleted {
|
||||
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
|
||||
EmojiItemView(chatItem: ci)
|
||||
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
|
||||
CIVoiceView(chatItem: ci, recordingFile: ci.file, duration: duration)
|
||||
} else if ci.content.msgContent == nil {
|
||||
ChatItemContentView(chatInfo: chatInfo, chatItem: chatItem, showMember: showMember, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
|
||||
} else {
|
||||
framedItemView()
|
||||
}
|
||||
} else {
|
||||
framedItemView()
|
||||
}
|
||||
}
|
||||
|
||||
private func framedItemView() -> some View {
|
||||
FramedItemView(chatInfo: chatInfo, chatItem: chatItem, showMember: showMember, maxWidth: maxWidth, scrollProxy: scrollProxy)
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatItemContentView<Content: View>: View {
|
||||
var chatInfo: ChatInfo
|
||||
var chatItem: ChatItem
|
||||
var showMember: Bool
|
||||
var msgContentView: () -> Content
|
||||
|
||||
var body: some View {
|
||||
switch chatItem.content {
|
||||
case .sndMsgContent: contentItemView()
|
||||
case .rcvMsgContent: contentItemView()
|
||||
case .sndMsgContent: msgContentView()
|
||||
case .rcvMsgContent: msgContentView()
|
||||
case .sndDeleted: deletedItemView()
|
||||
case .rcvDeleted: deletedItemView()
|
||||
case let .sndCall(status, duration): callItemView(status, duration)
|
||||
@@ -40,17 +71,6 @@ struct ChatItemView: View {
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func contentItemView() -> some View {
|
||||
if (chatItem.quotedItem == nil && chatItem.file == nil && isShortEmoji(chatItem.content.text)) {
|
||||
EmojiItemView(chatItem: chatItem)
|
||||
} else if chatItem.quotedItem == nil && chatItem.content.text.isEmpty,
|
||||
case let .voice(_, duration) = chatItem.content.msgContent {
|
||||
CIVoiceView(chatItem: chatItem, recordingFile: chatItem.file, duration: duration)
|
||||
} else {
|
||||
FramedItemView(chatInfo: chatInfo, chatItem: chatItem, showMember: showMember, maxWidth: maxWidth, scrollProxy: scrollProxy)
|
||||
}
|
||||
}
|
||||
|
||||
private func deletedItemView() -> some View {
|
||||
DeletedItemView(chatItem: chatItem, showMember: showMember)
|
||||
}
|
||||
@@ -75,12 +95,67 @@ struct ChatItemView: View {
|
||||
struct ChatItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group{
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getDeletedContentSample())
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .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.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, true, false), revealed: Binding.constant(false))
|
||||
ChatItemView(chatInfo: ChatInfo.sampleData.direct, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, true, false), revealed: Binding.constant(true))
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false))
|
||||
Group{
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, true, false, false),
|
||||
content: .rcvIntegrityError(msgError: .msgSkipped(fromMsgId: 1, toMsgId: 2)),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
),
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, true, false, false),
|
||||
content: .rcvGroupInvitation(groupInvitation: CIGroupInvitation.getSample(status: .pending), memberRole: .admin),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
),
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, true, false, false),
|
||||
content: .rcvGroupEvent(rcvGroupEvent: .memberAdded(groupMemberId: 1, profile: Profile.sampleData)),
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
),
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
ChatItemView(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItem: ChatItem(
|
||||
chatDir: .directRcv,
|
||||
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, true, false, false),
|
||||
content: ciFeatureContent,
|
||||
quotedItem: nil,
|
||||
file: nil
|
||||
),
|
||||
revealed: Binding.constant(true)
|
||||
)
|
||||
}
|
||||
.previewLayout(.fixed(width: 360, height: 70))
|
||||
}
|
||||
|
||||
@@ -402,128 +402,180 @@ struct ChatView: View {
|
||||
Rectangle().fill(.clear)
|
||||
.frame(width: memberImageSize, height: memberImageSize)
|
||||
}
|
||||
chatItemWithMenu(ci, maxWidth, showMember: showMember).padding(.leading, 8)
|
||||
ChatItemWithMenu(
|
||||
chat: chat,
|
||||
ci: ci,
|
||||
showMember: showMember,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: scrollProxy,
|
||||
deleteMessage: deleteMessage,
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
).padding(.leading, 8)
|
||||
}
|
||||
.padding(.trailing)
|
||||
.padding(.leading, 12)
|
||||
} else {
|
||||
chatItemWithMenu(ci, maxWidth).padding(.horizontal)
|
||||
ChatItemWithMenu(
|
||||
chat: chat,
|
||||
ci: ci,
|
||||
maxWidth: maxWidth,
|
||||
scrollProxy: scrollProxy,
|
||||
deleteMessage: deleteMessage,
|
||||
deletingItem: $deletingItem,
|
||||
composeState: $composeState,
|
||||
showDeleteMessage: $showDeleteMessage
|
||||
).padding(.horizontal)
|
||||
}
|
||||
}
|
||||
|
||||
private func chatItemWithMenu(_ ci: ChatItem, _ maxWidth: CGFloat, showMember: Bool = false) -> some View {
|
||||
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
|
||||
var menu: [UIAction] = []
|
||||
if let mc = ci.content.msgContent {
|
||||
menu.append(contentsOf: [
|
||||
UIAction(
|
||||
title: NSLocalizedString("Reply", comment: "chat item action"),
|
||||
image: UIImage(systemName: "arrowshape.turn.up.left")
|
||||
) { _ in
|
||||
withAnimation {
|
||||
if composeState.editing {
|
||||
composeState = ComposeState(contextItem: .quotedItem(chatItem: ci))
|
||||
} else {
|
||||
composeState = composeState.copy(contextItem: .quotedItem(chatItem: ci))
|
||||
private struct ChatItemWithMenu: View {
|
||||
var chat: Chat
|
||||
var ci: ChatItem
|
||||
var showMember: Bool = false
|
||||
var maxWidth: CGFloat
|
||||
var scrollProxy: ScrollViewProxy?
|
||||
var deleteMessage: (CIDeleteMode) -> Void
|
||||
@Binding var deletingItem: ChatItem?
|
||||
@Binding var composeState: ComposeState
|
||||
@Binding var showDeleteMessage: Bool
|
||||
|
||||
@State private var revealed = false
|
||||
|
||||
var body: some View {
|
||||
let alignment: Alignment = ci.chatDir.sent ? .trailing : .leading
|
||||
|
||||
ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, showMember: showMember, maxWidth: maxWidth, scrollProxy: scrollProxy, revealed: $revealed)
|
||||
.uiKitContextMenu(actions: menu())
|
||||
.confirmationDialog("Delete message?", isPresented: $showDeleteMessage, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessage(.cidmInternal)
|
||||
}
|
||||
if let di = deletingItem, di.meta.editable {
|
||||
Button(broadcastDeleteButtonText, role: .destructive) {
|
||||
deleteMessage(.cidmBroadcast)
|
||||
}
|
||||
}
|
||||
},
|
||||
UIAction(
|
||||
title: NSLocalizedString("Share", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.up")
|
||||
) { _ in
|
||||
var shareItems: [Any] = [ci.content.text]
|
||||
if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
|
||||
shareItems.append(image)
|
||||
}
|
||||
showShareSheet(items: shareItems)
|
||||
},
|
||||
UIAction(
|
||||
title: NSLocalizedString("Copy", comment: "chat item action"),
|
||||
image: UIImage(systemName: "doc.on.doc")
|
||||
) { _ in
|
||||
if case let .image(text, _) = ci.content.msgContent,
|
||||
text == "",
|
||||
let image = getLoadedImage(ci.file) {
|
||||
UIPasteboard.general.image = image
|
||||
} else {
|
||||
UIPasteboard.general.string = ci.content.text
|
||||
}
|
||||
}
|
||||
])
|
||||
if let filePath = getLoadedFilePath(ci.file) {
|
||||
if case .image = ci.content.msgContent,
|
||||
let image = UIImage(contentsOfFile: filePath) {
|
||||
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
|
||||
}
|
||||
|
||||
private func menu() -> [UIAction] {
|
||||
var menu: [UIAction] = []
|
||||
if let mc = ci.content.msgContent, !ci.meta.itemDeleted || revealed {
|
||||
if !ci.meta.itemDeleted {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Save", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.down")
|
||||
title: NSLocalizedString("Reply", comment: "chat item action"),
|
||||
image: UIImage(systemName: "arrowshape.turn.up.left")
|
||||
) { _ in
|
||||
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Save", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.down")
|
||||
) { _ in
|
||||
let fileURL = URL(fileURLWithPath: filePath)
|
||||
showShareSheet(items: [fileURL])
|
||||
withAnimation {
|
||||
if composeState.editing {
|
||||
composeState = ComposeState(contextItem: .quotedItem(chatItem: ci))
|
||||
} else {
|
||||
composeState = composeState.copy(contextItem: .quotedItem(chatItem: ci))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if ci.meta.editable,
|
||||
!mc.isVoice {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Edit", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.pencil")
|
||||
title: NSLocalizedString("Share", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.up")
|
||||
) { _ in
|
||||
withAnimation {
|
||||
composeState = ComposeState(editingItem: ci)
|
||||
var shareItems: [Any] = [ci.content.text]
|
||||
if case .image = ci.content.msgContent, let image = getLoadedImage(ci.file) {
|
||||
shareItems.append(image)
|
||||
}
|
||||
showShareSheet(items: shareItems)
|
||||
}
|
||||
)
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Copy", comment: "chat item action"),
|
||||
image: UIImage(systemName: "doc.on.doc")
|
||||
) { _ in
|
||||
if case let .image(text, _) = ci.content.msgContent,
|
||||
text == "",
|
||||
let image = getLoadedImage(ci.file) {
|
||||
UIPasteboard.general.image = image
|
||||
} else {
|
||||
UIPasteboard.general.string = ci.content.text
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Delete", comment: "chat item action"),
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: [.destructive]
|
||||
) { _ in
|
||||
showDeleteMessage = true
|
||||
deletingItem = ci
|
||||
}
|
||||
)
|
||||
} else if ci.isDeletedContent {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Delete", comment: "chat item action"),
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: [.destructive]
|
||||
) { _ in
|
||||
showDeleteMessage = true
|
||||
deletingItem = ci
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return ChatItemView(chatInfo: chat.chatInfo, chatItem: ci, showMember: showMember, maxWidth: maxWidth, scrollProxy: scrollProxy)
|
||||
.uiKitContextMenu(actions: menu)
|
||||
.confirmationDialog("Delete message?", isPresented: $showDeleteMessage, titleVisibility: .visible) {
|
||||
Button("Delete for me", role: .destructive) {
|
||||
deleteMessage(.cidmInternal)
|
||||
}
|
||||
if let di = deletingItem, di.meta.editable {
|
||||
Button("Delete for everyone",role: .destructive) {
|
||||
deleteMessage(.cidmBroadcast)
|
||||
if let filePath = getLoadedFilePath(ci.file) {
|
||||
if case .image = ci.content.msgContent, let image = UIImage(contentsOfFile: filePath) {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Save", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.down")
|
||||
) { _ in
|
||||
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Save", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.arrow.down")
|
||||
) { _ in
|
||||
let fileURL = URL(fileURLWithPath: filePath)
|
||||
showShareSheet(items: [fileURL])
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if ci.meta.editable && !mc.isVoice {
|
||||
menu.append(
|
||||
UIAction(
|
||||
title: NSLocalizedString("Edit", comment: "chat item action"),
|
||||
image: UIImage(systemName: "square.and.pencil")
|
||||
) { _ in
|
||||
withAnimation {
|
||||
composeState = ComposeState(editingItem: ci)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
menu.append(deleteUIAction())
|
||||
} else if ci.meta.itemDeleted {
|
||||
menu.append(revealUIAction())
|
||||
menu.append(deleteUIAction())
|
||||
} else if ci.isDeletedContent {
|
||||
menu.append(deleteUIAction())
|
||||
}
|
||||
.frame(maxWidth: maxWidth, maxHeight: .infinity, alignment: alignment)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: alignment)
|
||||
return menu
|
||||
}
|
||||
|
||||
private func deleteUIAction() -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Delete", comment: "chat item action"),
|
||||
image: UIImage(systemName: "trash"),
|
||||
attributes: [.destructive]
|
||||
) { _ in
|
||||
showDeleteMessage = true
|
||||
deletingItem = ci
|
||||
}
|
||||
}
|
||||
|
||||
private func revealUIAction() -> UIAction {
|
||||
UIAction(
|
||||
title: NSLocalizedString("Reveal", comment: "chat item action"),
|
||||
image: UIImage(systemName: "eye")
|
||||
) { _ in
|
||||
withAnimation {
|
||||
revealed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var broadcastDeleteButtonText: LocalizedStringKey {
|
||||
chat.chatInfo.fullDeletionAllowed ? "Delete for everyone" : "Mark deleted for everyone"
|
||||
}
|
||||
}
|
||||
|
||||
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
|
||||
@@ -552,7 +604,7 @@ struct ChatView: View {
|
||||
logger.debug("ChatView deleteMessage: in Task")
|
||||
do {
|
||||
if let di = deletingItem {
|
||||
let toItem = try await apiDeleteChatItem(
|
||||
let (deletedItem, toItem) = try await apiDeleteChatItem(
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
itemId: di.id,
|
||||
@@ -560,7 +612,11 @@ struct ChatView: View {
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
deletingItem = nil
|
||||
let _ = chatModel.removeChatItem(chat.chatInfo, toItem)
|
||||
if let toItem = toItem {
|
||||
_ = chatModel.upsertChatItem(chat.chatInfo, toItem)
|
||||
} else {
|
||||
chatModel.removeChatItem(chat.chatInfo, deletedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -16,10 +16,6 @@ struct ContextItemView: View {
|
||||
let cancelContextItem: () -> Void
|
||||
|
||||
var body: some View {
|
||||
let bgColor = contextItem.chatDir.sent
|
||||
? (colorScheme == .light ? sentColorLight : sentColorDark)
|
||||
: Color(uiColor: .tertiarySystemGroupedBackground)
|
||||
|
||||
HStack {
|
||||
Image(systemName: contextIcon)
|
||||
.resizable()
|
||||
@@ -45,7 +41,7 @@ struct ContextItemView: View {
|
||||
.padding(12)
|
||||
.frame(minHeight: 50)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(bgColor)
|
||||
.background(chatItemFrameColor(contextItem, colorScheme))
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ struct ContactPreferencesView: View {
|
||||
|
||||
VStack {
|
||||
List {
|
||||
// featureSection(.fullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, $featuresAllowed.fullDelete)
|
||||
featureSection(.fullDelete, user.fullPreferences.fullDelete.allow, contact.mergedPreferences.fullDelete, $featuresAllowed.fullDelete)
|
||||
featureSection(.voice, user.fullPreferences.voice.allow, contact.mergedPreferences.voice, $featuresAllowed.voice)
|
||||
|
||||
Section {
|
||||
|
||||
@@ -18,7 +18,7 @@ struct GroupPreferencesView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
// featureSection(.fullDelete, $preferences.fullDelete.enable)
|
||||
featureSection(.fullDelete, $preferences.fullDelete.enable)
|
||||
featureSection(.directMessages, $preferences.directMessages.enable)
|
||||
featureSection(.voice, $preferences.voice.enable)
|
||||
|
||||
|
||||
@@ -101,8 +101,10 @@ struct ChatPreviewView: View {
|
||||
|
||||
@ViewBuilder private func chatPreviewText(_ cItem: ChatItem?) -> some View {
|
||||
if let cItem = cItem {
|
||||
let itemText = !cItem.meta.itemDeleted ? cItem.text : NSLocalizedString("marked deleted", comment: "marked deleted chat item preview text")
|
||||
let itemFormattedText = !cItem.meta.itemDeleted ? cItem.formattedText : nil
|
||||
ZStack(alignment: .topTrailing) {
|
||||
(itemStatusMark(cItem) + messageText(cItem.text, cItem.formattedText, cItem.memberDisplayName, preview: true))
|
||||
(itemStatusMark(cItem) + messageText(itemText, itemFormattedText, cItem.memberDisplayName, preview: true))
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.leading)
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
@@ -204,6 +206,10 @@ struct ChatPreviewView_Previews: PreviewProvider {
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent)],
|
||||
chatStats: ChatStats(unreadCount: 11, minUnreadItemId: 0)
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent, true, false)]
|
||||
))
|
||||
ChatPreviewView(chat: Chat(
|
||||
chatInfo: ChatInfo.sampleData.direct,
|
||||
chatItems: [ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent)],
|
||||
|
||||
@@ -18,7 +18,7 @@ struct PreferencesView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
// featureSection(.fullDelete, $preferences.fullDelete.allow)
|
||||
featureSection(.fullDelete, $preferences.fullDelete.allow)
|
||||
featureSection(.voice, $preferences.voice.allow)
|
||||
|
||||
Section {
|
||||
|
||||
@@ -74,11 +74,6 @@
|
||||
5CA059ED279559F40002BEB4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059C4279559F40002BEB4 /* ContentView.swift */; };
|
||||
5CA059EF279559F40002BEB4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5CA059C5279559F40002BEB4 /* Assets.xcassets */; };
|
||||
5CA7DFC329302AF000F7FDDE /* AppSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA7DFC229302AF000F7FDDE /* AppSheet.swift */; };
|
||||
5CA7DFD32933E16C00F7FDDE /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA7DFCE2933E16B00F7FDDE /* libffi.a */; };
|
||||
5CA7DFD42933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA7DFCF2933E16B00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W-ghc8.10.7.a */; };
|
||||
5CA7DFD52933E16C00F7FDDE /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA7DFD02933E16C00F7FDDE /* libgmp.a */; };
|
||||
5CA7DFD62933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA7DFD12933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W.a */; };
|
||||
5CA7DFD72933E16C00F7FDDE /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CA7DFD22933E16C00F7FDDE /* libgmpxx.a */; };
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CADE79929211BB900072E13 /* PreferencesView.swift */; };
|
||||
5CADE79C292131E900072E13 /* ContactPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CADE79B292131E900072E13 /* ContactPreferencesView.swift */; };
|
||||
5CB0BA882826CB3A00B3292C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CB0BA862826CB3A00B3292C /* InfoPlist.strings */; };
|
||||
@@ -141,6 +136,12 @@
|
||||
644EFFDE292BCD9D00525D5B /* ComposeVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */; };
|
||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */; };
|
||||
644EFFEC293B5B1800525D5B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644EFFE7293B5B1700525D5B /* libgmpxx.a */; };
|
||||
644EFFED293B5B1800525D5B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644EFFE8293B5B1700525D5B /* libgmp.a */; };
|
||||
644EFFEE293B5B1800525D5B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644EFFE9293B5B1800525D5B /* libffi.a */; };
|
||||
644EFFEF293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644EFFEA293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X-ghc8.10.7.a */; };
|
||||
644EFFF0293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 644EFFEB293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X.a */; };
|
||||
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
|
||||
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; };
|
||||
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; };
|
||||
@@ -284,11 +285,6 @@
|
||||
5CA059DB279559F40002BEB4 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = "<group>"; };
|
||||
5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOSLaunchTests.swift; sourceTree = "<group>"; };
|
||||
5CA7DFC229302AF000F7FDDE /* AppSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSheet.swift; sourceTree = "<group>"; };
|
||||
5CA7DFCE2933E16B00F7FDDE /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CA7DFCF2933E16B00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CA7DFD02933E16C00F7FDDE /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CA7DFD12933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W.a"; sourceTree = "<group>"; };
|
||||
5CA7DFD22933E16C00F7FDDE /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CADE79929211BB900072E13 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
|
||||
5CADE79B292131E900072E13 /* ContactPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactPreferencesView.swift; sourceTree = "<group>"; };
|
||||
5CB0BA872826CB3A00B3292C /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@@ -351,6 +347,12 @@
|
||||
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
|
||||
644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkedDeletedItemView.swift; sourceTree = "<group>"; };
|
||||
644EFFE7293B5B1700525D5B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
644EFFE8293B5B1700525D5B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
644EFFE9293B5B1800525D5B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
644EFFEA293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
644EFFEB293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X.a"; sourceTree = "<group>"; };
|
||||
6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = "<group>"; };
|
||||
646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/System/Library/Frameworks/LocalAuthentication.framework; sourceTree = DEVELOPER_DIR; };
|
||||
646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = "<group>"; };
|
||||
@@ -396,13 +398,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
644EFFEC293B5B1800525D5B /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CA7DFD52933E16C00F7FDDE /* libgmp.a in Frameworks */,
|
||||
5CA7DFD62933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W.a in Frameworks */,
|
||||
5CA7DFD42933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W-ghc8.10.7.a in Frameworks */,
|
||||
5CA7DFD32933E16C00F7FDDE /* libffi.a in Frameworks */,
|
||||
644EFFED293B5B1800525D5B /* libgmp.a in Frameworks */,
|
||||
644EFFF0293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X.a in Frameworks */,
|
||||
644EFFEE293B5B1800525D5B /* libffi.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5CA7DFD72933E16C00F7FDDE /* libgmpxx.a in Frameworks */,
|
||||
644EFFEF293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X-ghc8.10.7.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -458,11 +460,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CA7DFCE2933E16B00F7FDDE /* libffi.a */,
|
||||
5CA7DFD02933E16C00F7FDDE /* libgmp.a */,
|
||||
5CA7DFD22933E16C00F7FDDE /* libgmpxx.a */,
|
||||
5CA7DFCF2933E16B00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W-ghc8.10.7.a */,
|
||||
5CA7DFD12933E16C00F7FDDE /* libHSsimplex-chat-4.3.0-KONDmy6IJf0HwVSmQIx39W.a */,
|
||||
644EFFE9293B5B1800525D5B /* libffi.a */,
|
||||
644EFFE8293B5B1700525D5B /* libgmp.a */,
|
||||
644EFFE7293B5B1700525D5B /* libgmpxx.a */,
|
||||
644EFFEA293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X-ghc8.10.7.a */,
|
||||
644EFFEB293B5B1800525D5B /* libHSsimplex-chat-4.3.0-GPZqasc9wbbBzOZyUtfq0X.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -679,6 +681,7 @@
|
||||
6440C9FF288857A10062C672 /* CIEventView.swift */,
|
||||
5C58BCD5292BEBE600AF9E4F /* CIChatFeatureView.swift */,
|
||||
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */,
|
||||
644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */,
|
||||
);
|
||||
path = ChatItem;
|
||||
sourceTree = "<group>";
|
||||
@@ -1022,6 +1025,7 @@
|
||||
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */,
|
||||
5C9C2DA7289957AE00CC63B1 /* AdvancedNetworkSettings.swift in Sources */,
|
||||
5CADE79A29211BB900072E13 /* PreferencesView.swift in Sources */,
|
||||
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -332,7 +332,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case newChatItem(chatItem: AChatItem)
|
||||
case chatItemStatusUpdated(chatItem: AChatItem)
|
||||
case chatItemUpdated(chatItem: AChatItem)
|
||||
case chatItemDeleted(deletedChatItem: AChatItem, toChatItem: AChatItem)
|
||||
case chatItemDeleted(deletedChatItem: AChatItem, toChatItem: AChatItem?, byUser: Bool)
|
||||
case contactsList(contacts: [Contact])
|
||||
// group events
|
||||
case groupCreated(groupInfo: GroupInfo)
|
||||
@@ -538,7 +538,7 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .newChatItem(chatItem): return String(describing: chatItem)
|
||||
case let .chatItemStatusUpdated(chatItem): return String(describing: chatItem)
|
||||
case let .chatItemUpdated(chatItem): return String(describing: chatItem)
|
||||
case let .chatItemDeleted(deletedChatItem, toChatItem): return "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))"
|
||||
case let .chatItemDeleted(deletedChatItem, toChatItem, byUser): return "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))\nbyUser: \(byUser)"
|
||||
case let .contactsList(contacts): return String(describing: contacts)
|
||||
case let .groupCreated(groupInfo): return String(describing: groupInfo)
|
||||
case let .sentGroupInvitation(groupInfo, contact, member): return "groupInfo: \(groupInfo)\ncontact: \(contact)\nmember: \(member)"
|
||||
|
||||
@@ -54,7 +54,7 @@ public struct Profile: Codable, NamedChat {
|
||||
(fullName == "" || displayName == fullName) ? displayName : "\(displayName) (\(fullName))"
|
||||
}
|
||||
|
||||
static let sampleData = Profile(
|
||||
public static let sampleData = Profile(
|
||||
displayName: "alice",
|
||||
fullName: "Alice"
|
||||
)
|
||||
@@ -695,7 +695,15 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.mergedPreferences.voice.enabled.forUser
|
||||
case let .group(groupInfo): return groupInfo.fullGroupPreferences.voice.on
|
||||
default: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
public var fullDeletionAllowed: Bool {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.mergedPreferences.fullDelete.enabled.forUser
|
||||
case let .group(groupInfo): return groupInfo.fullGroupPreferences.fullDelete.on
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1366,6 +1374,7 @@ public struct ChatItem: Identifiable, Decodable {
|
||||
|
||||
public var timestampText: Text { meta.timestampText }
|
||||
|
||||
// pair with formattedText
|
||||
public var text: String {
|
||||
switch (content.text, content.msgContent, file) {
|
||||
case let ("", .some(.voice(_, duration)), _): return "Voice message (\(durationText(duration)))"
|
||||
@@ -1766,6 +1775,13 @@ public enum MsgContent {
|
||||
}
|
||||
}
|
||||
|
||||
public var isText: Bool {
|
||||
switch self {
|
||||
case .text: return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
public var isVoice: Bool {
|
||||
switch self {
|
||||
case .voice: return true
|
||||
|
||||
Reference in New Issue
Block a user