mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Status word preference
This commit is contained in:
parent
f5aacf7624
commit
5f03ce7d8b
15 changed files with 163 additions and 71 deletions
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
Loading…
Reference in a new issue