Add reaction toggles to macOS timeline context menu. (#2801)

Highlight reactions that have already been sent.

Share the default reactions.
This commit is contained in:
Doug
2024-05-07 16:50:54 +01:00
committed by GitHub
parent ef6315d329
commit 37001cb711
3 changed files with 62 additions and 7 deletions
@@ -257,6 +257,9 @@ class RoomScreenInteractionHandler {
actionsSubject.send(.displayReportContent(itemID: itemID, senderID: eventTimelineItem.sender.id)) actionsSubject.send(.displayReportContent(itemID: itemID, senderID: eventTimelineItem.sender.id))
case .react: case .react:
showEmojiPicker(for: itemID) showEmojiPicker(for: itemID)
case .toggleReaction(let key):
guard let eventID = itemID.eventID else { return }
Task { await roomProxy.timeline.toggleReaction(key, to: eventID) }
case .endPoll(let pollStartID): case .endPoll(let pollStartID):
endPoll(pollStartID: pollStartID) endPoll(pollStartID: pollStartID)
} }
@@ -14,6 +14,8 @@
// limitations under the License. // limitations under the License.
// //
import Compound
import SFSafeSymbols
import SwiftUI import SwiftUI
/// The contents of the context menu shown when right clicking an item in the timeline on a Mac /// The contents of the context menu shown when right clicking an item in the timeline on a Mac
@@ -27,10 +29,26 @@ struct TimelineItemMacContextMenu: View {
if let menuActions = actionProvider?(item.id) { if let menuActions = actionProvider?(item.id) {
Section { Section {
if item.isReactable { if item.isReactable {
if #available(iOS 17.0, *) {
let reactions = (item as? EventBasedTimelineItemProtocol)?.properties.reactions ?? []
ControlGroup {
ForEach(menuActions.reactions, id: \.key) {
ReactionToggle(reaction: $0, reactions: reactions) {
send(.toggleReaction(key: $0))
}
}
Button { send(.react) } label: {
CompoundIcon(\.reactionAdd)
}
}
.controlGroupStyle(.palette)
} else {
Button { send(.react) } label: { Button { send(.react) } label: {
TimelineItemMenuAction.react.label TimelineItemMenuAction.react.label
} }
} }
}
ForEach(menuActions.actions) { action in ForEach(menuActions.actions) { action in
Button(role: action.isDestructive ? .destructive : nil) { Button(role: action.isDestructive ? .destructive : nil) {
@@ -54,3 +72,21 @@ struct TimelineItemMacContextMenu: View {
} }
} }
} }
/// A button that acts as a toggle for reacting to a message.
private struct ReactionToggle: View {
let reaction: TimelineItemMenuReaction
let reactions: [AggregatedReaction]
let action: (String) -> Void
var isOn: Bool {
reactions.contains { $0.key == reaction.key && $0.isHighlighted }
}
var body: some View {
Button { action(reaction.key) } label: {
Image(systemSymbol: reaction.symbol)
.symbolVariant(isOn ? .fill : .none)
}
}
}
@@ -15,9 +15,11 @@
// //
import Compound import Compound
import SFSafeSymbols
import SwiftUI import SwiftUI
struct TimelineItemMenuActions { struct TimelineItemMenuActions {
let reactions: [TimelineItemMenuReaction]
let actions: [TimelineItemMenuAction] let actions: [TimelineItemMenuAction]
let debugActions: [TimelineItemMenuAction] let debugActions: [TimelineItemMenuAction]
@@ -28,6 +30,13 @@ struct TimelineItemMenuActions {
self.actions = actions self.actions = actions
self.debugActions = debugActions self.debugActions = debugActions
reactions = [
.init(key: "👍️", symbol: .handThumbsup),
.init(key: "👎️", symbol: .handThumbsdown),
.init(key: "🔥", symbol: .flame),
.init(key: "❤️", symbol: .heart),
.init(key: "👏", symbol: .handsClap)
]
} }
var canReply: Bool { var canReply: Bool {
@@ -41,6 +50,11 @@ struct TimelineItemMenuActions {
} }
} }
struct TimelineItemMenuReaction {
let key: String
let symbol: SFSymbol
}
enum TimelineItemMenuAction: Identifiable, Hashable { enum TimelineItemMenuAction: Identifiable, Hashable {
case copy case copy
case edit case edit
@@ -52,6 +66,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
case retryDecryption(sessionID: String) case retryDecryption(sessionID: String)
case report case report
case react case react
case toggleReaction(key: String)
case endPoll(pollStartID: String) case endPoll(pollStartID: String)
var id: Self { self } var id: Self { self }
@@ -120,6 +135,9 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
Label(L10n.actionReportContent, icon: \.chatProblem) Label(L10n.actionReportContent, icon: \.chatProblem)
case .react: case .react:
Label(L10n.actionReact, icon: \.reactionAdd) Label(L10n.actionReact, icon: \.reactionAdd)
case .toggleReaction:
// Unused label - manually created in TimelineItemMacContextMenu.
Label(L10n.actionReact, icon: \.reactionAdd)
case .endPoll: case .endPoll:
Label(L10n.actionEndPoll, icon: \.pollsEnd) Label(L10n.actionEndPoll, icon: \.pollsEnd)
} }
@@ -216,11 +234,9 @@ struct TimelineItemMenu: View {
private var reactionsSection: some View { private var reactionsSection: some View {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack(alignment: .center, spacing: 8) { HStack(alignment: .center, spacing: 8) {
reactionButton(for: "👍️") ForEach(actions.reactions, id: \.key) {
reactionButton(for: "👎️") reactionButton(for: $0.key)
reactionButton(for: "🔥") }
reactionButton(for: "❤️")
reactionButton(for: "👏")
Button { Button {
dismiss() dismiss()