Files
letro-ios/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift
2025-06-10 15:09:20 +03:00

193 lines
5.6 KiB
Swift

//
// Copyright 2024 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import OrderedCollections
import SFSafeSymbols
import SwiftUI
@MainActor
struct TimelineItemMenuActions {
let reactions: [TimelineItemMenuReaction]
let actions: [TimelineItemMenuAction]
let secondaryActions: [TimelineItemMenuAction]
init?(isReactable: Bool,
actions: [TimelineItemMenuAction],
secondaryActions: [TimelineItemMenuAction],
emojiProvider: EmojiProviderProtocol) {
if !isReactable, actions.isEmpty, secondaryActions.isEmpty {
return nil
}
self.actions = actions
self.secondaryActions = secondaryActions
var frequentlyUsed: OrderedSet<TimelineItemMenuReaction> = [
.init(key: "👍️", symbol: .handThumbsup),
.init(key: "👎️", symbol: .handThumbsdown),
.init(key: "🎉", symbol: .partyPopper),
.init(key: "❤️", symbol: .heart)
]
frequentlyUsed.append(contentsOf: emojiProvider.frequentlyUsedSystemEmojis().map { TimelineItemMenuReaction(key: $0, symbol: .heart) })
reactions = if isReactable {
Array(frequentlyUsed.elements.prefix(10))
} else {
[]
}
}
}
struct TimelineItemMenuReaction: Hashable {
let key: String
let symbol: SFSymbol
// Frequently used emojis on the all use the same .heart SFSymbol.
// Override equatable so we can remove duplicates.
static func == (lhs: TimelineItemMenuReaction, rhs: TimelineItemMenuReaction) -> Bool {
lhs.key == rhs.key
}
}
enum TimelineItemMenuAction: Identifiable, Hashable {
case copy
case copyCaption
case edit
case addCaption
case editCaption
case removeCaption
case editPoll
case copyPermalink
case redact
case reply(isThread: Bool)
case replyInThread
case forward(itemID: TimelineItemIdentifier)
case viewSource
case report
case react
case toggleReaction(key: String)
case endPoll(pollStartID: String)
case pin
case unpin
case viewInRoomTimeline
case share
case save
var id: Self { self }
/// Whether the item should cancel a reply/edit occurring in the composer.
var switchToDefaultComposer: Bool {
switch self {
case .reply, .edit, .addCaption, .editCaption, .editPoll:
false
default:
true
}
}
/// Whether the action should be shown for an item that failed to send.
var canAppearInFailedEcho: Bool {
switch self {
case .copy, .edit, .redact, .viewSource, .editPoll:
true
default:
false
}
}
/// Whether the action should be shown for a redacted item.
var canAppearInRedacted: Bool {
switch self {
case .viewSource, .unpin, .viewInRoomTimeline:
true
default:
false
}
}
/// Whether or not the action is destructive.
var isDestructive: Bool {
switch self {
case .redact, .report, .removeCaption:
true
default:
false
}
}
var canAppearInPinnedEventsTimeline: Bool {
switch self {
case .viewInRoomTimeline, .pin, .unpin, .forward:
true
default:
false
}
}
var canAppearInMediaDetails: Bool {
switch self {
case .viewInRoomTimeline, .share, .save, .redact:
true
default:
false
}
}
/// The action's label.
@ViewBuilder
var label: some View {
switch self {
case .copy:
Label(L10n.actionCopyText, icon: \.copy)
case .copyCaption:
Label(L10n.actionCopyCaption, icon: \.copy)
case .edit:
Label(L10n.actionEdit, icon: \.edit)
case .addCaption:
Label(L10n.actionAddCaption, icon: \.edit)
case .editCaption:
Label(L10n.actionEditCaption, icon: \.edit)
case .removeCaption:
Label(L10n.actionRemoveCaption, icon: \.close)
case .editPoll:
Label(L10n.actionEditPoll, icon: \.edit)
case .copyPermalink:
Label(L10n.actionCopyLinkToMessage, icon: \.link)
case .reply(let isThread):
Label(isThread ? L10n.actionReplyInThread : L10n.actionReply, icon: \.reply)
case .replyInThread:
Label(L10n.actionReplyInThread, icon: \.threads)
case .forward:
Label(L10n.actionForward, icon: \.forward)
case .redact:
Label(L10n.actionRemoveMessage, icon: \.delete)
case .viewSource:
Label(L10n.actionViewSource, icon: \.code)
case .report:
Label(L10n.actionReportContent, icon: \.chatProblem)
case .react:
Label(L10n.actionReact, icon: \.reactionAdd)
case .toggleReaction:
// Unused label - manually created in TimelineItemMacContextMenu.
Label(L10n.actionReact, icon: \.reactionAdd)
case .endPoll:
Label(L10n.actionEndPoll, icon: \.pollsEnd)
case .pin:
Label(L10n.actionPin, icon: \.pin)
case .unpin:
Label(L10n.actionUnpin, icon: \.unpin)
case .viewInRoomTimeline:
Label(L10n.actionViewInTimeline, icon: \.visibilityOn)
case .share:
Label(L10n.actionShare, icon: \.shareIos)
case .save:
Label(L10n.actionSave, icon: \.downloadIos)
}
}
}