diff --git a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift index 7e04ec696..6e6c70090 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineInteractionHandler.swift @@ -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 { diff --git a/ElementX/Sources/Screens/Timeline/TimelineModels.swift b/ElementX/Sources/Screens/Timeline/TimelineModels.swift index 024cb5ced..4177d5cd4 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineModels.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineModels.swift @@ -160,6 +160,9 @@ struct TimelineViewStateBindings { var readReceiptsSummaryInfo: ReadReceiptSummaryInfo? var manageMemberViewModel: ManageRoomMemberSheetViewModel? + + var showTranslation = false + var textToBeTranslated: String? } struct TimelineItemActionMenuInfo: Equatable, Identifiable { diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index 2a469fc89..be5b9356b 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -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) diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift index 01e0f0210..9fd2a5abe 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuAction.swift @@ -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: diff --git a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift index 1569c4c28..61895b6fe 100644 --- a/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift +++ b/ElementX/Sources/Screens/Timeline/View/ItemMenu/TimelineItemMenuActionProvider.swift @@ -91,6 +91,7 @@ struct TimelineItemMenuActionProvider { if item.isCopyable { actions.append(.copy) + actions.append(.translate) } else if item.hasMediaCaption { actions.append(.copyCaption) } diff --git a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift index 261bbd0ea..aadeef6dc 100644 --- a/ElementX/Sources/Screens/Timeline/View/TimelineView.swift +++ b/ElementX/Sources/Screens/Timeline/View/TimelineView.swift @@ -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)