IOS-80 Add AXCustomContent to ProfileCardView (#979)

This commit is contained in:
Jed Fox 2023-03-20 03:02:41 -04:00 committed by GitHub
parent 85ad331a5e
commit f0753e9d0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 107 additions and 11 deletions

View File

@ -508,7 +508,8 @@
"my_followers": "followers", "my_followers": "followers",
"other_posts": "posts", "other_posts": "posts",
"other_following": "following", "other_following": "following",
"other_followers": "followers" "other_followers": "followers",
"familiar_followers": "mutuals"
}, },
"fields": { "fields": {
"joined": "Joined", "joined": "Joined",

View File

@ -553,7 +553,8 @@
"my_followers": "followers", "my_followers": "followers",
"other_posts": "posts", "other_posts": "posts",
"other_following": "following", "other_following": "following",
"other_followers": "followers" "other_followers": "followers",
"familiar_followers": "mutuals"
}, },
"fields": { "fields": {
"joined": "Joined", "joined": "Joined",

View File

@ -70,7 +70,13 @@ extension DiscoveryForYouViewController {
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] isFetching in .sink { [weak self] isFetching in
guard let self = self else { return } guard let self = self else { return }
if !isFetching { if isFetching {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if !self.refreshControl.isRefreshing {
self.refreshControl.beginRefreshing()
}
}
} else {
self.refreshControl.endRefreshing() self.refreshControl.endRefreshing()
} }
} }

View File

@ -899,6 +899,8 @@ public enum L10n {
public static let showBannerImage = L10n.tr("Localizable", "Scene.Profile.Accessibility.ShowBannerImage", fallback: "Show banner image") public static let showBannerImage = L10n.tr("Localizable", "Scene.Profile.Accessibility.ShowBannerImage", fallback: "Show banner image")
} }
public enum Dashboard { public enum Dashboard {
/// mutuals
public static let familiarFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.FamiliarFollowers", fallback: "mutuals")
/// followers /// followers
public static let myFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.MyFollowers", fallback: "followers") public static let myFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.MyFollowers", fallback: "followers")
/// following /// following

View File

@ -311,6 +311,7 @@ uploaded to Mastodon.";
"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; "Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image";
"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; "Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image";
"Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; "Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image";
"Scene.Profile.Dashboard.FamiliarFollowers" = "mutuals";
"Scene.Profile.Dashboard.MyFollowers" = "followers"; "Scene.Profile.Dashboard.MyFollowers" = "followers";
"Scene.Profile.Dashboard.MyFollowing" = "following"; "Scene.Profile.Dashboard.MyFollowing" = "following";
"Scene.Profile.Dashboard.MyPosts" = "posts"; "Scene.Profile.Dashboard.MyPosts" = "posts";

View File

@ -0,0 +1,21 @@
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
import Accessibility
extension AXCustomContent {
convenience init?(label: String, value: String?) {
if let value, !value.isEmpty {
self.init(label: label, value: value)
} else {
return nil
}
}
convenience init?(label: String, value: (some BinaryInteger)?) {
if let value {
self.init(label: label, value: value.formatted())
} else {
return nil
}
}
}

View File

@ -7,6 +7,7 @@
import Meta import Meta
import MastodonLocalization import MastodonLocalization
import Foundation
extension Meta { extension Meta {
public var accessibilityLabel: String? { public var accessibilityLabel: String? {
@ -25,3 +26,14 @@ extension Meta {
} }
} }
} }
extension MetaContent {
public var accessibilityLabel: String {
return entities.reversed().reduce(string) { string, entity in
if case .emoji(_, let shortcode, _, _) = entity.meta {
return (string as NSString).replacingCharacters(in: entity.range, with: ":" + shortcode + ":")
}
return string
} as String
}
}

View File

