Add "Translate" to TimelineItemMenuActions (#4846)

* Add "Translate" to TimelineItemMenuActions

* Update TimelineView.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Update strings using `download-strings`

* Update ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>

* Clear `textToBeTranslated` after translation was dismissed

* `swift run tools download-strings --all-languages`

---------

Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
# Conflicts:
#	ElementX/Resources/Localizations/et.lproj/Localizable.strings
#	ElementX/Resources/Localizations/fr.lproj/Localizable.strings
#	ElementX/Resources/Localizations/hr.lproj/Localizable.strings
This commit is contained in:
Lukas
2026-01-05 14:03:53 +01:00
committed by Doug
parent 616fd09b34
commit 83594b4d2f
6 changed files with 27 additions and 0 deletions

View File

@@ -26,6 +26,7 @@ enum TimelineInteractionHandlerAction {
case viewInRoomTimeline(eventID: String)
case displayThread(itemID: TimelineItemIdentifier)
case showTranslation(text: String)
}
/// The interaction handler groups logic for dealing with various actions the user can take on a timeline's
@@ -198,6 +199,9 @@ class TimelineInteractionHandler {
break // Handled inline in the media preview screen with a ShareLink.
case .save:
break // Handled inline in the media preview screen.
case .translate:
guard let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol else { return }
actionsSubject.send(.showTranslation(text: messageTimelineItem.body))
}
if action.switchToDefaultComposer {

View File

@@ -160,6 +160,9 @@ struct TimelineViewStateBindings {
var readReceiptsSummaryInfo: ReadReceiptSummaryInfo?
var manageMemberViewModel: ManageRoomMemberSheetViewModel?
var showTranslation = false
var textToBeTranslated: String?
}
struct TimelineItemActionMenuInfo: Equatable, Identifiable {

View File

@@ -498,6 +498,9 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
Task { await self.viewInRoomTimeline(eventID: eventID) }
case .displayThread(let itemID):
actionsSubject.send(.displayThread(itemID: itemID))
case .showTranslation(let text):
self.state.bindings.textToBeTranslated = text
self.state.bindings.showTranslation = true
}
}
.store(in: &cancellables)

View File

@@ -57,6 +57,7 @@ struct TimelineItemMenuReaction: Hashable {
enum TimelineItemMenuAction: Identifiable, Hashable {
case copy
case translate
case copyCaption
case edit
case addCaption
@@ -145,6 +146,13 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
switch self {
case .copy:
Label(L10n.actionCopyText, icon: \.copy)
case .translate:
Label { Text(L10n.actionTranslate) } icon: {
Image(systemSymbol: .translate)
.resizable()
.aspectRatio(contentMode: .fit)
.scaledFrame(size: 24)
}
case .copyCaption:
Label(L10n.actionCopyCaption, icon: \.copy)
case .edit:

View File

@@ -91,6 +91,7 @@ struct TimelineItemMenuActionProvider {
if item.isCopyable {
actions.append(.copy)
actions.append(.translate)
} else if item.hasMediaCaption {
actions.append(.copyCaption)
}

View File

@@ -7,6 +7,7 @@
//
import SwiftUI
import Translation
import WysiwygComposer
struct TimelineView: View {
@@ -55,6 +56,13 @@ struct TimelineView: View {
ReadReceiptsSummaryView(orderedReadReceipts: $0.orderedReceipts)
.environmentObject(timelineContext)
}
.translationPresentation(isPresented: $timelineContext.showTranslation, text: timelineContext.textToBeTranslated ?? "")
.onChange(of: timelineContext.showTranslation) { oldValue, newValue in
if oldValue, !newValue {
// clear texts after translation was dismissed
timelineContext.textToBeTranslated = nil
}
}
.onDrop(of: ["public.item", "public.file-url"], isTargeted: $dragOver) { providers -> Bool in
let supportedProviders = providers.filter(\.isSupportedForPasteOrDrop)