diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift index da5e97820..d304816d0 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Status.swift @@ -151,7 +151,7 @@ extension DataSourceFacade { struct MenuContext { let author: ManagedObjectRecord? - let status: ManagedObjectRecord? + let statusViewModel: StatusView.ViewModel? let button: UIButton? let barButtonItem: UIBarButtonItem? } @@ -266,7 +266,7 @@ extension DataSourceFacade { context: dependency.context, authContext: dependency.authContext, user: user, - status: menuContext.status + status: menuContext.statusViewModel?.originalStatus?.asRecord ) _ = dependency.coordinator.present( @@ -297,7 +297,7 @@ extension DataSourceFacade { ) case .bookmarkStatus: Task { - guard let status = menuContext.status else { + guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { assertionFailure() return } @@ -310,7 +310,7 @@ extension DataSourceFacade { Task { let managedObjectContext = dependency.context.managedObjectContext guard let status: ManagedObjectRecord = try? await managedObjectContext.perform(block: { - guard let object = menuContext.status?.object(in: managedObjectContext) else { return nil } + guard let object = menuContext.statusViewModel?.originalStatus?.asRecord.object(in: managedObjectContext) else { return nil } let objectID = (object.reblog ?? object).objectID return .init(objectID: objectID) }) else { @@ -344,7 +344,7 @@ extension DataSourceFacade { style: .destructive ) { [weak dependency] _ in guard let dependency = dependency else { return } - guard let status = menuContext.status else { return } + guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { return } Task { try await DataSourceFacade.responseToDeleteStatus( dependency: dependency, @@ -358,12 +358,12 @@ extension DataSourceFacade { dependency.present(alertController, animated: true) case .translateStatus: - guard let status = menuContext.status else { return } + guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { return } + do { - try await DataSourceFacade.translateStatus( - provider: dependency, - status: status - ) + let translation = try await DataSourceFacade.translateStatus(provider: dependency,status: status) + + menuContext.statusViewModel?.translation = translation } catch TranslationFailure.emptyOrInvalidResponse { let alertController = UIAlertController(title: L10n.Common.Alerts.TranslationFailed.title, message: L10n.Common.Alerts.TranslationFailed.message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: L10n.Common.Alerts.TranslationFailed.button, style: .default)) @@ -371,7 +371,7 @@ extension DataSourceFacade { } case .editStatus: - guard let status = menuContext.status?.object(in: dependency.context.managedObjectContext) else { return } + guard let status = menuContext.statusViewModel?.originalStatus?.asRecord.object(in: dependency.context.managedObjectContext) else { return } let statusSource = try await dependency.context.apiService.getStatusSource( forStatusID: status.id, diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift index 8ce9c2447..912540f69 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Translate.swift @@ -9,6 +9,7 @@ import UIKit import CoreData import CoreDataStack import MastodonCore +import MastodonSDK typealias Provider = UIViewController & NeedsDependency & AuthContextProvider @@ -20,26 +21,26 @@ extension DataSourceFacade { public static func translateStatus( provider: Provider, status: ManagedObjectRecord - ) async throws { + ) async throws -> Mastodon.Entity.Translation? { let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() await selectionFeedbackGenerator.selectionChanged() guard let status = status.object(in: provider.context.managedObjectContext) else { - return + return nil } if let reblog = status.reblog { - try await translateAndApply(provider: provider, status: reblog) + return try await translateStatus(provider: provider, status: reblog) } else { - try await translateAndApply(provider: provider, status: status) + return try await translateStatus(provider: provider, status: status) } } } private extension DataSourceFacade { - static func translateStatus(provider: Provider, status: Status) async throws -> Status.TranslatedContent? { + static func translateStatus(provider: Provider, status: Status) async throws -> Mastodon.Entity.Translation? { do { let value = try await provider.context .apiService @@ -49,22 +50,12 @@ private extension DataSourceFacade { ).value guard let content = value.content else { - throw TranslationFailure.emptyOrInvalidResponse + return nil } - return Status.TranslatedContent(content: content, provider: value.provider) + return value } catch { throw TranslationFailure.emptyOrInvalidResponse } } - - static func translateAndApply(provider: Provider, status: Status) async throws { - do { - let translated = try await translateStatus(provider: provider, status: status) - status.update(translatedContent: translated) - } catch { - status.update(translatedContent: nil) - throw TranslationFailure.emptyOrInvalidResponse - } - } } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift index 019f780bb..0974510bf 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+NotificationTableViewCellDelegate.swift @@ -44,7 +44,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut action: action, menuContext: .init( author: author, - status: nil, + statusViewModel: nil, button: button, barButtonItem: nil ) diff --git a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift index 85718c94a..dafd65fdf 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider+StatusTableViewCellDelegate.swift @@ -498,13 +498,23 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte } } } - + + let statusViewModel: StatusView.ViewModel? + + if let cell = cell as? StatusTableViewCell { + statusViewModel = await cell.statusView.viewModel + } else if let cell = cell as? StatusThreadRootTableViewCell { + statusViewModel = await cell.statusView.viewModel + } else { + statusViewModel = nil + } + try await DataSourceFacade.responseToMenuAction( dependency: self, action: action, menuContext: .init( author: author, - status: status, + statusViewModel: statusViewModel, button: button, barButtonItem: nil ) diff --git a/Mastodon/Scene/Profile/ProfileViewController.swift b/Mastodon/Scene/Profile/ProfileViewController.swift index c83401329..1edb7e5f7 100644 --- a/Mastodon/Scene/Profile/ProfileViewController.swift +++ b/Mastodon/Scene/Profile/ProfileViewController.swift @@ -894,7 +894,7 @@ extension ProfileViewController: MastodonMenuDelegate { action: action, menuContext: DataSourceFacade.MenuContext( author: userRecord, - status: nil, + statusViewModel: nil, button: nil, barButtonItem: self.moreMenuBarButtonItem ) diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift index f0b86dd54..1df4d714b 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusTableViewCell.swift @@ -90,8 +90,7 @@ extension StatusTableViewCell { } .store(in: &_disposeBag) - statusView.viewModel - .$translatedFromLanguage + statusView.viewModel.$translation .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] _ in self?.invalidateIntrinsicContentSize() diff --git a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift index 54a38308b..b2361ffc5 100644 --- a/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift +++ b/Mastodon/Scene/Share/View/TableviewCell/StatusThreadRootTableViewCell.swift @@ -76,8 +76,7 @@ extension StatusThreadRootTableViewCell { statusView.contentMetaText.textView.isAccessibilityElement = true statusView.contentMetaText.textView.isSelectable = true - statusView.viewModel - .$translatedFromLanguage + statusView.viewModel.$translation .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] _ in self?.invalidateIntrinsicContentSize() diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift index b9fcf3a3c..736d4b7eb 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift @@ -10,17 +10,7 @@ import Foundation public final class Status: NSManagedObject { public typealias ID = String - - public class TranslatedContent: NSObject { - public let content: String - public let provider: String? - - public init(content: String, provider: String?) { - self.content = content - self.provider = provider - } - } - + // sourcery: autoGenerateProperty @NSManaged public private(set) var identifier: ID // sourcery: autoGenerateProperty @@ -118,9 +108,6 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var deletedAt: Date? // sourcery: autoUpdatableObject @NSManaged public private(set) var revealedAt: Date? - - // sourcery: autoUpdatableObject - @NSManaged public private(set) var translatedContent: TranslatedContent? } extension Status { @@ -535,11 +522,6 @@ extension Status: AutoUpdatableObject { self.revealedAt = revealedAt } } - public func update(translatedContent: TranslatedContent?) { - if self.translatedContent != translatedContent { - self.translatedContent = translatedContent - } - } public func update(attachments: [MastodonAttachment]) { if self.attachments != attachments { self.attachments = attachments diff --git a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift index eebb16be4..006c9881f 100644 --- a/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift +++ b/MastodonSDK/Sources/MastodonCore/Extension/CoreDataStack/Instance.swift @@ -49,7 +49,7 @@ extension Instance { version?.majorServerVersion(greaterThanOrEquals: 4) ?? false // following Tags is support beginning with Mastodon v4.0.0 } - var isTranslationEnabled: Bool { + public var isTranslationEnabled: Bool { if let configuration = configurationV2 { return configuration.translation?.enabled == true } diff --git a/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift b/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift index a9880472a..0b1a9db49 100644 --- a/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift +++ b/MastodonSDK/Sources/MastodonCore/MastodonAuthentication.swift @@ -84,12 +84,14 @@ public struct MastodonAuthentication: Codable, Hashable { } public func instance(in context: NSManagedObjectContext) -> Instance? { - guard - let instanceObjectIdURI = instanceObjectIdURI, - let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI) - else { return nil } - - return try? context.existingObject(with: objectID) as? Instance + guard let instanceObjectIdURI, + let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI) + else { + return nil + } + + let instance = try? context.existingObject(with: objectID) as? Instance + return instance } public func user(in context: NSManagedObjectContext) -> MastodonUser? { diff --git a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift index 483f52431..3289b9b81 100644 --- a/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift +++ b/MastodonSDK/Sources/MastodonCore/Service/API/APIService+Status+Translate.swift @@ -19,12 +19,14 @@ extension APIService { ) async throws -> Mastodon.Response.Content { let domain = authenticationBox.domain let authorization = authenticationBox.userAuthorization + let targetLanguage = Bundle.main.preferredLocalizations.first let response = try await Mastodon.API.Statuses.translate( session: session, domain: domain, statusID: statusID, - authorization: authorization + authorization: authorization, + targetLanguage: targetLanguage ).singleOutput() return response diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift index c3e790b67..b9d947ea7 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Statuses+Translate.swift @@ -16,7 +16,11 @@ extension Mastodon.API.Statuses { .appendingPathComponent(statusID) .appendingPathComponent("translate") } - + + public struct TranslateQuery: Codable, PostQuery { + public let lang: String + } + /// Translate Status /// /// Translate a given Status. @@ -31,11 +35,21 @@ extension Mastodon.API.Statuses { session: URLSession, domain: String, statusID: Mastodon.Entity.Status.ID, - authorization: Mastodon.API.OAuth.Authorization? - ) -> AnyPublisher, Error> { + authorization: Mastodon.API.OAuth.Authorization?, + targetLanguage: String? + ) -> AnyPublisher, Error> { + + let query: TranslateQuery? + + if let targetLanguage { + query = TranslateQuery(lang: targetLanguage) + } else { + query = nil + } + let request = Mastodon.API.post( url: translateEndpointURL(domain: domain, statusID: statusID), - query: nil, + query: query, authorization: authorization ) return session.dataTaskPublisher(for: request) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift index 5879eed4f..b51011e54 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/NotificationView+ViewModel.swift @@ -221,38 +221,27 @@ extension NotificationView.ViewModel { ) ) .sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslatedIsFollowed in - guard let name = authorName?.string else { + guard let name = authorName?.string, let self, let context = self.context, let authContext = self.authContext else { notificationView.menuButton.menu = nil return } - - let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed - - lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = { - guard - let self = self, - let context = self.context, - let authContext = self.authContext - else { return nil } - - var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil - context.managedObjectContext.performAndWait { - let authentication = authContext.mastodonAuthenticationBox.authentication - configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2 - } - return configuration - }() - - let menuContext = NotificationView.AuthorMenuContext( + + let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed + + let authentication = authContext.mastodonAuthenticationBox.authentication + let instance = authentication.instance(in: context.managedObjectContext) + let isTranslationEnabled = instance?.isTranslationEnabled ?? false + + let menuContext = NotificationView.AuthorMenuContext( name: name, isMuting: isMuting, isBlocking: isBlocking, isMyself: isMyself, isBookmarking: false, // no bookmark action display for notification item isFollowed: isFollowed, - isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, + isTranslationEnabled: isTranslationEnabled, isTranslated: isTranslated, - statusLanguage: "" + statusLanguage: nil ) let (menu, actions) = notificationView.setupAuthorMenu(menuContext: menuContext) notificationView.menuButton.menu = menu diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift index 4e9c8cbf9..db34f081c 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusAuthorView.swift @@ -179,7 +179,10 @@ extension StatusAuthorView { postActions.append(.editStatus) } - if let statusLanguage = menuContext.statusLanguage, menuContext.isTranslationEnabled { + if menuContext.isTranslationEnabled, + let statusLanguage = menuContext.statusLanguage, + let deviceLanguage = Bundle.main.preferredLocalizations.first, + deviceLanguage != statusLanguage { if menuContext.isTranslated == false { postActions.append(.translateStatus(.init(language: statusLanguage))) } else { diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift index 653854bf4..fcb468d0d 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+Configuration.swift @@ -85,13 +85,10 @@ extension StatusView { configureToolbar(status: status) configureFilter(status: status) viewModel.originalStatus = status - [ - status.publisher(for: \.translatedContent), - status.reblog?.publisher(for: \.translatedContent) - ].compactMap { $0 } - .last? + + viewModel.$translation .receive(on: DispatchQueue.main) - .sink { [weak self] _ in + .sink { [weak self] translation in self?.configureTranslated(status: status) } .store(in: &disposeBag) @@ -293,36 +290,22 @@ extension StatusView { public func revertTranslation() { guard let originalStatus = viewModel.originalStatus else { return } - viewModel.translatedFromLanguage = nil - viewModel.translatedUsingProvider = nil - originalStatus.reblog?.update(translatedContent: nil) - originalStatus.update(translatedContent: nil) + viewModel.translation = nil configure(status: originalStatus) } func configureTranslated(status: Status) { - let translatedContent: Status.TranslatedContent? = { - if let translatedContent = status.reblog?.translatedContent { - return translatedContent - } - return status.translatedContent - - }() - - guard - let translatedContent = translatedContent - else { + guard let translation = viewModel.translation, + let translatedContent = translation.content else { viewModel.isCurrentlyTranslating = false return } // content do { - let content = MastodonContent(content: translatedContent.content, emojis: status.emojis.asDictionary) + let content = MastodonContent(content: translatedContent, emojis: status.emojis.asDictionary) let metaContent = try MastodonMetaContent.convert(document: content) viewModel.content = metaContent - viewModel.translatedFromLanguage = status.reblog?.language ?? status.language - viewModel.translatedUsingProvider = status.reblog?.translatedContent?.provider ?? status.translatedContent?.provider viewModel.isCurrentlyTranslating = false } catch { assertionFailure(error.localizedDescription) @@ -342,7 +325,6 @@ extension StatusView { let content = MastodonContent(content: statusEdit.content, emojis: statusEdit.emojis.asDictionary) let metaContent = try MastodonMetaContent.convert(document: content) viewModel.content = metaContent - viewModel.translatedFromLanguage = nil viewModel.isCurrentlyTranslating = false } catch { assertionFailure(error.localizedDescription) @@ -351,7 +333,7 @@ extension StatusView { } private func configureContent(status: Status) { - guard status.translatedContent == nil else { + guard viewModel.translation == nil else { return configureTranslated(status: status) } @@ -377,7 +359,6 @@ extension StatusView { let content = MastodonContent(content: status.content, emojis: status.emojis.asDictionary) let metaContent = try MastodonMetaContent.convert(document: content) viewModel.content = metaContent - viewModel.translatedFromLanguage = nil viewModel.isCurrentlyTranslating = false } catch { assertionFailure(error.localizedDescription) diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift index ab8b449d4..86948b5b5 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView+ViewModel.swift @@ -46,8 +46,7 @@ extension StatusView { // Translation @Published public var isCurrentlyTranslating = false - @Published public var translatedFromLanguage: String? - @Published public var translatedUsingProvider: String? + @Published public var translation: Mastodon.Entity.Translation? = nil @Published public var timestamp: Date? public var timestampFormatter: ((_ date: Date, _ isEdited: Bool) -> String)? @@ -148,10 +147,9 @@ extension StatusView { isContentSensitive = false isMediaSensitive = false isSensitiveToggled = false - translatedFromLanguage = nil - translatedUsingProvider = nil isCurrentlyTranslating = false - + translation = nil + activeFilters = [] filterContext = nil } @@ -657,60 +655,49 @@ extension StatusView.ViewModel { $isFollowed ) let publishersThree = Publishers.CombineLatest( - $translatedFromLanguage, + $translation, $language ) - + Publishers.CombineLatest3( publisherOne.eraseToAnyPublisher(), publishersTwo.eraseToAnyPublisher(), publishersThree.eraseToAnyPublisher() ).eraseToAnyPublisher() - .sink { tupleOne, tupleTwo, tupleThree in - let (authorName, isMyself) = tupleOne - let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo - let (translatedFromLanguage, language) = tupleThree - - guard let name = authorName?.string else { - statusView.authorView.menuButton.menu = nil - return - } - - lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = { - guard - let context = self.context, - let authContext = self.authContext - else { - return nil + .sink { tupleOne, tupleTwo, tupleThree in + let (authorName, isMyself) = tupleOne + let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo + let (translatedFromLanguage, language) = tupleThree + + guard let name = authorName?.string, let context = self.context, let authContext = self.authContext else { + statusView.authorView.menuButton.menu = nil + return } + + let authentication = authContext.mastodonAuthenticationBox.authentication + let instance = authentication.instance(in: context.managedObjectContext) + let isTranslationEnabled = instance?.isTranslationEnabled ?? false + + let menuContext = StatusAuthorView.AuthorMenuContext( + name: name, + isMuting: isMuting, + isBlocking: isBlocking, + isMyself: isMyself, + isBookmarking: isBookmark, + isFollowed: isFollowed, + isTranslationEnabled: isTranslationEnabled, + isTranslated: translatedFromLanguage != nil, + statusLanguage: language + ) - var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil - context.managedObjectContext.performAndWait { - let authentication = authContext.mastodonAuthenticationBox.authentication - configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2 - } - return configuration - }() - - let menuContext = StatusAuthorView.AuthorMenuContext( - name: name, - isMuting: isMuting, - isBlocking: isBlocking, - isMyself: isMyself, - isBookmarking: isBookmark, - isFollowed: isFollowed, - isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, - isTranslated: translatedFromLanguage != nil, - statusLanguage: language - ) - let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext) - authorView.menuButton.menu = menu - authorView.authorActions = actions - authorView.menuButton.showsMenuAsPrimaryAction = true - } - .store(in: &disposeBag) + let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext) + authorView.menuButton.menu = menu + authorView.authorActions = actions + authorView.menuButton.showsMenuAsPrimaryAction = true + } + .store(in: &disposeBag) } - + private func bindFilter(statusView: StatusView) { $isFiltered .sink { isFiltered in @@ -877,15 +864,20 @@ extension StatusView.ViewModel { .assign(to: \.toolbarActions, on: statusView) .store(in: &disposeBag) - let translatedFromLabel = Publishers.CombineLatest($translatedFromLanguage, $translatedUsingProvider) - .map { (language, provider) -> String? in - if let language { - return L10n.Common.Controls.Status.Translation.translatedFrom( - Locale.current.localizedString(forIdentifier: language) ?? L10n.Common.Controls.Status.Translation.unknownLanguage, - provider ?? L10n.Common.Controls.Status.Translation.unknownProvider - ) + let translatedFromLabel = $translation + .map { translation -> String? in + guard let translation else { return nil } + + let provider = translation.provider ?? L10n.Common.Controls.Status.Translation.unknownProvider + let sourceLanguage: String + + if let language = translation.sourceLanguage { + sourceLanguage = Locale.current.localizedString(forIdentifier: language) ?? L10n.Common.Controls.Status.Translation.unknownLanguage + } else { + sourceLanguage = L10n.Common.Controls.Status.Translation.unknownLanguage } - return nil + + return L10n.Common.Controls.Status.Translation.translatedFrom(sourceLanguage, provider) } translatedFromLabel diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift index d669c5d2d..c636620e6 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/StatusView.swift @@ -668,7 +668,7 @@ extension StatusView { } private var hideTranslationAction: UIAccessibilityCustomAction? { - guard viewModel.translatedFromLanguage != nil else { return nil } + guard viewModel.translation?.sourceLanguage != nil else { return nil } return UIAccessibilityCustomAction(name: L10n.Common.Controls.Status.Translation.showOriginal) { [weak self] _ in self?.revertTranslation() return true