Merge pull request #1135 from mastodon/nuke_coredata_translations

Don't persist translations
This commit is contained in:
Nathan Mattes 2023-10-19 12:04:45 +02:00 committed by GitHub
commit a3733ea578
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 139 additions and 175 deletions

View File

@ -151,7 +151,7 @@ extension DataSourceFacade {
struct MenuContext { struct MenuContext {
let author: ManagedObjectRecord<MastodonUser>? let author: ManagedObjectRecord<MastodonUser>?
let status: ManagedObjectRecord<Status>? let statusViewModel: StatusView.ViewModel?
let button: UIButton? let button: UIButton?
let barButtonItem: UIBarButtonItem? let barButtonItem: UIBarButtonItem?
} }
@ -266,7 +266,7 @@ extension DataSourceFacade {
context: dependency.context, context: dependency.context,
authContext: dependency.authContext, authContext: dependency.authContext,
user: user, user: user,
status: menuContext.status status: menuContext.statusViewModel?.originalStatus?.asRecord
) )
_ = dependency.coordinator.present( _ = dependency.coordinator.present(
@ -297,7 +297,7 @@ extension DataSourceFacade {
) )
case .bookmarkStatus: case .bookmarkStatus:
Task { Task {
guard let status = menuContext.status else { guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else {
assertionFailure() assertionFailure()
return return
} }
@ -310,7 +310,7 @@ extension DataSourceFacade {
Task { Task {
let managedObjectContext = dependency.context.managedObjectContext let managedObjectContext = dependency.context.managedObjectContext
guard let status: ManagedObjectRecord<Status> = try? await managedObjectContext.perform(block: { guard let status: ManagedObjectRecord<Status> = 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 let objectID = (object.reblog ?? object).objectID
return .init(objectID: objectID) return .init(objectID: objectID)
}) else { }) else {
@ -344,7 +344,7 @@ extension DataSourceFacade {
style: .destructive style: .destructive
) { [weak dependency] _ in ) { [weak dependency] _ in
guard let dependency = dependency else { return } guard let dependency = dependency else { return }
guard let status = menuContext.status else { return } guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { return }
Task { Task {
try await DataSourceFacade.responseToDeleteStatus( try await DataSourceFacade.responseToDeleteStatus(
dependency: dependency, dependency: dependency,
@ -358,12 +358,12 @@ extension DataSourceFacade {
dependency.present(alertController, animated: true) dependency.present(alertController, animated: true)
case .translateStatus: case .translateStatus:
guard let status = menuContext.status else { return } guard let status = menuContext.statusViewModel?.originalStatus?.asRecord else { return }
do { do {
try await DataSourceFacade.translateStatus( let translation = try await DataSourceFacade.translateStatus(provider: dependency,status: status)
provider: dependency,
status: status menuContext.statusViewModel?.translation = translation
)
} catch TranslationFailure.emptyOrInvalidResponse { } catch TranslationFailure.emptyOrInvalidResponse {
let alertController = UIAlertController(title: L10n.Common.Alerts.TranslationFailed.title, message: L10n.Common.Alerts.TranslationFailed.message, preferredStyle: .alert) 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)) alertController.addAction(UIAlertAction(title: L10n.Common.Alerts.TranslationFailed.button, style: .default))
@ -371,7 +371,7 @@ extension DataSourceFacade {
} }
case .editStatus: 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( let statusSource = try await dependency.context.apiService.getStatusSource(
forStatusID: status.id, forStatusID: status.id,

View File

@ -9,6 +9,7 @@ import UIKit
import CoreData import CoreData
import CoreDataStack import CoreDataStack
import MastodonCore import MastodonCore
import MastodonSDK
typealias Provider = UIViewController & NeedsDependency & AuthContextProvider typealias Provider = UIViewController & NeedsDependency & AuthContextProvider
@ -20,26 +21,26 @@ extension DataSourceFacade {
public static func translateStatus( public static func translateStatus(
provider: Provider, provider: Provider,
status: ManagedObjectRecord<Status> status: ManagedObjectRecord<Status>
) async throws { ) async throws -> Mastodon.Entity.Translation? {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator() let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged() await selectionFeedbackGenerator.selectionChanged()
guard guard
let status = status.object(in: provider.context.managedObjectContext) let status = status.object(in: provider.context.managedObjectContext)
else { else {
return return nil
} }
if let reblog = status.reblog { if let reblog = status.reblog {
try await translateAndApply(provider: provider, status: reblog) return try await translateStatus(provider: provider, status: reblog)
} else { } else {
try await translateAndApply(provider: provider, status: status) return try await translateStatus(provider: provider, status: status)
} }
} }
} }
private extension DataSourceFacade { 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 { do {
let value = try await provider.context let value = try await provider.context
.apiService .apiService
@ -49,22 +50,12 @@ private extension DataSourceFacade {
).value ).value
guard let content = value.content else { guard let content = value.content else {
throw TranslationFailure.emptyOrInvalidResponse return nil
} }
return Status.TranslatedContent(content: content, provider: value.provider) return value
} catch { } catch {
throw TranslationFailure.emptyOrInvalidResponse 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
}
}
} }

View File

@ -44,7 +44,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
action: action, action: action,
menuContext: .init( menuContext: .init(
author: author, author: author,
status: nil, statusViewModel: nil,
button: button, button: button,
barButtonItem: nil barButtonItem: nil
) )

View File

@ -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( try await DataSourceFacade.responseToMenuAction(
dependency: self, dependency: self,
action: action, action: action,
menuContext: .init( menuContext: .init(
author: author, author: author,
status: status, statusViewModel: statusViewModel,
button: button, button: button,
barButtonItem: nil barButtonItem: nil
) )

View File

@ -894,7 +894,7 @@ extension ProfileViewController: MastodonMenuDelegate {
action: action, action: action,
menuContext: DataSourceFacade.MenuContext( menuContext: DataSourceFacade.MenuContext(
author: userRecord, author: userRecord,
status: nil, statusViewModel: nil,
button: nil, button: nil,
barButtonItem: self.moreMenuBarButtonItem barButtonItem: self.moreMenuBarButtonItem
) )

View File

@ -90,8 +90,7 @@ extension StatusTableViewCell {
} }
.store(in: &_disposeBag) .store(in: &_disposeBag)
statusView.viewModel statusView.viewModel.$translation
.$translatedFromLanguage
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in .sink(receiveValue: { [weak self] _ in
self?.invalidateIntrinsicContentSize() self?.invalidateIntrinsicContentSize()

View File

@ -76,8 +76,7 @@ extension StatusThreadRootTableViewCell {
statusView.contentMetaText.textView.isAccessibilityElement = true statusView.contentMetaText.textView.isAccessibilityElement = true
statusView.contentMetaText.textView.isSelectable = true statusView.contentMetaText.textView.isSelectable = true
statusView.viewModel statusView.viewModel.$translation
.$translatedFromLanguage
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in .sink(receiveValue: { [weak self] _ in
self?.invalidateIntrinsicContentSize() self?.invalidateIntrinsicContentSize()

View File

@ -10,17 +10,7 @@ import Foundation
public final class Status: NSManagedObject { public final class Status: NSManagedObject {
public typealias ID = String 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 // sourcery: autoGenerateProperty
@NSManaged public private(set) var identifier: ID @NSManaged public private(set) var identifier: ID
// sourcery: autoGenerateProperty // sourcery: autoGenerateProperty
@ -118,9 +108,6 @@ public final class Status: NSManagedObject {
@NSManaged public private(set) var deletedAt: Date? @NSManaged public private(set) var deletedAt: Date?
// sourcery: autoUpdatableObject // sourcery: autoUpdatableObject
@NSManaged public private(set) var revealedAt: Date? @NSManaged public private(set) var revealedAt: Date?
// sourcery: autoUpdatableObject
@NSManaged public private(set) var translatedContent: TranslatedContent?
} }
extension Status { extension Status {
@ -535,11 +522,6 @@ extension Status: AutoUpdatableObject {
self.revealedAt = revealedAt self.revealedAt = revealedAt
} }
} }
public func update(translatedContent: TranslatedContent?) {
if self.translatedContent != translatedContent {
self.translatedContent = translatedContent
}
}
public func update(attachments: [MastodonAttachment]) { public func update(attachments: [MastodonAttachment]) {
if self.attachments != attachments { if self.attachments != attachments {
self.attachments = attachments self.attachments = attachments

View File

@ -49,7 +49,7 @@ extension Instance {
version?.majorServerVersion(greaterThanOrEquals: 4) ?? false // following Tags is support beginning with Mastodon v4.0.0 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 { if let configuration = configurationV2 {
return configuration.translation?.enabled == true return configuration.translation?.enabled == true
} }

View File

@ -84,12 +84,14 @@ public struct MastodonAuthentication: Codable, Hashable {
} }
public func instance(in context: NSManagedObjectContext) -> Instance? { public func instance(in context: NSManagedObjectContext) -> Instance? {
guard guard let instanceObjectIdURI,
let instanceObjectIdURI = instanceObjectIdURI, let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI)
let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: instanceObjectIdURI) else {
else { return nil } return nil
}
return try? context.existingObject(with: objectID) as? Instance
let instance = try? context.existingObject(with: objectID) as? Instance
return instance
} }
public func user(in context: NSManagedObjectContext) -> MastodonUser? { public func user(in context: NSManagedObjectContext) -> MastodonUser? {

View File

@ -19,12 +19,14 @@ extension APIService {
) async throws -> Mastodon.Response.Content<Mastodon.Entity.Translation> { ) async throws -> Mastodon.Response.Content<Mastodon.Entity.Translation> {
let domain = authenticationBox.domain let domain = authenticationBox.domain
let authorization = authenticationBox.userAuthorization let authorization = authenticationBox.userAuthorization
let targetLanguage = Bundle.main.preferredLocalizations.first
let response = try await Mastodon.API.Statuses.translate( let response = try await Mastodon.API.Statuses.translate(
session: session, session: session,
domain: domain, domain: domain,
statusID: statusID, statusID: statusID,
authorization: authorization authorization: authorization,
targetLanguage: targetLanguage
).singleOutput() ).singleOutput()
return response return response

View File

@ -16,7 +16,11 @@ extension Mastodon.API.Statuses {
.appendingPathComponent(statusID) .appendingPathComponent(statusID)
.appendingPathComponent("translate") .appendingPathComponent("translate")
} }
public struct TranslateQuery: Codable, PostQuery {
public let lang: String
}
/// Translate Status /// Translate Status
/// ///
/// Translate a given Status. /// Translate a given Status.
@ -31,11 +35,21 @@ extension Mastodon.API.Statuses {
session: URLSession, session: URLSession,
domain: String, domain: String,
statusID: Mastodon.Entity.Status.ID, statusID: Mastodon.Entity.Status.ID,
authorization: Mastodon.API.OAuth.Authorization? authorization: Mastodon.API.OAuth.Authorization?,
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Translation>, Error> { targetLanguage: String?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Translation>, Error> {
let query: TranslateQuery?
if let targetLanguage {
query = TranslateQuery(lang: targetLanguage)
} else {
query = nil
}
let request = Mastodon.API.post( let request = Mastodon.API.post(
url: translateEndpointURL(domain: domain, statusID: statusID), url: translateEndpointURL(domain: domain, statusID: statusID),
query: nil, query: query,
authorization: authorization authorization: authorization
) )
return session.dataTaskPublisher(for: request) return session.dataTaskPublisher(for: request)

View File

@ -221,38 +221,27 @@ extension NotificationView.ViewModel {
) )
) )
.sink { [weak self] authorName, isMuting, isBlocking, isMyselfIsTranslatedIsFollowed in .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 notificationView.menuButton.menu = nil
return return
} }
let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed let (isMyself, isTranslated, isFollowed) = isMyselfIsTranslatedIsFollowed
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = { let authentication = authContext.mastodonAuthenticationBox.authentication
guard let instance = authentication.instance(in: context.managedObjectContext)
let self = self, let isTranslationEnabled = instance?.isTranslationEnabled ?? false
let context = self.context,
let authContext = self.authContext let menuContext = NotificationView.AuthorMenuContext(
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(
name: name, name: name,
isMuting: isMuting, isMuting: isMuting,
isBlocking: isBlocking, isBlocking: isBlocking,
isMyself: isMyself, isMyself: isMyself,
isBookmarking: false, // no bookmark action display for notification item isBookmarking: false, // no bookmark action display for notification item
isFollowed: isFollowed, isFollowed: isFollowed,
isTranslationEnabled: instanceConfigurationV2?.translation?.enabled == true, isTranslationEnabled: isTranslationEnabled,
isTranslated: isTranslated, isTranslated: isTranslated,
statusLanguage: "" statusLanguage: nil
) )
let (menu, actions) = notificationView.setupAuthorMenu(menuContext: menuContext) let (menu, actions) = notificationView.setupAuthorMenu(menuContext: menuContext)
notificationView.menuButton.menu = menu notificationView.menuButton.menu = menu

View File

@ -179,7 +179,10 @@ extension StatusAuthorView {
postActions.append(.editStatus) 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 { if menuContext.isTranslated == false {
postActions.append(.translateStatus(.init(language: statusLanguage))) postActions.append(.translateStatus(.init(language: statusLanguage)))
} else { } else {

View File

@ -85,13 +85,10 @@ extension StatusView {
configureToolbar(status: status) configureToolbar(status: status)
configureFilter(status: status) configureFilter(status: status)
viewModel.originalStatus = status viewModel.originalStatus = status
[
status.publisher(for: \.translatedContent), viewModel.$translation
status.reblog?.publisher(for: \.translatedContent)
].compactMap { $0 }
.last?
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] _ in .sink { [weak self] translation in
self?.configureTranslated(status: status) self?.configureTranslated(status: status)
} }
.store(in: &disposeBag) .store(in: &disposeBag)
@ -293,36 +290,22 @@ extension StatusView {
public func revertTranslation() { public func revertTranslation() {
guard let originalStatus = viewModel.originalStatus else { return } guard let originalStatus = viewModel.originalStatus else { return }
viewModel.translatedFromLanguage = nil viewModel.translation = nil
viewModel.translatedUsingProvider = nil
originalStatus.reblog?.update(translatedContent: nil)
originalStatus.update(translatedContent: nil)
configure(status: originalStatus) configure(status: originalStatus)
} }
func configureTranslated(status: Status) { func configureTranslated(status: Status) {
let translatedContent: Status.TranslatedContent? = { guard let translation = viewModel.translation,
if let translatedContent = status.reblog?.translatedContent { let translatedContent = translation.content else {
return translatedContent
}
return status.translatedContent
}()
guard
let translatedContent = translatedContent
else {
viewModel.isCurrentlyTranslating = false viewModel.isCurrentlyTranslating = false
return return
} }
// content // content
do { 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) let metaContent = try MastodonMetaContent.convert(document: content)
viewModel.content = metaContent viewModel.content = metaContent
viewModel.translatedFromLanguage = status.reblog?.language ?? status.language
viewModel.translatedUsingProvider = status.reblog?.translatedContent?.provider ?? status.translatedContent?.provider
viewModel.isCurrentlyTranslating = false viewModel.isCurrentlyTranslating = false
} catch { } catch {
assertionFailure(error.localizedDescription) assertionFailure(error.localizedDescription)
@ -342,7 +325,6 @@ extension StatusView {
let content = MastodonContent(content: statusEdit.content, emojis: statusEdit.emojis.asDictionary) let content = MastodonContent(content: statusEdit.content, emojis: statusEdit.emojis.asDictionary)
let metaContent = try MastodonMetaContent.convert(document: content) let metaContent = try MastodonMetaContent.convert(document: content)
viewModel.content = metaContent viewModel.content = metaContent
viewModel.translatedFromLanguage = nil
viewModel.isCurrentlyTranslating = false viewModel.isCurrentlyTranslating = false
} catch { } catch {
assertionFailure(error.localizedDescription) assertionFailure(error.localizedDescription)
@ -351,7 +333,7 @@ extension StatusView {
} }
private func configureContent(status: Status) { private func configureContent(status: Status) {
guard status.translatedContent == nil else { guard viewModel.translation == nil else {
return configureTranslated(status: status) return configureTranslated(status: status)
} }
@ -377,7 +359,6 @@ extension StatusView {
let content = MastodonContent(content: status.content, emojis: status.emojis.asDictionary) let content = MastodonContent(content: status.content, emojis: status.emojis.asDictionary)
let metaContent = try MastodonMetaContent.convert(document: content) let metaContent = try MastodonMetaContent.convert(document: content)
viewModel.content = metaContent viewModel.content = metaContent
viewModel.translatedFromLanguage = nil
viewModel.isCurrentlyTranslating = false viewModel.isCurrentlyTranslating = false
} catch { } catch {
assertionFailure(error.localizedDescription) assertionFailure(error.localizedDescription)

View File

@ -46,8 +46,7 @@ extension StatusView {
// Translation // Translation
@Published public var isCurrentlyTranslating = false @Published public var isCurrentlyTranslating = false
@Published public var translatedFromLanguage: String? @Published public var translation: Mastodon.Entity.Translation? = nil
@Published public var translatedUsingProvider: String?
@Published public var timestamp: Date? @Published public var timestamp: Date?
public var timestampFormatter: ((_ date: Date, _ isEdited: Bool) -> String)? public var timestampFormatter: ((_ date: Date, _ isEdited: Bool) -> String)?
@ -148,10 +147,9 @@ extension StatusView {
isContentSensitive = false isContentSensitive = false
isMediaSensitive = false isMediaSensitive = false
isSensitiveToggled = false isSensitiveToggled = false
translatedFromLanguage = nil
translatedUsingProvider = nil
isCurrentlyTranslating = false isCurrentlyTranslating = false
translation = nil
activeFilters = [] activeFilters = []
filterContext = nil filterContext = nil
} }
@ -657,60 +655,49 @@ extension StatusView.ViewModel {
$isFollowed $isFollowed
) )
let publishersThree = Publishers.CombineLatest( let publishersThree = Publishers.CombineLatest(
$translatedFromLanguage, $translation,
$language $language
) )
Publishers.CombineLatest3( Publishers.CombineLatest3(
publisherOne.eraseToAnyPublisher(), publisherOne.eraseToAnyPublisher(),
publishersTwo.eraseToAnyPublisher(), publishersTwo.eraseToAnyPublisher(),
publishersThree.eraseToAnyPublisher() publishersThree.eraseToAnyPublisher()
).eraseToAnyPublisher() ).eraseToAnyPublisher()
.sink { tupleOne, tupleTwo, tupleThree in .sink { tupleOne, tupleTwo, tupleThree in
let (authorName, isMyself) = tupleOne let (authorName, isMyself) = tupleOne
let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo let (isMuting, isBlocking, isBookmark, isFollowed) = tupleTwo
let (translatedFromLanguage, language) = tupleThree let (translatedFromLanguage, language) = tupleThree
guard let name = authorName?.string else { guard let name = authorName?.string, let context = self.context, let authContext = self.authContext else {
statusView.authorView.menuButton.menu = nil statusView.authorView.menuButton.menu = nil
return return
}
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = {
guard
let context = self.context,
let authContext = self.authContext
else {
return nil
} }
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 let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext)
context.managedObjectContext.performAndWait { authorView.menuButton.menu = menu
let authentication = authContext.mastodonAuthenticationBox.authentication authorView.authorActions = actions
configuration = authentication.instance(in: context.managedObjectContext)?.configurationV2 authorView.menuButton.showsMenuAsPrimaryAction = true
} }
return configuration .store(in: &disposeBag)
}()
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)
} }
private func bindFilter(statusView: StatusView) { private func bindFilter(statusView: StatusView) {
$isFiltered $isFiltered
.sink { isFiltered in .sink { isFiltered in
@ -877,15 +864,20 @@ extension StatusView.ViewModel {
.assign(to: \.toolbarActions, on: statusView) .assign(to: \.toolbarActions, on: statusView)
.store(in: &disposeBag) .store(in: &disposeBag)
let translatedFromLabel = Publishers.CombineLatest($translatedFromLanguage, $translatedUsingProvider) let translatedFromLabel = $translation
.map { (language, provider) -> String? in .map { translation -> String? in
if let language { guard let translation else { return nil }
return L10n.Common.Controls.Status.Translation.translatedFrom(
Locale.current.localizedString(forIdentifier: language) ?? L10n.Common.Controls.Status.Translation.unknownLanguage, let provider = translation.provider ?? L10n.Common.Controls.Status.Translation.unknownProvider
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 translatedFromLabel

View File

@ -668,7 +668,7 @@ extension StatusView {
} }
private var hideTranslationAction: UIAccessibilityCustomAction? { 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 return UIAccessibilityCustomAction(name: L10n.Common.Controls.Status.Translation.showOriginal) { [weak self] _ in
self?.revertTranslation() self?.revertTranslation()
return true return true