Skip to content
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
Copy Markdown
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
Copy Markdown
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