IceCubesApp/Packages/StatusKit/Sources/StatusKit/Row/StatusRowAccessibilityLabel.swift
2024-01-06 19:27:26 +01:00

123 lines
4.2 KiB
Swift

import SwiftUI
import Models
/// A utility that creates a suitable combined accessibility label for a `StatusRowView` that is not focused.
@MainActor
struct StatusRowAccessibilityLabel {
let viewModel: StatusRowViewModel
var hasSpoiler: Bool {
viewModel.displaySpoiler && viewModel.finalStatus.spoilerText.asRawText.isEmpty == false
}
var isReply: Bool {
if let accountId = viewModel.status.inReplyToAccountId, viewModel.status.mentions.contains(where: { $0.id == accountId }) {
return true
}
return false
}
var isBoost: Bool {
viewModel.status.reblog != nil
}
var filter: Filter? {
guard viewModel.isFiltered else {
return nil
}
return viewModel.filter?.filter
}
func finalLabel() -> Text {
if let filter {
switch filter.filterAction {
case .warn:
Text("status.filter.filtered-by-\(filter.title)")
case .hide:
Text("")
}
} else {
userNamePreamble() +
Text(hasSpoiler
? viewModel.finalStatus.spoilerText.asRawText
: viewModel.finalStatus.content.asRawText
) +
Text(hasSpoiler
? "status.editor.spoiler"
: ""
) + Text(", ") +
pollText() +
imageAltText() +
Text(viewModel.finalStatus.createdAt.relativeFormatted) + Text(", ") +
Text("status.summary.n-replies \(viewModel.finalStatus.repliesCount)") + Text(", ") +
Text("status.summary.n-boosts \(viewModel.finalStatus.reblogsCount)") + Text(", ") +
Text("status.summary.n-favorites \(viewModel.finalStatus.favouritesCount)")
}
}
func userNamePreamble() -> Text {
switch (isReply, isBoost) {
case (true, false):
Text("accessibility.status.a-replied-to-\(finalUserDisplayName())") + Text(" ")
case (_, true):
Text("accessibility.status.a-boosted-b-\(userDisplayName())-\(finalUserDisplayName())") + Text(", ")
default:
Text(userDisplayName()) + Text(", ")
}
}
func userDisplayName() -> String {
viewModel.status.account.displayNameWithoutEmojis.count < 4
? viewModel.status.account.safeDisplayName
: viewModel.status.account.displayNameWithoutEmojis
}
func finalUserDisplayName() -> String {
viewModel.finalStatus.account.displayNameWithoutEmojis.count < 4
? viewModel.finalStatus.account.safeDisplayName
: viewModel.finalStatus.account.displayNameWithoutEmojis
}
func imageAltText() -> Text {
let descriptions = viewModel.finalStatus.mediaAttachments
.compactMap(\.description)
if descriptions.count == 1 {
return Text("accessibility.image.alt-text-\(descriptions[0])") + Text(", ")
} else if descriptions.count > 1 {
return Text("accessibility.image.alt-text-\(descriptions[0])") + Text(", ") + Text("accessibility.image.alt-text-more.label") + Text(", ")
} else if viewModel.finalStatus.mediaAttachments.isEmpty == false {
let differentTypes = Set(viewModel.finalStatus.mediaAttachments.compactMap(\.localizedTypeDescription)).sorted()
return Text("accessibility.status.contains-media.label-\(ListFormatter.localizedString(byJoining: differentTypes))") + Text(", ")
} else {
return Text("")
}
}
func pollText() -> Text {
if let poll = viewModel.finalStatus.poll {
let showPercentage = poll.expired || poll.voted ?? false
let title: LocalizedStringKey = poll.expired
? "accessibility.status.poll.finished.label"
: "accessibility.status.poll.active.label"
return poll.options.enumerated().reduce(into: Text(title)) { text, pair in
let (index, option) = pair
let selected = poll.ownVotes?.contains(index) ?? false
let percentage = poll.safeVotersCount > 0 && option.votesCount != nil
? Int(round(Double(option.votesCount!) / Double(poll.safeVotersCount) * 100))
: 0
text = text +
Text(selected ? "accessibility.status.poll.selected.label" : "") +
Text(", ") +
Text("accessibility.status.poll.option-prefix-\(index + 1)-of-\(poll.options.count)") +
Text(", ") +
Text(option.title) +
Text(showPercentage ? ", \(percentage)%. " : ". ")
}
}
return Text("")
}
}