ios: simplify chat item context menu (#1483)

This commit is contained in:
JRoberts
2022-12-03 15:53:46 +04:00
committed by GitHub
parent 07ef6e4090
commit 48d24d3582

View File

@@ -34,7 +34,7 @@ struct ChatView: View {
// opening GroupMemberInfoView on member icon
@State private var selectedMember: GroupMember? = nil
@State private var memberConnectionStats: ConnectionStats?
var body: some View {
let cInfo = chat.chatInfo
return VStack(spacing: 0) {
@@ -48,9 +48,9 @@ struct ChatView: View {
floatingButtons(proxy)
}
}
Spacer(minLength: 0)
ComposeView(
chat: chat,
composeState: $composeState,
@@ -170,16 +170,16 @@ struct ChatView: View {
}
}
}
private func searchToolbar() -> some View {
HStack {
HStack {
Image(systemName: "magnifyingglass")
TextField("Search", text: $searchText)
.focused($searchFocussed)
.foregroundColor(.primary)
.frame(maxWidth: .infinity)
.focused($searchFocussed)
.foregroundColor(.primary)
.frame(maxWidth: .infinity)
Button {
searchText = ""
} label: {
@@ -190,7 +190,7 @@ struct ChatView: View {
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)
Button ("Cancel") {
searchText = ""
searchMode = false
@@ -204,37 +204,37 @@ struct ChatView: View {
.padding(.horizontal)
.padding(.vertical, 8)
}
private func chatItemsList() -> some View {
let cInfo = chat.chatInfo
return GeometryReader { g in
ScrollViewReader { proxy in
ScrollView {
let maxWidth =
cInfo.chatType == .group
? (g.size.width - 28) * 0.84 - 42
: (g.size.width - 32) * 0.84
cInfo.chatType == .group
? (g.size.width - 28) * 0.84 - 42
: (g.size.width - 32) * 0.84
LazyVStack(spacing: 5) {
ForEach(chatModel.reversedChatItems, id: \.viewId) { ci in
chatItemView(ci, maxWidth)
.scaleEffect(x: 1, y: -1, anchor: .center)
.onAppear {
itemsInView.insert(ci.viewId)
loadChatItems(cInfo, ci, proxy)
if ci.isRcvNew() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
if chatModel.chatId == cInfo.id && itemsInView.contains(ci.viewId) {
Task {
await apiMarkChatItemRead(cInfo, ci)
NtfManager.shared.decNtfBadgeCount()
.scaleEffect(x: 1, y: -1, anchor: .center)
.onAppear {
itemsInView.insert(ci.viewId)
loadChatItems(cInfo, ci, proxy)
if ci.isRcvNew() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
if chatModel.chatId == cInfo.id && itemsInView.contains(ci.viewId) {
Task {
await apiMarkChatItemRead(cInfo, ci)
NtfManager.shared.decNtfBadgeCount()
}
}
}
}
}
}
.onDisappear {
itemsInView.remove(ci.viewId)
}
.onDisappear {
itemsInView.remove(ci.viewId)
}
}
}
}
@@ -258,7 +258,7 @@ struct ChatView: View {
}
.scaleEffect(x: 1, y: -1, anchor: .center)
}
private func floatingButtons(_ proxy: ScrollViewProxy) -> some View {
let counts = chatModel.unreadChatItemCounts(itemsInView: itemsInView)
return VStack {
@@ -300,7 +300,7 @@ struct ChatView: View {
}
.padding()
}
private func circleButton<Content: View>(_ content: @escaping () -> Content) -> some View {
ZStack {
Circle()
@@ -309,7 +309,7 @@ struct ChatView: View {
content()
}
}
private func callButton(_ contact: Contact, _ media: CallMediaType, imageName: String) -> some View {
Button {
CallController.shared.startCall(contact, media)
@@ -317,7 +317,7 @@ struct ChatView: View {
Image(systemName: imageName)
}
}
private func searchButton() -> some View {
Button {
searchMode = true
@@ -327,7 +327,7 @@ struct ChatView: View {
Label("Search", systemImage: "magnifyingglass")
}
}
private func addMembersButton() -> some View {
Button {
if case let .group(gInfo) = chat.chatInfo {
@@ -343,7 +343,7 @@ struct ChatView: View {
Image(systemName: "person.crop.circle.badge.plus")
}
}
private func loadChatItems(_ cInfo: ChatInfo, _ ci: ChatItem, _ proxy: ScrollViewProxy) {
if let firstItem = chatModel.reversedChatItems.last, firstItem.id == ci.id {
if loadingItems || firstPage { return }
@@ -371,7 +371,7 @@ struct ChatView: View {
}
}
}
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
if case let .groupRcv(member) = ci.chatDir,
case let .group(groupInfo) = chat.chatInfo {
@@ -429,7 +429,7 @@ struct ChatView: View {
).padding(.horizontal)
}
}
private struct ChatItemWithMenu: View {
var chat: Chat
var ci: ChatItem
@@ -440,12 +440,12 @@ struct ChatView: View {
@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) {
@@ -461,85 +461,24 @@ struct ChatView: View {
.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("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))
}
}
}
)
menu.append(replyUIAction())
}
menu.append(
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)
}
)
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(shareUIAction())
menu.append(copyUIAction())
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)
}
)
menu.append(saveImageAction(image))
} 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])
}
)
menu.append(saveFileAction(filePath))
}
}
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(editAction())
}
menu.append(deleteUIAction())
} else if ci.meta.itemDeleted {
@@ -551,6 +490,79 @@ struct ChatView: View {
return menu
}
private func replyUIAction() -> UIAction {
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 func shareUIAction() -> UIAction {
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)
}
}
private func copyUIAction() -> UIAction {
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
}
}
}
private func saveImageAction(_ image: UIImage) -> UIAction {
UIAction(
title: NSLocalizedString("Save", comment: "chat item action"),
image: UIImage(systemName: "square.and.arrow.down")
) { _ in
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
private func saveFileAction(_ filePath: String) -> UIAction {
UIAction(
title: NSLocalizedString("Save", comment: "chat item action"),
image: UIImage(systemName: "square.and.arrow.down")
) { _ in
let fileURL = URL(fileURLWithPath: filePath)
showShareSheet(items: [fileURL])
}
}
private func editAction() -> UIAction {
UIAction(
title: NSLocalizedString("Edit", comment: "chat item action"),
image: UIImage(systemName: "square.and.pencil")
) { _ in
withAnimation {
composeState = ComposeState(editingItem: ci)
}
}
}
private func deleteUIAction() -> UIAction {
UIAction(
title: NSLocalizedString("Delete", comment: "chat item action"),
@@ -561,7 +573,7 @@ struct ChatView: View {
deletingItem = ci
}
}
private func revealUIAction() -> UIAction {
UIAction(
title: NSLocalizedString("Reveal", comment: "chat item action"),
@@ -572,12 +584,12 @@ struct ChatView: View {
}
}
}
private var broadcastDeleteButtonText: LocalizedStringKey {
chat.chatInfo.fullDeletionAllowed ? "Delete for everyone" : "Mark deleted for everyone"
}
}
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
switch (prevItem?.chatDir) {
case .groupSnd: return true
@@ -585,19 +597,19 @@ struct ChatView: View {
default: return false
}
}
private func scrollToBottom(_ proxy: ScrollViewProxy) {
if let ci = chatModel.reversedChatItems.first {
withAnimation { proxy.scrollTo(ci.viewId, anchor: .top) }
}
}
private func scrollUp(_ proxy: ScrollViewProxy) {
if let ci = chatModel.topItemInView(itemsInView: itemsInView) {
withAnimation { proxy.scrollTo(ci.viewId, anchor: .top) }
}
}
private func deleteMessage(_ mode: CIDeleteMode) {
logger.debug("ChatView deleteMessage")
Task {