@ -9,6 +9,7 @@ import os.log
import UIKit import UIKit
import Combine import Combine
import CoreDataStack import CoreDataStack
import Meta
import MastodonCore import MastodonCore
import MastodonMeta import MastodonMeta
import MastodonLocalization import MastodonLocalization
@ -23,6 +24,8 @@ extension FamiliarFollowersDashboardView {
@Published var names: [String] = [] @Published var names: [String] = []
@Published var emojis: MastodonContent.Emojis = [:] @Published var emojis: MastodonContent.Emojis = [:]
@Published var backgroundColor: UIColor? @Published var backgroundColor: UIColor?
@Published var label: MetaContent?
} }
} }
@ -74,11 +77,11 @@ extension FamiliarFollowersDashboardView.ViewModel {
} }
.store(in: &disposeBag) .store(in: &disposeBag)
Publishers.CombineLatest( let label = Publishers.CombineLatest(
$names, $names,
$emojis $emojis
) )
.sink { names, emojis in .map { (names, emojis) -> MetaContent in
let content: String = { let content: String = {
guard names.count > 0 else { return " " } guard names.count > 0 else { return " " }
@ -97,13 +100,18 @@ extension FamiliarFollowersDashboardView.ViewModel {
}() }()
let document = MastodonContent(content: content, emojis: emojis) let document = MastodonContent(content: content, emojis: emojis)
do { do {
let metaContent = try MastodonMetaContent.convert(document: document) return try MastodonMetaContent.convert(document: document)
view.descriptionMetaLabel.configure(content: metaContent)
} catch { } catch {
assertionFailure() assertionFailure()
view.descriptionMetaLabel.configure(content: PlaintextMetaContent(string: content)) return PlaintextMetaContent(string: content)
} }
} }
.store(in: &disposeBag)
label
.sink { [weak self] metaContent in
view.descriptionMetaLabel.configure(content: metaContent)
self?.label = metaContent
}
.store(in: &disposeBag)
} }
} }

View File

@ -246,5 +246,47 @@ extension ProfileCardView.ViewModel {
view.accessibilityLabel = accessibilityLabel view.accessibilityLabel = accessibilityLabel
} }
.store(in: &disposeBag) .store(in: &disposeBag)
let statusesContent = $statusesCount
.removeDuplicates()
.map {
AXCustomContent(
label: L10n.Scene.Profile.Dashboard.otherPosts,
value: $0
)
}
let followingContent = $followingCount
.removeDuplicates()
.map {
AXCustomContent(
label: L10n.Scene.Profile.Dashboard.otherFollowing,
value: $0
)
}
let followersContent = $followersCount
.removeDuplicates()
.map {
AXCustomContent(
label: L10n.Scene.Profile.Dashboard.otherFollowers,
value: $0
)
}
let familiarContent = view.familiarFollowersDashboardView.viewModel.$label
.map { $0?.accessibilityLabel }
.removeDuplicates()
.map {
AXCustomContent(
label: L10n.Scene.Profile.Dashboard.familiarFollowers,
value: $0
)
}
Publishers.CombineLatest4(
statusesContent,
followingContent,
followersContent,
familiarContent
).sink { statuses, following, followers, familiar in
view.accessibilityCustomContent = [statuses, following, followers, familiar].compactMap { $0 }
}.store(in: &disposeBag)
} }
} }

View File

@ -16,7 +16,7 @@ public protocol ProfileCardViewDelegate: AnyObject {
func profileCardView(_ profileCardView: ProfileCardView, familiarFollowersDashboardViewDidPressed view: FamiliarFollowersDashboardView) func profileCardView(_ profileCardView: ProfileCardView, familiarFollowersDashboardViewDidPressed view: FamiliarFollowersDashboardView)
} }
public final class ProfileCardView: UIView { public final class ProfileCardView: UIView, AXCustomContentProvider {
let logger = Logger(subsystem: "ProfileCardView", category: "View") let logger = Logger(subsystem: "ProfileCardView", category: "View")
@ -27,7 +27,9 @@ public final class ProfileCardView: UIView {
weak var delegate: ProfileCardViewDelegate? weak var delegate: ProfileCardViewDelegate?
private var _disposeBag = Set<AnyCancellable>() private var _disposeBag = Set<AnyCancellable>()
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
public var accessibilityCustomContent: [AXCustomContent]! = []
let container = UIStackView() let container = UIStackView()
let bannerImageView: UIImageView = { let bannerImageView: UIImageView = {