Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply languageIdentifier attribute to user-generated content where possible #881

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
9 changes: 5 additions & 4 deletions Mastodon/Protocol/Provider/DataSourceFacade+Media.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ extension DataSourceFacade {
previewContext: AttachmentPreviewContext
) async throws {
let managedObjectContext = dependency.context.managedObjectContext
let attachments: [MastodonAttachment] = try await managedObjectContext.perform {
guard let _status = status.object(in: managedObjectContext) else { return [] }
let (attachments, language): ([MastodonAttachment], String?) = try await managedObjectContext.perform {
guard let _status = status.object(in: managedObjectContext) else { return ([], nil) }
let status = _status.reblog ?? _status
return status.attachments
return (status.attachments, status.language)
}

let thumbnails = await previewContext.thumbnails()
Expand Down Expand Up @@ -120,7 +120,8 @@ extension DataSourceFacade {
let mediaPreviewItem = MediaPreviewViewModel.PreviewItem.attachment(.init(
attachments: attachments,
initialIndex: previewContext.index,
thumbnails: thumbnails
thumbnails: thumbnails,
language: language
))

coordinateToMediaPreviewScene(
Expand Down
10 changes: 6 additions & 4 deletions Mastodon/Scene/MediaPreview/AltTextViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,23 @@ class AltTextViewController: UIViewController {

textView.textContainer.maximumNumberOfLines = 0
textView.textContainer.lineBreakMode = .byWordWrapping
textView.font = .preferredFont(forTextStyle: .callout)
textView.isScrollEnabled = true
textView.backgroundColor = .clear
textView.isOpaque = false
textView.isEditable = false
textView.tintColor = .white
textView.textContainerInset = UIEdgeInsets(top: 12, left: 8, bottom: 8, right: 8)
textView.contentInsetAdjustmentBehavior = .always
textView.verticalScrollIndicatorInsets.bottom = 4

return textView
}()

init(alt: String, sourceView: UIView?) {
textView.text = alt
init(alt: String, language: String?, sourceView: UIView?) {
textView.attributedText = NSAttributedString(string: alt, attributes: [
.languageIdentifier: "",
.foregroundColor: UIColor.white,
.font: UIFont.preferredFont(forTextStyle: .callout),
])
super.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .popover
self.popoverPresentationController?.delegate = self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,12 @@ extension MediaPreviewImageViewController {
let previewImageViewContextMenuInteraction = UIContextMenuInteraction(delegate: self)
previewImageView.addInteraction(previewImageViewContextMenuInteraction)

previewImageView.imageView.accessibilityLabel = viewModel.item.altText
previewImageView.imageView.attributedAccessibilityLabel = viewModel.item.altText.map { altText in
AttributedString(
altText,
attributes: AttributeContainer(\.languageIdentifier, value: viewModel.item.language)
)
}

if let thumbnail = viewModel.item.thumbnail {
previewImageView.imageView.image = thumbnail
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ extension MediaPreviewImageViewModel {
let assetURL: URL?
let thumbnail: UIImage?
let altText: String?
let language: String?
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ extension MediaPreviewViewController {
@objc private func altButtonPressed(_ sender: UIButton) {
guard let alt = viewModel.altText else { return }

present(AltTextViewController(alt: alt, sourceView: sender), animated: true)
present(AltTextViewController(alt: alt, language: viewModel.language, sourceView: sender), animated: true)
}
}

Expand Down
12 changes: 9 additions & 3 deletions Mastodon/Scene/MediaPreview/MediaPreviewViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class MediaPreviewViewModel: NSObject {
@Published var currentPage: Int
@Published var showingChrome = true
@Published var altText: String?
@Published var language: String?

// output
let viewControllers: [MediaPreviewPage]
Expand All @@ -47,6 +48,7 @@ final class MediaPreviewViewModel: NSObject {
switch item {
case .attachment(let previewContext):
getAltText = { previewContext.attachments[$0].altDescription }
self.language = previewContext.language

currentPage = previewContext.initialIndex
for (i, attachment) in previewContext.attachments.enumerated() {
Expand All @@ -58,7 +60,8 @@ final class MediaPreviewViewModel: NSObject {
item: .init(
assetURL: attachment.assetURL.flatMap { URL(string: $0) },
thumbnail: previewContext.thumbnail(at: i),
altText: attachment.altDescription
altText: attachment.altDescription,
language: previewContext.language
)
)
viewController.viewModel = viewModel
Expand Down Expand Up @@ -96,7 +99,8 @@ final class MediaPreviewViewModel: NSObject {
item: .init(
assetURL: previewContext.assetURL.flatMap { URL(string: $0) },
thumbnail: previewContext.thumbnail,
altText: nil
altText: nil,
language: nil
)
)
viewController.viewModel = viewModel
Expand All @@ -108,7 +112,8 @@ final class MediaPreviewViewModel: NSObject {
item: .init(
assetURL: previewContext.assetURL.flatMap { URL(string: $0) },
thumbnail: previewContext.thumbnail,
altText: nil
altText: nil,
language: nil
)
)
viewController.viewModel = viewModel
Expand Down Expand Up @@ -161,6 +166,7 @@ extension MediaPreviewViewModel {
let attachments: [MastodonAttachment]
let initialIndex: Int
let thumbnails: [UIImage?]
let language: String?

func thumbnail(at index: Int) -> UIImage? {
guard index < thumbnails.count else { return nil }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ extension StatusTableViewCell {
.receive(on: DispatchQueue.main)
.sink { [weak self] accessibilityLabel in
guard let self = self else { return }
self.accessibilityLabel = accessibilityLabel
self.attributedAccessibilityLabel = accessibilityLabel
}
.store(in: &_disposeBag)

Expand All @@ -92,7 +92,7 @@ extension StatusTableViewCell {
.receive(on: DispatchQueue.main)
.sink { [weak self] contentLabel, accessibilityLabel in
guard let self = self else { return }
self.accessibilityUserInputLabels = [contentLabel, accessibilityLabel]
self.attributedAccessibilityUserInputLabels = [contentLabel, accessibilityLabel]
}
.store(in: &_disposeBag)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ public final class StatusEdit: NSManagedObject {
// sourcery: autoUpdatableObject, autoGenerateProperty
@NSManaged public var spoilerText: String?

// sourcery: autoUpdatableObject, autoGenerateProperty
@NSManaged public var status: Status?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Does the CoreData model already reflect this relationship? I haven't seen any changes to our Model in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes:

<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="editHistory" inverseEntity="Status"/>


// MARK: - AutoGenerateProperty
// sourcery:inline:StatusEdit.AutoGenerateProperty

Expand Down Expand Up @@ -205,6 +208,11 @@ extension StatusEdit: AutoUpdatableObject {
self.spoilerText = spoilerText
}
}
public func update(status: Status?) {
if self.status != status {
self.status = status
}
}
public func update(emojis: [MastodonEmoji]) {
if self.emojis != emojis {
self.emojis = emojis
Expand Down
17 changes: 17 additions & 0 deletions MastodonSDK/Sources/MastodonExtension/AttributeContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.

import Foundation

extension AttributeContainer {
public init<T>(_ attribute: WritableKeyPath<AttributeContainer, T>, value: T) {
self.init()
self[keyPath: attribute] = value
}

public init<T>(_ attribute: WritableKeyPath<AttributeContainer, T>, value: T?) {
self.init()
if let value {
self[keyPath: attribute] = value
}
}
}
25 changes: 25 additions & 0 deletions MastodonSDK/Sources/MastodonExtension/NSAccessibility.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.

import UIKit

extension NSObject {
@inlinable public var attributedAccessibilityLabel: AttributedString? {
get { accessibilityAttributedLabel.map(AttributedString.init) }
set { accessibilityAttributedLabel = newValue.map(NSAttributedString.init) }
}

@inlinable public var attributedAccessibilityValue: AttributedString? {
get { accessibilityAttributedValue.map(AttributedString.init) }
set { accessibilityAttributedValue = newValue.map(NSAttributedString.init) }
}

@inlinable public var attributedAccessibilityHint: AttributedString? {
get { accessibilityAttributedHint.map(AttributedString.init) }
set { accessibilityAttributedHint = newValue.map(NSAttributedString.init) }
}

@inlinable public var attributedAccessibilityUserInputLabels: [AttributedString]! {
get { accessibilityAttributedUserInputLabels?.map(AttributedString.init) }
set { accessibilityAttributedUserInputLabels = newValue?.map(NSAttributedString.init) }
}
}
26 changes: 26 additions & 0 deletions MastodonSDK/Sources/MastodonExtension/Sequence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.

import Foundation

extension Collection<AttributedString> {
// ref: https://github.com/apple/swift/blob/700bcb4e4b97da61517c8b8831c72015207612f9/stdlib/public/core/String.swift#L727-L750
@inlinable public func joined(separator: AttributedString = "") -> AttributedString {
var result: AttributedString = ""
if separator.characters.isEmpty {
for x in self {
result.append(x)
}
return result
}

var iter = makeIterator()
if let first = iter.next() {
result.append(first)
while let next = iter.next() {
result.append(separator)
result.append(next)
}
}
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public protocol StatusCompatible {
var attachments: [MastodonAttachment] { get }
var isMediaSensitive: Bool { get }
var isSensitiveToggled: Bool { get }
var language: String? { get }
}

extension Status: StatusCompatible {}
Expand All @@ -24,4 +25,8 @@ extension StatusEdit: StatusCompatible {
public var isSensitiveToggled: Bool {
true
}

public var language: String? {
status?.language
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension MediaView {
var disposeBag = Set<AnyCancellable>()

public let info: Info
public let language: String?
public let blurhash: String?
public let index: Int
public let total: Int
Expand All @@ -31,11 +32,13 @@ extension MediaView {

public init(
info: MediaView.Configuration.Info,
language: String?,
blurhash: String?,
index: Int,
total: Int
) {
self.info = info
self.language = language
self.blurhash = blurhash
self.index = index
self.total = total
Expand Down Expand Up @@ -203,6 +206,7 @@ extension MediaView {
)
return .init(
info: .image(info: info),
language: status.language,
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
Expand All @@ -211,6 +215,7 @@ extension MediaView {
let info = videoInfo(from: attachment)
return .init(
info: .video(info: info),
language: status.language,
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
Expand All @@ -219,6 +224,7 @@ extension MediaView {
let info = videoInfo(from: attachment)
return .init(
info: .gif(info: info),
language: status.language,
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
Expand All @@ -227,6 +233,7 @@ extension MediaView {
let info = videoInfo(from: attachment)
return .init(
info: .video(info: info),
language: status.language,
blurhash: attachment.blurhash,
index: idx,
total: attachments.count
Expand Down
15 changes: 12 additions & 3 deletions MastodonSDK/Sources/MastodonUI/View/Content/MediaView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,14 +209,23 @@ extension MediaView {
}

private func bindAlt(configuration: Configuration, altDescription: String?) {
let languageAttributes = AttributeContainer(\.languageIdentifier, value: configuration.language)

if configuration.total > 1 {
accessibilityLabel = L10n.Common.Controls.Status.Media.accessibilityLabel(
altDescription ?? "",
let placeholder = "<description>"
let labelString = L10n.Common.Controls.Status.Media.accessibilityLabel(
placeholder,
configuration.index + 1,
configuration.total
)
var label = AttributedString(labelString)
label.replaceSubrange(
label.range(of: placeholder)!,
with: AttributedString(altDescription ?? "", attributes: languageAttributes)
)
self.attributedAccessibilityLabel = label
} else {
accessibilityLabel = altDescription
self.attributedAccessibilityLabel = altDescription.map { AttributedString($0, attributes: languageAttributes) }
}

badgeViewController.rootView.altDescription = altDescription
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extension NewsView {
assetURL: link.image,
altDescription: nil
)),
language: nil,
blurhash: link.blurhash,
index: 1,
total: 1
Expand Down
Loading