From fb8b273bdea5cc2e2ec1d929066686e5a75dc1b9 Mon Sep 17 00:00:00 2001 From: 12944qwerty Date: Sun, 2 Jul 2023 14:33:37 -0500 Subject: [PATCH 1/6] channel context menu --- Swiftcord/Views/Server/ChannelList.swift | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/Swiftcord/Views/Server/ChannelList.swift b/Swiftcord/Views/Server/ChannelList.swift index b16497b4..0a154a16 100644 --- a/Swiftcord/Views/Server/ChannelList.swift +++ b/Swiftcord/Views/Server/ChannelList.swift @@ -29,6 +29,26 @@ struct ChannelList: View { Circle().fill(.primary).frame(width: 8, height: 8).offset(x: 2) } }) + .contextMenu { + let isRead = gateway.readState[channel.id]?.id == channel.last_message_id + Button(action: { readChannel(channel) }) { + Image(systemName: isRead ? "message" : "message.badge") + Text("Mark as read") + }.disabled(isRead) + + Divider() + + Group { + Button(action: { copyLink(channel) }) { + Image(systemName: "link") + Text("Copy Link") + } + Button(action: { copyId(channel) }) { + Image(systemName: "number.circle.fill") + Text("Copy ID") + } + } + } } var body: some View { @@ -68,6 +88,19 @@ struct ChannelList: View { Section(header: Text(channel.name ?? "").textCase(.uppercase).padding(.leading, 8)) { ForEach(channels, id: \.id) { channel in item(for: channel) } } + .contextMenu { + Button(action: { readChannels(channels) }) { + Image(systemName: "message.badge") + Text("Mark as read") + } + + Divider() + + Button(action: { copyId(channel) }) { + Image(systemName: "number.circle.fill") + Text("Copy ID") + } + } } } } @@ -83,3 +116,33 @@ struct ChannelList: View { .environment(\.defaultMinListRowHeight, 1) } } + +private extension ChannelList { + func readChannels(_ channels: [Channel]) { + for channel in channels { + readChannel(channel) + } + } + + func readChannel(_ channel: Channel) { + gateway.readState[channel.id] = gateway.readState[channel.id]?.updatingLastMessage(id: channel.last_message_id ?? "") + } + + func copyLink(_ channel: Channel) { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString( + "https://canary.discord.com/channels/\(channel.guild_id ?? "@me")/\(channel.id)", + forType: .string + ) + } + + func copyId(_ channel: Channel) { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString( + channel.id, + forType: .string + ) + } +} From 21d8f20cb3be696d23b00229b9497233a33610c1 Mon Sep 17 00:00:00 2001 From: 12944qwerty Date: Sun, 2 Jul 2023 14:35:33 -0500 Subject: [PATCH 2/6] show guild popover with max width --- Swiftcord/Views/Server/ServerButton.swift | 36 +++++++++++++---------- Swiftcord/Views/Server/ServerFolder.swift | 1 + 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Swiftcord/Views/Server/ServerButton.swift b/Swiftcord/Views/Server/ServerButton.swift index 74027f76..8be5fb16 100644 --- a/Swiftcord/Views/Server/ServerButton.swift +++ b/Swiftcord/Views/Server/ServerButton.swift @@ -32,23 +32,27 @@ struct ServerButton: View { .animation(capsuleAnimation, value: hovered) Button("", action: onSelect) - .buttonStyle( - ServerButtonStyle( - selected: selected, - name: name, - bgColor: bgColor, - systemName: systemIconName, - assetName: assetIconName, - serverIconURL: serverIconURL, - loading: isLoading, - hovered: $hovered + .buttonStyle( + ServerButtonStyle( + selected: selected, + name: name, + bgColor: bgColor, + systemName: systemIconName, + assetName: assetIconName, + serverIconURL: serverIconURL, + loading: isLoading, + hovered: $hovered + ) ) - ) - /*.popover(isPresented: .constant(true)) { - Text(name).padding(8) - }*/ - .padding(.trailing, 8) - + .popover(isPresented: $hovered) { + Text(name) + .font(.title3) + .padding(8) + .frame(maxWidth: 300) + .interactiveDismissDisabled() + } + .padding(.trailing, 8) + Spacer() } .frame(width: 72, height: 48) diff --git a/Swiftcord/Views/Server/ServerFolder.swift b/Swiftcord/Views/Server/ServerFolder.swift index 43f25b7a..826bff3f 100644 --- a/Swiftcord/Views/Server/ServerFolder.swift +++ b/Swiftcord/Views/Server/ServerFolder.swift @@ -80,6 +80,7 @@ struct ServerFolder: View { Text(folder.name) .font(.title3) .padding(10) + .frame(maxWidth: 300) // Prevent popover from blocking clicks to other views .interactiveDismissDisabled() } From c699143fcb12e23640a1ea69feac0bbce1deab1a Mon Sep 17 00:00:00 2001 From: 12944qwerty Date: Sun, 2 Jul 2023 14:38:32 -0500 Subject: [PATCH 3/6] guild contextmenus --- Swiftcord/Views/ContentView.swift | 1 + Swiftcord/Views/Server/ServerButton.swift | 75 +++++++++++++++++++---- Swiftcord/Views/Server/ServerFolder.swift | 1 + 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/Swiftcord/Views/ContentView.swift b/Swiftcord/Views/ContentView.swift index d47f3d59..9f1524bd 100644 --- a/Swiftcord/Views/ContentView.swift +++ b/Swiftcord/Views/ContentView.swift @@ -105,6 +105,7 @@ struct ContentView: View { case .guild(let guild): ServerButton( selected: state.selectedGuildID == guild.id || loadingGuildID == guild.id, + guild: guild, name: guild.name, serverIconURL: guild.icon != nil ? "\(DiscordKitConfig.default.cdnURL)icons/\(guild.id)/\(guild.icon!).webp?size=240" : nil, isLoading: loadingGuildID == guild.id, diff --git a/Swiftcord/Views/Server/ServerButton.swift b/Swiftcord/Views/Server/ServerButton.swift index 8be5fb16..8844368a 100644 --- a/Swiftcord/Views/Server/ServerButton.swift +++ b/Swiftcord/Views/Server/ServerButton.swift @@ -6,10 +6,13 @@ // import SwiftUI +import DiscordKit +import DiscordKitCore import CachedAsyncImage struct ServerButton: View { let selected: Bool + var guild: Guild? let name: String var systemIconName: String? var assetIconName: String? @@ -35,6 +38,7 @@ struct ServerButton: View { .buttonStyle( ServerButtonStyle( selected: selected, + guild: guild, name: name, bgColor: bgColor, systemName: systemIconName, @@ -77,6 +81,7 @@ struct ServerButton: View { struct ServerButtonStyle: ButtonStyle { let selected: Bool + var guild: Guild? let name: String let bgColor: Color? let systemName: String? @@ -84,6 +89,8 @@ struct ServerButtonStyle: ButtonStyle { let serverIconURL: String? let loading: Bool @Binding var hovered: Bool + + @EnvironmentObject var gateway: DiscordGateway func makeBody(configuration: Configuration) -> some View { ZStack { @@ -138,18 +145,62 @@ struct ServerButtonStyle: ButtonStyle { .animation(.none, value: configuration.isPressed) .animation(.interpolatingSpring(stiffness: 500, damping: 30), value: hovered) .onHover { hover in hovered = hover } - } + .contextMenu { + if guild != nil { + Text(name) + + Divider() + + Button(action: readAll) { + Image(systemName: "message.badge") + Text("Mark as read") + } + + Divider() + + Group { + Button(action: copyLink) { + Image(systemName: "link") + Text("Copy Link") + } + Button(action: copyId) { + Image(systemName: "number.circle.fill") + Text("Copy ID") + } + } + } + } + } } -struct ServerButton_Previews: PreviewProvider { - static var previews: some View { - ServerButton( - selected: false, - name: "Hello world, discord!", - systemIconName: nil, - assetIconName: nil, - serverIconURL: nil, - bgColor: nil - ) {} - } +private extension ServerButtonStyle { + func readAll() { + if guild != nil { + for channel in guild?.channels ?? [] { + gateway.readState[channel.id] = gateway.readState[channel.id]?.updatingLastMessage(id: channel.last_message_id ?? "") + } + } + } + + func copyLink() { + if guild != nil { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString( + "https://canary.discord.com/channels/\(guild?.id ?? "@me")", + forType: .string + ) + } + } + + func copyId() { + if guild != nil { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString( + guild?.id ?? "", + forType: .string + ) + } + } } diff --git a/Swiftcord/Views/Server/ServerFolder.swift b/Swiftcord/Views/Server/ServerFolder.swift index 826bff3f..0919d954 100644 --- a/Swiftcord/Views/Server/ServerFolder.swift +++ b/Swiftcord/Views/Server/ServerFolder.swift @@ -89,6 +89,7 @@ struct ServerFolder: View { ForEach(folder.guilds, id: \.id) { [self] guild in ServerButton( selected: selectedGuildID == guild.id || loadingGuildID == guild.id, + guild: guild, name: guild.name, serverIconURL: guild.icon != nil ? "\(DiscordKitConfig.default.cdnURL)icons/\(guild.id)/\(guild.icon!).webp?size=240" : nil, isLoading: loadingGuildID == guild.id From 94ec77771be53f4f0a5296fd49e28fbce8c0b893 Mon Sep 17 00:00:00 2001 From: 12944qwerty Date: Thu, 28 Sep 2023 18:56:24 -0400 Subject: [PATCH 4/6] manual ack --- .../MessageRenderViews/MessageView.swift | 12 ++++++++++ Swiftcord/Views/Server/ChannelList.swift | 14 ++++++----- Swiftcord/Views/Server/ServerButton.swift | 24 ++++++++++--------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift b/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift index a452c7e7..f0370916 100644 --- a/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift +++ b/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift @@ -159,6 +159,12 @@ struct MessageView: View, Equatable { Text("Edit") } } + + Button(action: { Task { await readMessage() } }) { + Image(systemName: "message.badge") + Text("Mark as unread") + } + Button(role: .destructive, action: deleteMessage) { Image(systemName: "xmark.bin.fill") Text("Delete Message").foregroundColor(.red) @@ -209,6 +215,12 @@ private extension MessageView { func editMessage() { print(#function) } + + func readMessage() async { + do { + let _ = try await restAPI.ackMessageRead(id: message.channel_id, msgID: message.id, manual: true, mention_count: 0) + } catch {} + } func deleteMessage() { Task { diff --git a/Swiftcord/Views/Server/ChannelList.swift b/Swiftcord/Views/Server/ChannelList.swift index 2fb676e7..51211304 100644 --- a/Swiftcord/Views/Server/ChannelList.swift +++ b/Swiftcord/Views/Server/ChannelList.swift @@ -31,7 +31,7 @@ struct ChannelList: View, Equatable { }) .contextMenu { let isRead = gateway.readState[channel.id]?.id == channel.last_message_id - Button(action: { readChannel(channel) }) { + Button(action: { Task { await readChannel(channel) } }) { Image(systemName: isRead ? "message" : "message.badge") Text("Mark as read") }.disabled(isRead) @@ -98,7 +98,7 @@ struct ChannelList: View, Equatable { ForEach(channels, id: \.id) { channel in item(for: channel) } } .contextMenu { - Button(action: { readChannels(channels) }) { + Button(action: { Task { await readChannels(channels) } }) { Image(systemName: "message.badge") Text("Mark as read") } @@ -131,14 +131,16 @@ struct ChannelList: View, Equatable { } private extension ChannelList { - func readChannels(_ channels: [Channel]) { + func readChannels(_ channels: [Channel]) async { for channel in channels { - readChannel(channel) + await readChannel(channel) } } - func readChannel(_ channel: Channel) { - gateway.readState[channel.id] = gateway.readState[channel.id]?.updatingLastMessage(id: channel.last_message_id ?? "") + func readChannel(_ channel: Channel) async { + do { + let _ = try await restAPI.ackMessageRead(id: channel.id, msgID: channel.last_message_id ?? "", manual: true, mention_count: 0) + } catch {} } func copyLink(_ channel: Channel) { diff --git a/Swiftcord/Views/Server/ServerButton.swift b/Swiftcord/Views/Server/ServerButton.swift index 807cd468..71c21ec3 100644 --- a/Swiftcord/Views/Server/ServerButton.swift +++ b/Swiftcord/Views/Server/ServerButton.swift @@ -28,7 +28,7 @@ import CachedAsyncImage struct ServerButton: View { let selected: Bool - var guild: Guild? + var guild: PreloadedGuild? let name: String var systemIconName: String? var assetIconName: String? @@ -81,7 +81,7 @@ struct ServerButton: View { struct ServerButtonStyle: ButtonStyle { let selected: Bool - var guild: Guild? + var guild: PreloadedGuild? let name: String let bgColor: Color? let systemName: String? @@ -151,7 +151,7 @@ struct ServerButtonStyle: ButtonStyle { Divider() - Button(action: readAll) { + Button(action: { Task { await readAll() } }) { Image(systemName: "message.badge") Text("Mark as read") } @@ -174,31 +174,33 @@ struct ServerButtonStyle: ButtonStyle { } private extension ServerButtonStyle { - func readAll() { - if guild != nil { - for channel in guild?.channels ?? [] { - gateway.readState[channel.id] = gateway.readState[channel.id]?.updatingLastMessage(id: channel.last_message_id ?? "") + func readAll() async { + if let guild = guild { + for channel in guild.channels { + do { + let _ = try await restAPI.ackMessageRead(id: channel.id, msgID: channel.last_message_id ?? "", manual: true, mention_count: 0) + } catch {} } } } func copyLink() { - if guild != nil { + if let guild = guild { let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString( - "https://canary.discord.com/channels/\(guild?.id ?? "@me")", + "https://canary.discord.com/channels/\(guild.id)", forType: .string ) } } func copyId() { - if guild != nil { + if let guild = guild { let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString( - guild?.id ?? "", + guild.id, forType: .string ) } From 6eba528ad216a2725fe04f89826e832939416f57 Mon Sep 17 00:00:00 2001 From: 12944qwerty Date: Thu, 28 Sep 2023 19:08:59 -0400 Subject: [PATCH 5/6] read on send --- Swiftcord/Utils/Extensions/MessagesView+.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Swiftcord/Utils/Extensions/MessagesView+.swift b/Swiftcord/Utils/Extensions/MessagesView+.swift index a1ee5f37..4f602bb7 100644 --- a/Swiftcord/Utils/Extensions/MessagesView+.swift +++ b/Swiftcord/Utils/Extensions/MessagesView+.swift @@ -74,7 +74,7 @@ internal extension MessagesView { Task { do { - _ = try await restAPI.createChannelMsg( + let newMessage = try await restAPI.createChannelMsg( message: NewMessage( content: message, allowed_mentions: allowedMentions, @@ -90,6 +90,8 @@ internal extension MessagesView { attachments: attachments, id: ctx.channel!.id ) + + _ = try await restAPI.ackMessageRead(id: newMessage.channel_id, msgID: newMessage.id) } catch { viewModel.showingInfoBar = true viewModel.infoBarData = InfoBarData( From dcd95199afcdd9537915bbdecae0b322ab5d0b38 Mon Sep 17 00:00:00 2001 From: 12944qwerty Date: Fri, 29 Sep 2023 09:46:14 -0400 Subject: [PATCH 6/6] manual mark msg as unread --- .../Message/MessageRenderViews/MessageView.swift | 11 ++++++++++- Swiftcord/Views/Message/MessagesView.swift | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift b/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift index f0370916..fc3f7591 100644 --- a/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift +++ b/Swiftcord/Views/Message/MessageRenderViews/MessageView.swift @@ -41,6 +41,7 @@ struct MessageView: View, Equatable { } let message: Message + let prevMessage: Message? let shrunk: Bool let quotedMsg: Message? let onQuoteClick: (Snowflake) -> Void @@ -218,7 +219,15 @@ private extension MessageView { func readMessage() async { do { - let _ = try await restAPI.ackMessageRead(id: message.channel_id, msgID: message.id, manual: true, mention_count: 0) + let id = Int(floor((message.id as NSString).doubleValue / pow(2, 22)) - 1) + var defaultId: Snowflake + if id < 0 { + defaultId = "0" + } else { + defaultId = String(id << 22) + } + + let _ = try await restAPI.ackMessageRead(id: message.channel_id, msgID: prevMessage?.id ?? defaultId, manual: true, mention_count: 0) } catch {} } diff --git a/Swiftcord/Views/Message/MessagesView.swift b/Swiftcord/Views/Message/MessagesView.swift index 13c2b7ad..c4c07679 100644 --- a/Swiftcord/Views/Message/MessagesView.swift +++ b/Swiftcord/Views/Message/MessagesView.swift @@ -159,6 +159,7 @@ struct MessagesView: View { func cell(for msg: Message, shrunk: Bool) -> some View { MessageView( message: msg, + prevMessage: viewModel.messages.after(msg), shrunk: shrunk, quotedMsg: msg.message_reference != nil ? viewModel.messages.first {