diff --git a/Localization/StringsConvertor/input/Base.lproj/app.json b/Localization/StringsConvertor/input/Base.lproj/app.json index 20271d6af..1ea148b0c 100644 --- a/Localization/StringsConvertor/input/Base.lproj/app.json +++ b/Localization/StringsConvertor/input/Base.lproj/app.json @@ -508,7 +508,8 @@ "my_followers": "followers", "other_posts": "posts", "other_following": "following", - "other_followers": "followers" + "other_followers": "followers", + "familiar_followers": "mutuals" }, "fields": { "joined": "Joined", diff --git a/Localization/app.json b/Localization/app.json index abc42ccf7..13ed0c86c 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -553,7 +553,8 @@ "my_followers": "followers", "other_posts": "posts", "other_following": "following", - "other_followers": "followers" + "other_followers": "followers", + "familiar_followers": "mutuals" }, "fields": { "joined": "Joined", diff --git a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift index dec1d9c72..007abd901 100644 --- a/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift +++ b/Mastodon/Scene/Discovery/ForYou/DiscoveryForYouViewController.swift @@ -70,7 +70,13 @@ extension DiscoveryForYouViewController { .receive(on: DispatchQueue.main) .sink { [weak self] isFetching in 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() } } diff --git a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift index 297a47505..6cdd90473 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift +++ b/MastodonSDK/Sources/MastodonLocalization/Generated/Strings.swift @@ -899,6 +899,8 @@ public enum L10n { public static let showBannerImage = L10n.tr("Localizable", "Scene.Profile.Accessibility.ShowBannerImage", fallback: "Show banner image") } public enum Dashboard { + /// mutuals + public static let familiarFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.FamiliarFollowers", fallback: "mutuals") /// followers public static let myFollowers = L10n.tr("Localizable", "Scene.Profile.Dashboard.MyFollowers", fallback: "followers") /// following diff --git a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings index d5ff76940..7e6f9a89e 100644 --- a/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings +++ b/MastodonSDK/Sources/MastodonLocalization/Resources/Base.lproj/Localizable.strings @@ -311,6 +311,7 @@ uploaded to Mastodon."; "Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image"; "Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image"; "Scene.Profile.Accessibility.ShowBannerImage" = "Show banner image"; +"Scene.Profile.Dashboard.FamiliarFollowers" = "mutuals"; "Scene.Profile.Dashboard.MyFollowers" = "followers"; "Scene.Profile.Dashboard.MyFollowing" = "following"; "Scene.Profile.Dashboard.MyPosts" = "posts"; diff --git a/MastodonSDK/Sources/MastodonUI/Extension/AXCustomContent.swift b/MastodonSDK/Sources/MastodonUI/Extension/AXCustomContent.swift new file mode 100644 index 000000000..f8b2a8fca --- /dev/null +++ b/MastodonSDK/Sources/MastodonUI/Extension/AXCustomContent.swift @@ -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 + } + } +} diff --git a/MastodonSDK/Sources/MastodonUI/Extension/Meta+Accessibility.swift b/MastodonSDK/Sources/MastodonUI/Extension/Meta+Accessibility.swift index 1cc014433..d2ca0ff31 100644 --- a/MastodonSDK/Sources/MastodonUI/Extension/Meta+Accessibility.swift +++ b/MastodonSDK/Sources/MastodonUI/Extension/Meta+Accessibility.swift @@ -7,6 +7,7 @@ import Meta import MastodonLocalization +import Foundation extension Meta { 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 + } +} diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift index 8de1eead2..651ae5b13 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/FamiliarFollowersDashboardView+ViewModel.swift @@ -9,6 +9,7 @@ import os.log import UIKit import Combine import CoreDataStack +import Meta import MastodonCore import MastodonMeta import MastodonLocalization @@ -23,6 +24,8 @@ extension FamiliarFollowersDashboardView { @Published var names: [String] = [] @Published var emojis: MastodonContent.Emojis = [:] @Published var backgroundColor: UIColor? + + @Published var label: MetaContent? } } @@ -74,11 +77,11 @@ extension FamiliarFollowersDashboardView.ViewModel { } .store(in: &disposeBag) - Publishers.CombineLatest( + let label = Publishers.CombineLatest( $names, $emojis ) - .sink { names, emojis in + .map { (names, emojis) -> MetaContent in let content: String = { guard names.count > 0 else { return " " } @@ -97,13 +100,18 @@ extension FamiliarFollowersDashboardView.ViewModel { }() let document = MastodonContent(content: content, emojis: emojis) do { - let metaContent = try MastodonMetaContent.convert(document: document) - view.descriptionMetaLabel.configure(content: metaContent) + return try MastodonMetaContent.convert(document: document) } catch { 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) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift index f9a34f392..111568e46 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView+ViewModel.swift @@ -246,5 +246,47 @@ extension ProfileCardView.ViewModel { view.accessibilityLabel = accessibilityLabel } .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) } } diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift index cb7d8f839..15d48b375 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/ProfileCardView.swift @@ -16,7 +16,7 @@ public protocol ProfileCardViewDelegate: AnyObject { 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") @@ -27,7 +27,9 @@ public final class ProfileCardView: UIView { weak var delegate: ProfileCardViewDelegate? private var _disposeBag = Set() var disposeBag = Set() - + + public var accessibilityCustomContent: [AXCustomContent]! = [] + let container = UIStackView() let bannerImageView: UIImageView = {