Merge pull request #1135 from mastodon/nuke_coredata_translations
Don't persist translations
This commit is contained in:
commit
a3733ea578
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -499,12 +499,22 @@ 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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -11,16 +11,6 @@ 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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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? {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -17,6 +17,10 @@ extension Mastodon.API.Statuses {
|
||||||
.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)
|
||||||
|
|
|
@ -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
|
|
||||||
else { return nil }
|
|
||||||
|
|
||||||
var configuration: Mastodon.Entity.V2.Instance.Configuration? = nil
|
let menuContext = NotificationView.AuthorMenuContext(
|
||||||
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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,9 +147,8 @@ 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,7 +655,7 @@ extension StatusView.ViewModel {
|
||||||
$isFollowed
|
$isFollowed
|
||||||
)
|
)
|
||||||
let publishersThree = Publishers.CombineLatest(
|
let publishersThree = Publishers.CombineLatest(
|
||||||
$translatedFromLanguage,
|
$translation,
|
||||||
$language
|
$language
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -666,49 +664,38 @@ extension StatusView.ViewModel {
|
||||||
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
let (menu, actions) = authorView.setupAuthorMenu(menuContext: menuContext)
|
||||||
|
authorView.menuButton.menu = menu
|
||||||
|
authorView.authorActions = actions
|
||||||
|
authorView.menuButton.showsMenuAsPrimaryAction = true
|
||||||
}
|
}
|
||||||
|
.store(in: &disposeBag)
|
||||||
lazy var instanceConfigurationV2: Mastodon.Entity.V2.Instance.Configuration? = {
|
|
||||||
guard
|
|
||||||
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 = 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) {
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue