Status word preference

This commit is contained in:
Justin Mazzocchi 2021-01-30 17:43:48 -08:00
parent f5aacf7624
commit 5f03ce7d8b
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
15 changed files with 163 additions and 71 deletions

View file

@ -573,9 +573,9 @@ public extension ContentDatabase {
return accountsPublisher.combineLatest(statusesPublisher) return accountsPublisher.combineLatest(statusesPublisher)
.map { accounts, statuses in .map { accounts, statuses in
[.init(items: accounts, titleLocalizedStringKey: "search.scope.accounts"), [.init(items: accounts, searchScope: .accounts),
.init(items: statuses, titleLocalizedStringKey: "search.scope.statuses"), .init(items: statuses, searchScope: .statuses),
.init(items: hashtags, titleLocalizedStringKey: "search.scope.tags")] .init(items: hashtags, searchScope: .tags)]
.filter { !$0.items.isEmpty } .filter { !$0.items.isEmpty }
} }
.removeDuplicates() .removeDuplicates()

View file

@ -4,10 +4,10 @@ import Foundation
public struct CollectionSection: Hashable { public struct CollectionSection: Hashable {
public let items: [CollectionItem] public let items: [CollectionItem]
public let titleLocalizedStringKey: String? public let searchScope: SearchScope?
public init(items: [CollectionItem], titleLocalizedStringKey: String? = nil) { public init(items: [CollectionItem], searchScope: SearchScope? = nil) {
self.items = items self.items = items
self.titleLocalizedStringKey = titleLocalizedStringKey self.searchScope = searchScope
} }
} }

View file

@ -6,8 +6,11 @@ import ViewModels
final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection.Identifier, CollectionItem> { final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection.Identifier, CollectionItem> {
private let updateQueue = private let updateQueue =
DispatchQueue(label: "com.metabolist.metatext.collection-data-source.update-queue") DispatchQueue(label: "com.metabolist.metatext.collection-data-source.update-queue")
private let viewModel: CollectionViewModel
init(tableView: UITableView, viewModel: CollectionViewModel) {
self.viewModel = viewModel
init(tableView: UITableView, viewModelProvider: @escaping (IndexPath) -> CollectionItemViewModel) {
for cellClass in CollectionItem.cellClasses { for cellClass in CollectionItem.cellClasses {
tableView.register(cellClass, forCellReuseIdentifier: String(describing: cellClass)) tableView.register(cellClass, forCellReuseIdentifier: String(describing: cellClass))
} }
@ -17,7 +20,7 @@ final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection
withIdentifier: String(describing: item.cellClass), withIdentifier: String(describing: item.cellClass),
for: indexPath) for: indexPath)
switch (cell, viewModelProvider(indexPath)) { switch (cell, viewModel.viewModel(indexPath: indexPath)) {
case let (statusCell as StatusTableViewCell, statusViewModel as StatusViewModel): case let (statusCell as StatusTableViewCell, statusViewModel as StatusViewModel):
statusCell.viewModel = statusViewModel statusCell.viewModel = statusViewModel
case let (accountCell as AccountTableViewCell, accountViewModel as AccountViewModel): case let (accountCell as AccountTableViewCell, accountViewModel as AccountViewModel):
@ -32,8 +35,9 @@ final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection
tagCell.viewModel = tagViewModel tagCell.viewModel = tagViewModel
case let (_, moreResultsViewModel as MoreResultsViewModel): case let (_, moreResultsViewModel as MoreResultsViewModel):
var configuration = cell.defaultContentConfiguration() var configuration = cell.defaultContentConfiguration()
let statusWord = viewModel.identityContext.appPreferences.statusWord
configuration.text = moreResultsViewModel.scope.moreDescription configuration.text = moreResultsViewModel.scope.moreDescription(statusWord: statusWord)
cell.contentConfiguration = configuration cell.contentConfiguration = configuration
cell.accessoryType = .disclosureIndicator cell.accessoryType = .disclosureIndicator
@ -58,8 +62,8 @@ final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection
let section = currentSnapshot.sectionIdentifiers[section] let section = currentSnapshot.sectionIdentifiers[section]
if currentSnapshot.numberOfItems(inSection: section) > 0, if currentSnapshot.numberOfItems(inSection: section) > 0,
let localizedStringKey = section.titleLocalizedStringKey { let searchScope = section.searchScope {
return NSLocalizedString(localizedStringKey, comment: "") return searchScope.title(statusWord: viewModel.identityContext.appPreferences.statusWord)
} }
return nil return nil

View file

@ -6,7 +6,7 @@ import ViewModels
extension CollectionSection { extension CollectionSection {
struct Identifier: Hashable { struct Identifier: Hashable {
let index: Int let index: Int
let titleLocalizedStringKey: String? let searchScope: SearchScope?
} }
} }
@ -17,7 +17,7 @@ extension Array where Element == CollectionSection {
for (index, section) in enumerated() { for (index, section) in enumerated() {
let identifier = CollectionSection.Identifier( let identifier = CollectionSection.Identifier(
index: index, index: index,
titleLocalizedStringKey: section.titleLocalizedStringKey) searchScope: section.searchScope)
snapshot.appendSections([identifier]) snapshot.appendSections([identifier])
snapshot.appendItems(section.items, toSection: identifier) snapshot.appendItems(section.items, toSection: identifier)
} }

View file

@ -4,12 +4,22 @@ import Foundation
import ViewModels import ViewModels
extension ProfileCollection { extension ProfileCollection {
var title: String { func title(statusWord: AppPreferences.StatusWord) -> String {
switch self { switch self {
case .statuses: case .statuses:
return NSLocalizedString("account.statuses", comment: "") switch statusWord {
case .toot:
return NSLocalizedString("account.statuses.toot", comment: "")
case .post:
return NSLocalizedString("account.statuses.post", comment: "")
}
case .statusesAndReplies: case .statusesAndReplies:
return NSLocalizedString("account.statuses-and-replies", comment: "") switch statusWord {
case .toot:
return NSLocalizedString("account.statuses-and-replies.toot", comment: "")
default:
return NSLocalizedString("account.statuses-and-replies.post", comment: "")
}
case .media: case .media:
return NSLocalizedString("account.media", comment: "") return NSLocalizedString("account.media", comment: "")
} }

View file

@ -4,27 +4,38 @@ import Foundation
import ViewModels import ViewModels
extension SearchScope { extension SearchScope {
var title: String { func title(statusWord: AppPreferences.StatusWord) -> String {
switch self { switch self {
case .all: case .all:
return NSLocalizedString("search.scope.all", comment: "") return NSLocalizedString("search.scope.all", comment: "")
case .accounts: case .accounts:
return NSLocalizedString("search.scope.accounts", comment: "") return NSLocalizedString("search.scope.accounts", comment: "")
case .statuses: case .statuses:
return NSLocalizedString("search.scope.statuses", comment: "") switch statusWord {
case .toot:
return NSLocalizedString("search.scope.statuses.toot", comment: "")
case .post:
return NSLocalizedString("search.scope.statuses.post", comment: "")
}
case .tags: case .tags:
return NSLocalizedString("search.scope.tags", comment: "") return NSLocalizedString("search.scope.tags", comment: "")
} }
} }
var moreDescription: String? { func moreDescription(statusWord: AppPreferences.StatusWord) -> String? {
switch self { switch self {
case .all: case .all:
return nil return nil
case .accounts: case .accounts:
return NSLocalizedString("more-results.accounts", comment: "") return NSLocalizedString("more-results.accounts", comment: "")
case .statuses: case .statuses:
return NSLocalizedString("more-results.statuses", comment: "") switch statusWord {
case .toot:
return NSLocalizedString("more-results.statuses.toot", comment: "")
case .post:
return NSLocalizedString("more-results.statuses.post", comment: "")
}
case .tags: case .tags:
return NSLocalizedString("more-results.tags", comment: "") return NSLocalizedString("more-results.tags", comment: "")
} }

View file

@ -17,8 +17,10 @@
"account.hide-reblogs" = "Hide boosts"; "account.hide-reblogs" = "Hide boosts";
"account.mute" = "Mute"; "account.mute" = "Mute";
"account.request" = "Request"; "account.request" = "Request";
"account.statuses" = "Posts"; "account.statuses.post" = "Posts";
"account.statuses-and-replies" = "Posts & Replies"; "account.statuses.toot" = "Toots";
"account.statuses-and-replies.post" = "Posts & Replies";
"account.statuses-and-replies.toot" = "Toots & Replies";
"account.media" = "Media"; "account.media" = "Media";
"account.show-reblogs" = "Show boosts"; "account.show-reblogs" = "Show boosts";
"account.unavailable" = "Profile unavailable"; "account.unavailable" = "Profile unavailable";
@ -129,12 +131,9 @@
"preferences.media.autoplay.always" = "Always"; "preferences.media.autoplay.always" = "Always";
"preferences.media.autoplay.wifi" = "On Wi-Fi"; "preferences.media.autoplay.wifi" = "On Wi-Fi";
"preferences.media.autoplay.never" = "Never"; "preferences.media.autoplay.never" = "Never";
"preferences.posting-reading" = "Posting and Reading";
"preferences.posting" = "Posting";
"preferences.use-preferences-from-server" = "Use preferences from server"; "preferences.use-preferences-from-server" = "Use preferences from server";
"preferences.posting-default-visiblility" = "Default visibility"; "preferences.posting-default-visiblility" = "Default visibility";
"preferences.posting-default-sensitive" = "Mark content sensitive by default"; "preferences.posting-default-sensitive" = "Mark content sensitive by default";
"preferences.reading" = "Reading";
"preferences.reading-expand-media" = "Expand media"; "preferences.reading-expand-media" = "Expand media";
"preferences.expand-media.default" = "Hide sensitive"; "preferences.expand-media.default" = "Hide sensitive";
"preferences.expand-media.show-all" = "Show all"; "preferences.expand-media.show-all" = "Show all";
@ -156,6 +155,7 @@
"preferences.position.sync-position" = "Sync position with web and other devices"; "preferences.position.sync-position" = "Sync position with web and other devices";
"preferences.position.newest" = "Load newest"; "preferences.position.newest" = "Load newest";
"preferences.show-reblog-and-favorite-counts" = "Show reblog and favorite counts"; "preferences.show-reblog-and-favorite-counts" = "Show reblog and favorite counts";
"preferences.status-word" = "Status word";
"filters.active" = "Active"; "filters.active" = "Active";
"filters.expired" = "Expired"; "filters.expired" = "Expired";
"filter.add-new" = "Add New Filter"; "filter.add-new" = "Add New Filter";
@ -165,7 +165,7 @@
"filter.expire-after" = "Expire after"; "filter.expire-after" = "Expire after";
"filter.contexts" = "Filter contexts"; "filter.contexts" = "Filter contexts";
"filter.irreversible" = "Drop instead of hide"; "filter.irreversible" = "Drop instead of hide";
"filter.irreversible-explanation" = "Filtered posts will disappear irreversibly, even if filter is later removed"; "filter.irreversible-explanation" = "Filtered statuses will disappear irreversibly, even if filter is later removed";
"filter.whole-word" = "Whole word"; "filter.whole-word" = "Whole word";
"filter.whole-word-explanation" = "When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word"; "filter.whole-word-explanation" = "When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word";
"filter.save-changes" = "Save Changes"; "filter.save-changes" = "Save Changes";
@ -176,7 +176,8 @@
"filter.context.account" = "Profiles"; "filter.context.account" = "Profiles";
"filter.context.unknown" = "Unknown context"; "filter.context.unknown" = "Unknown context";
"more-results.accounts" = "More people"; "more-results.accounts" = "More people";
"more-results.statuses" = "More posts"; "more-results.statuses.post" = "More posts";
"more-results.statuses.toot" = "More toots";
"more-results.tags" = "More hashtags"; "more-results.tags" = "More hashtags";
"notifications" = "Notifications"; "notifications" = "Notifications";
"notifications.reblogged-your-status" = "%@ boosted your status"; "notifications.reblogged-your-status" = "%@ boosted your status";
@ -194,18 +195,22 @@
"report.forward-%@" = "Forward report to %@"; "report.forward-%@" = "Forward report to %@";
"search.scope.all" = "All"; "search.scope.all" = "All";
"search.scope.accounts" = "People"; "search.scope.accounts" = "People";
"search.scope.statuses" = "Posts"; "search.scope.statuses.post" = "Posts";
"search.scope.statuses.toot" = "Toots";
"search.scope.tags" = "Hashtags"; "search.scope.tags" = "Hashtags";
"share-extension-error.no-account-found" = "No account found"; "share-extension-error.no-account-found" = "No account found";
"status.bookmark" = "Bookmark"; "status.bookmark" = "Bookmark";
"status.content-warning-abbreviation" = "CW"; "status.content-warning-abbreviation" = "CW";
"status.delete" = "Delete"; "status.delete" = "Delete";
"status.delete.confirm" = "Are you sure you want to delete this post?"; "status.delete.confirm.post" = "Are you sure you want to delete this post?";
"status.delete.confirm.toot" = "Are you sure you want to delete this toot?";
"status.delete-and-redraft" = "Delete & re-draft"; "status.delete-and-redraft" = "Delete & re-draft";
"status.delete-and-redraft.confirm" = "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned."; "status.delete-and-redraft.confirm.post" = "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.";
"status.delete-and-redraft.confirm.toot" = "Are you sure you want to delete this toot and re-draft it? Favorites and boosts will be lost, and replies to the original toot will be orphaned.";
"status.mute" = "Mute conversation"; "status.mute" = "Mute conversation";
"status.pin" = "Pin on profile"; "status.pin" = "Pin on profile";
"status.pinned-post" = "Pinned post"; "status.pinned.post" = "Pinned post";
"status.pinned.toot" = "Pinned toot";
"status.poll.vote" = "Vote"; "status.poll.vote" = "Vote";
"status.poll.time-left" = "%@ left"; "status.poll.time-left" = "%@ left";
"status.poll.refresh" = "Refresh"; "status.poll.refresh" = "Refresh";
@ -229,3 +234,4 @@
"timelines.home" = "Home"; "timelines.home" = "Home";
"timelines.local" = "Local"; "timelines.local" = "Local";
"timelines.federated" = "Federated"; "timelines.federated" = "Federated";
"toot" = "Toot";

View file

@ -15,6 +15,13 @@ public struct AppPreferences {
} }
public extension AppPreferences { public extension AppPreferences {
enum StatusWord: String, CaseIterable, Identifiable {
case toot
case post
public var id: String { rawValue }
}
enum AnimateAvatars: String, CaseIterable, Identifiable { enum AnimateAvatars: String, CaseIterable, Identifiable {
case everywhere case everywhere
case profiles case profiles
@ -44,6 +51,18 @@ public extension AppPreferences {
set { self[.useSystemReduceMotionForMedia] = newValue } set { self[.useSystemReduceMotionForMedia] = newValue }
} }
var statusWord: StatusWord {
get {
if let rawValue = self[.statusWord] as String?,
let value = StatusWord(rawValue: rawValue) {
return value
}
return .toot
}
set { self[.statusWord] = newValue.rawValue }
}
var animateAvatars: AnimateAvatars { var animateAvatars: AnimateAvatars {
get { get {
if let rawValue = self[.animateAvatars] as String?, if let rawValue = self[.animateAvatars] as String?,
@ -140,23 +159,9 @@ public extension AppPreferences {
} }
} }
extension AppPreferences {
var updatedInstanceFilter: BloomFilter<String>? {
guard let data = self[.updatedFilter] as Data? else {
return nil
}
return try? JSONDecoder().decode(BloomFilter<String>.self, from: data)
}
func updateInstanceFilter( _ filter: BloomFilter<String>) {
userDefaults.set(try? JSONEncoder().encode(filter), forKey: Item.updatedFilter.rawValue)
}
}
private extension AppPreferences { private extension AppPreferences {
enum Item: String { enum Item: String {
case updatedFilter case statusWord
case useSystemReduceMotionForMedia case useSystemReduceMotionForMedia
case animateAvatars case animateAvatars
case animateHeaders case animateHeaders

View file

@ -53,10 +53,16 @@ final class ExploreViewController: UICollectionViewController {
let searchController = UISearchController(searchResultsController: searchResultsController) let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchBar.scopeButtonTitles = SearchScope.allCases.map(\.title)
searchController.searchResultsUpdater = self searchController.searchResultsUpdater = self
navigationItem.searchController = searchController navigationItem.searchController = searchController
viewModel.identityContext.$appPreferences.sink { appPreferences in
searchController.searchBar.scopeButtonTitles = SearchScope.allCases.map {
$0.title(statusWord: appPreferences.statusWord)
}
}
.store(in: &cancellables)
viewModel.events.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables) viewModel.events.sink { [weak self] in self?.handle(event: $0) }.store(in: &cancellables)
viewModel.$loading.sink { [weak self] in viewModel.$loading.sink { [weak self] in

View file

@ -15,7 +15,7 @@ final class NewStatusViewController: UIViewController {
private let stackView = UIStackView() private let stackView = UIStackView()
private let activityIndicatorView = UIActivityIndicatorView(style: .large) private let activityIndicatorView = UIActivityIndicatorView(style: .large)
private let postButton = UIBarButtonItem( private let postButton = UIBarButtonItem(
title: NSLocalizedString("post", comment: ""), title: nil,
style: .done, style: .done,
target: nil, target: nil,
action: nil) action: nil)
@ -40,6 +40,7 @@ final class NewStatusViewController: UIViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
// swiftlint:disable:next function_body_length
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -76,7 +77,16 @@ final class NewStatusViewController: UIViewController {
primaryAction: UIAction { [weak self] _ in self?.dismiss() }) primaryAction: UIAction { [weak self] _ in self?.dismiss() })
navigationItem.rightBarButtonItem = postButton navigationItem.rightBarButtonItem = postButton
postButton.primaryAction = UIAction(title: NSLocalizedString("post", comment: "")) { [weak self] _ in let postActionTitle: String
switch viewModel.identityContext.appPreferences.statusWord {
case .toot:
postActionTitle = NSLocalizedString("toot", comment: "")
case .post:
postActionTitle = NSLocalizedString("post", comment: "")
}
postButton.primaryAction = UIAction(title: postActionTitle) { [weak self] _ in
self?.viewModel.post() self?.viewModel.post()
} }

View file

@ -25,10 +25,7 @@ final class ProfileViewController: TableViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
// Initial size is to avoid unsatisfiable constraint warning let accountHeaderView = AccountHeaderView(viewModel: viewModel)
let accountHeaderView = AccountHeaderView(frame: .init(origin: .zero, size: .init(width: 300, height: 300)))
accountHeaderView.viewModel = viewModel
viewModel.$accountViewModel viewModel.$accountViewModel
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)

View file

@ -24,7 +24,7 @@ class TableViewController: UITableViewController {
private weak var parentNavigationController: UINavigationController? private weak var parentNavigationController: UINavigationController?
private lazy var dataSource: TableViewDataSource = { private lazy var dataSource: TableViewDataSource = {
.init(tableView: tableView, viewModelProvider: viewModel.viewModel(indexPath:)) .init(tableView: tableView, viewModel: viewModel)
}() }()
init(viewModel: CollectionViewModel, init(viewModel: CollectionViewModel,
@ -441,11 +441,23 @@ private extension TableViewController {
} }
func confirmDelete(statusViewModel: StatusViewModel, redraft: Bool) { func confirmDelete(statusViewModel: StatusViewModel, redraft: Bool) {
let deleteAndRedraftConfirmMessage: String
let deleteConfirmMessage: String
switch viewModel.identityContext.appPreferences.statusWord {
case .toot:
deleteAndRedraftConfirmMessage = NSLocalizedString("status.delete-and-redraft.confirm.toot", comment: "")
deleteConfirmMessage = NSLocalizedString("status.delete.confirm.toot", comment: "")
case .post:
deleteAndRedraftConfirmMessage = NSLocalizedString("status.delete-and-redraft.confirm.post", comment: "")
deleteConfirmMessage = NSLocalizedString("status.delete.confirm.post", comment: "")
}
let alertController = UIAlertController( let alertController = UIAlertController(
title: nil, title: nil,
message: redraft message: redraft
? NSLocalizedString("status.delete-and-redraft.confirm", comment: "") ? deleteAndRedraftConfirmMessage
: NSLocalizedString("status.delete.confirm", comment: ""), : deleteConfirmMessage,
preferredStyle: .alert) preferredStyle: .alert)
let deleteAction = UIAlertAction( let deleteAction = UIAlertAction(

View file

@ -62,6 +62,12 @@ struct PreferencesView: View {
.disabled(viewModel.preferences.useServerPostingReadingPreferences) .disabled(viewModel.preferences.useServerPostingReadingPreferences)
} }
Section(header: Text("preferences.app")) { Section(header: Text("preferences.app")) {
Picker("preferences.status-word",
selection: $identityContext.appPreferences.statusWord) {
ForEach(AppPreferences.StatusWord.allCases) { option in
Text(option.localizedStringKey).tag(option)
}
}
if accessibilityReduceMotion { if accessibilityReduceMotion {
Toggle("preferences.media.use-system-reduce-motion", Toggle("preferences.media.use-system-reduce-motion",
isOn: $identityContext.appPreferences.useSystemReduceMotionForMedia) isOn: $identityContext.appPreferences.useSystemReduceMotionForMedia)
@ -119,6 +125,17 @@ private extension PreferencesView {
} }
} }
extension AppPreferences.StatusWord {
var localizedStringKey: LocalizedStringKey {
switch self {
case .toot:
return "toot"
case .post:
return "post"
}
}
}
extension AppPreferences.AnimateAvatars { extension AppPreferences.AnimateAvatars {
var localizedStringKey: LocalizedStringKey { var localizedStringKey: LocalizedStringKey {
switch self { switch self {

View file

@ -26,9 +26,9 @@ final class AccountHeaderView: UIView {
let segmentedControl = UISegmentedControl() let segmentedControl = UISegmentedControl()
let unavailableLabel = UILabel() let unavailableLabel = UILabel()
var viewModel: ProfileViewModel? { var viewModel: ProfileViewModel {
didSet { didSet {
if let accountViewModel = viewModel?.accountViewModel { if let accountViewModel = viewModel.accountViewModel {
headerImageView.kf.setImage(with: accountViewModel.headerURL) { [weak self] in headerImageView.kf.setImage(with: accountViewModel.headerURL) { [weak self] in
if case let .success(result) = $0, result.image.size != Self.missingHeaderImageSize { if case let .success(result) = $0, result.image.size != Self.missingHeaderImageSize {
self?.headerButton.isEnabled = true self?.headerButton.isEnabled = true
@ -127,8 +127,11 @@ final class AccountHeaderView: UIView {
} }
} }
override init(frame: CGRect) { init(viewModel: ProfileViewModel) {
super.init(frame: frame) self.viewModel = viewModel
// Initial size is to avoid unsatisfiable constraint warning
super.init(frame: .init(origin: .zero, size: .init(width: 300, height: 300)))
initialSetup() initialSetup()
} }
@ -158,7 +161,7 @@ extension AccountHeaderView: UITextViewDelegate {
interaction: UITextItemInteraction) -> Bool { interaction: UITextItemInteraction) -> Bool {
switch interaction { switch interaction {
case .invokeDefaultAction: case .invokeDefaultAction:
viewModel?.accountViewModel?.urlSelected(URL) viewModel.accountViewModel?.urlSelected(URL)
return false return false
case .preview: return false case .preview: return false
case .presentActions: return false case .presentActions: return false
@ -189,7 +192,7 @@ private extension AccountHeaderView {
headerButton.translatesAutoresizingMaskIntoConstraints = false headerButton.translatesAutoresizingMaskIntoConstraints = false
headerButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted) headerButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
headerButton.addAction(UIAction { [weak self] _ in self?.viewModel?.presentHeader() }, for: .touchUpInside) headerButton.addAction(UIAction { [weak self] _ in self?.viewModel.presentHeader() }, for: .touchUpInside)
headerButton.isEnabled = false headerButton.isEnabled = false
let avatarBackgroundViewDimension = Self.avatarDimension + .compactSpacing * 2 let avatarBackgroundViewDimension = Self.avatarDimension + .compactSpacing * 2
@ -210,7 +213,7 @@ private extension AccountHeaderView {
avatarButton.translatesAutoresizingMaskIntoConstraints = false avatarButton.translatesAutoresizingMaskIntoConstraints = false
avatarButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted) avatarButton.setBackgroundImage(.highlightedButtonBackground, for: .highlighted)
avatarButton.addAction(UIAction { [weak self] _ in self?.viewModel?.presentAvatar() }, for: .touchUpInside) avatarButton.addAction(UIAction { [weak self] _ in self?.viewModel.presentAvatar() }, for: .touchUpInside)
addSubview(relationshipButtonsStackView) addSubview(relationshipButtonsStackView)
relationshipButtonsStackView.translatesAutoresizingMaskIntoConstraints = false relationshipButtonsStackView.translatesAutoresizingMaskIntoConstraints = false
@ -230,7 +233,7 @@ private extension AccountHeaderView {
withConfiguration: UIImage.SymbolConfiguration(scale: .small)), withConfiguration: UIImage.SymbolConfiguration(scale: .small)),
for: .normal) for: .normal)
followButton.addAction( followButton.addAction(
UIAction { [weak self] _ in self?.viewModel?.accountViewModel?.follow() }, UIAction { [weak self] _ in self?.viewModel.accountViewModel?.follow() },
for: .touchUpInside) for: .touchUpInside)
unfollowButton.setImage( unfollowButton.setImage(
@ -241,7 +244,7 @@ private extension AccountHeaderView {
unfollowButton.setTitle(NSLocalizedString("account.following", comment: ""), for: .normal) unfollowButton.setTitle(NSLocalizedString("account.following", comment: ""), for: .normal)
unfollowButton.showsMenuAsPrimaryAction = true unfollowButton.showsMenuAsPrimaryAction = true
unfollowButton.menu = UIMenu(children: [UIDeferredMenuElement { [weak self] completion in unfollowButton.menu = UIMenu(children: [UIDeferredMenuElement { [weak self] completion in
guard let accountViewModel = self?.viewModel?.accountViewModel else { return } guard let accountViewModel = self?.viewModel.accountViewModel else { return }
let unfollowAction = UIAction( let unfollowAction = UIAction(
title: NSLocalizedString("account.unfollow", comment: ""), title: NSLocalizedString("account.unfollow", comment: ""),
@ -299,20 +302,22 @@ private extension AccountHeaderView {
followStackView.distribution = .fillEqually followStackView.distribution = .fillEqually
followingButton.addAction( followingButton.addAction(
UIAction { [weak self] _ in self?.viewModel?.accountViewModel?.followingSelected() }, UIAction { [weak self] _ in self?.viewModel.accountViewModel?.followingSelected() },
for: .touchUpInside) for: .touchUpInside)
followStackView.addArrangedSubview(followingButton) followStackView.addArrangedSubview(followingButton)
followersButton.addAction( followersButton.addAction(
UIAction { [weak self] _ in self?.viewModel?.accountViewModel?.followersSelected() }, UIAction { [weak self] _ in self?.viewModel.accountViewModel?.followersSelected() },
for: .touchUpInside) for: .touchUpInside)
followStackView.addArrangedSubview(followersButton) followStackView.addArrangedSubview(followersButton)
let statusWord = viewModel.identityContext.appPreferences.statusWord
for (index, collection) in ProfileCollection.allCases.enumerated() { for (index, collection) in ProfileCollection.allCases.enumerated() {
segmentedControl.insertSegment( segmentedControl.insertSegment(
action: UIAction(title: collection.title) { [weak self] _ in action: UIAction(title: collection.title(statusWord: statusWord)) { [weak self] _ in
self?.viewModel?.collection = collection self?.viewModel.collection = collection
self?.viewModel?.request(maxId: nil, minId: nil, search: nil) self?.viewModel.request(maxId: nil, minId: nil, search: nil)
}, },
at: index, at: index,
animated: false) animated: false)

View file

@ -354,7 +354,16 @@ private extension StatusView {
infoLabel.isHidden = false infoLabel.isHidden = false
infoIcon.isHidden = false infoIcon.isHidden = false
} else if viewModel.configuration.isPinned { } else if viewModel.configuration.isPinned {
infoLabel.text = NSLocalizedString("status.pinned-post", comment: "") let pinnedText: String
switch viewModel.identityContext.appPreferences.statusWord {
case .toot:
pinnedText = NSLocalizedString("status.pinned.toot", comment: "")
case .post:
pinnedText = NSLocalizedString("status.pinned.post", comment: "")
}
infoLabel.text = pinnedText
infoIcon.image = UIImage( infoIcon.image = UIImage(
systemName: "pin", systemName: "pin",
withConfiguration: UIImage.SymbolConfiguration(scale: .small)) withConfiguration: UIImage.SymbolConfiguration(scale: .small))