mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Rename Feed to FeedProtocol. (This is part one of renaming WebFeed to Feed.)
This commit is contained in:
@@ -200,7 +200,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
return nil
|
||||
}
|
||||
|
||||
public func existingFeed(with feedID: FeedIdentifier) -> Feed? {
|
||||
public func existingFeed(with feedID: FeedIdentifier) -> FeedProtocol? {
|
||||
switch feedID {
|
||||
case .folder(let accountID, let folderName):
|
||||
if let account = existingAccount(with: accountID) {
|
||||
|
||||
@@ -15,14 +15,14 @@ public enum ReadFilterType {
|
||||
case alwaysRead
|
||||
}
|
||||
|
||||
public protocol Feed: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider {
|
||||
public protocol FeedProtocol: FeedIdentifiable, ArticleFetcher, DisplayNameProvider, UnreadCountProvider {
|
||||
|
||||
var account: Account? { get }
|
||||
var defaultReadFilterType: ReadFilterType { get }
|
||||
|
||||
}
|
||||
|
||||
public extension Feed {
|
||||
public extension FeedProtocol {
|
||||
|
||||
func readFiltered(readFilterEnabledTable: [FeedIdentifier: Bool]) -> Bool {
|
||||
guard defaultReadFilterType != .alwaysRead else {
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import Articles
|
||||
import RSCore
|
||||
|
||||
public final class Folder: Feed, Renamable, Container, Hashable {
|
||||
public final class Folder: FeedProtocol, Renamable, Container, Hashable {
|
||||
|
||||
public var defaultReadFilterType: ReadFilterType {
|
||||
return .read
|
||||
|
||||
@@ -11,7 +11,7 @@ import RSCore
|
||||
import RSWeb
|
||||
import Articles
|
||||
|
||||
public final class WebFeed: Feed, Renamable, Hashable, ObservableObject {
|
||||
public final class WebFeed: FeedProtocol, Renamable, Hashable, ObservableObject {
|
||||
|
||||
public var defaultReadFilterType: ReadFilterType {
|
||||
return .none
|
||||
|
||||
@@ -22,7 +22,7 @@ import Account
|
||||
alert.messageText = NSLocalizedString("alert.title.delete-folder", comment: "Delete Folder")
|
||||
let localizedInformativeText = NSLocalizedString("alert.message.delete-folder.%@", comment: "Are you sure you want to delete the “%@” folder?")
|
||||
alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, folder.nameForDisplay) as String
|
||||
} else if let feed = nodes.first?.representedObject as? Feed {
|
||||
} else if let feed = nodes.first?.representedObject as? FeedProtocol {
|
||||
alert.messageText = NSLocalizedString("alert.title.delete-feed", comment: "Delete Feed")
|
||||
let localizedInformativeText = NSLocalizedString("alert.message.delete-feed.%@", comment: "Are you sure you want to delete the “%@” feed?")
|
||||
alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, feed.nameForDisplay) as String
|
||||
|
||||
@@ -455,7 +455,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
|
||||
// MARK: - API
|
||||
|
||||
func selectFeed(_ feed: Feed) {
|
||||
func selectFeed(_ feed: FeedProtocol) {
|
||||
if isReadFiltered, let feedID = feed.feedID {
|
||||
self.treeControllerDelegate.addFilterException(feedID)
|
||||
|
||||
@@ -476,7 +476,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
func deepLinkRevealAndSelect(for userInfo: [AnyHashable : Any]) {
|
||||
guard let accountNode = findAccountNode(userInfo),
|
||||
let feedNode = findFeedNode(userInfo, beginningAt: accountNode),
|
||||
let feed = feedNode.representedObject as? Feed else {
|
||||
let feed = feedNode.representedObject as? FeedProtocol else {
|
||||
return
|
||||
}
|
||||
selectFeed(feed)
|
||||
@@ -521,8 +521,8 @@ private extension SidebarViewController {
|
||||
return [Node]()
|
||||
}
|
||||
|
||||
var selectedFeeds: [Feed] {
|
||||
selectedNodes.compactMap { $0.representedObject as? Feed }
|
||||
var selectedFeeds: [FeedProtocol] {
|
||||
selectedNodes.compactMap { $0.representedObject as? FeedProtocol }
|
||||
}
|
||||
|
||||
var singleSelectedNode: Node? {
|
||||
@@ -543,7 +543,7 @@ private extension SidebarViewController {
|
||||
selectedFeeds.forEach { addToFilterExeptionsIfNecessary($0) }
|
||||
}
|
||||
|
||||
func addToFilterExeptionsIfNecessary(_ feed: Feed?) {
|
||||
func addToFilterExeptionsIfNecessary(_ feed: FeedProtocol?) {
|
||||
if isReadFiltered, let feedID = feed?.feedID {
|
||||
if feed is PseudoFeed {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
@@ -560,7 +560,7 @@ private extension SidebarViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func addParentFolderToFilterExceptions(_ feed: Feed) {
|
||||
func addParentFolderToFilterExceptions(_ feed: FeedProtocol) {
|
||||
guard let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject),
|
||||
let folder = node.parent?.representedObject as? Folder,
|
||||
let folderFeedID = folder.feedID else {
|
||||
@@ -621,7 +621,7 @@ private extension SidebarViewController {
|
||||
}
|
||||
|
||||
func addTreeControllerToFilterExceptionsVisitor(node: Node) {
|
||||
if let feed = node.representedObject as? Feed, let feedID = feed.feedID {
|
||||
if let feed = node.representedObject as? FeedProtocol, let feedID = feed.feedID {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
|
||||
private var readFilterEnabledTable = [FeedIdentifier: Bool]()
|
||||
var isReadFiltered: Bool? {
|
||||
guard representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? Feed else {
|
||||
guard representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? FeedProtocol else {
|
||||
return nil
|
||||
}
|
||||
guard timelineFeed.defaultReadFilterType != .alwaysRead else {
|
||||
@@ -45,7 +45,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
var isCleanUpAvailable: Bool {
|
||||
let isEligibleForCleanUp: Bool?
|
||||
|
||||
if representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? Feed, timelineFeed.defaultReadFilterType == .alwaysRead {
|
||||
if representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? FeedProtocol, timelineFeed.defaultReadFilterType == .alwaysRead {
|
||||
isEligibleForCleanUp = true
|
||||
} else {
|
||||
isEligibleForCleanUp = isReadFiltered
|
||||
@@ -281,7 +281,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
func toggleReadFilter() {
|
||||
guard let filter = isReadFiltered, let feedID = (representedObjects?.first as? Feed)?.feedID else { return }
|
||||
guard let filter = isReadFiltered, let feedID = (representedObjects?.first as? FeedProtocol)?.feedID else { return }
|
||||
readFilterEnabledTable[feedID] = !filter
|
||||
delegate?.timelineInvalidatedRestorationState(self)
|
||||
fetchAndReplacePreservingSelection()
|
||||
@@ -1242,7 +1242,7 @@ private extension TimelineViewController {
|
||||
|
||||
var fetchedArticles = Set<Article>()
|
||||
for fetchers in fetchers {
|
||||
if (fetchers as? Feed)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true {
|
||||
if (fetchers as? FeedProtocol)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true {
|
||||
if let articles = try? fetchers.fetchUnreadArticles() {
|
||||
fetchedArticles.formUnion(articles)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class ActivityManager {
|
||||
invalidateNextUnread()
|
||||
}
|
||||
|
||||
func selecting(feed: Feed) {
|
||||
func selecting(feed: FeedProtocol) {
|
||||
invalidateCurrentActivities()
|
||||
|
||||
selectingActivity = makeSelectFeedActivity(feed: feed)
|
||||
@@ -87,7 +87,7 @@ class ActivityManager {
|
||||
nextUnreadActivity = nil
|
||||
}
|
||||
|
||||
func reading(feed: Feed?, article: Article?) {
|
||||
func reading(feed: FeedProtocol?, article: Article?) {
|
||||
invalidateReading()
|
||||
invalidateNextUnread()
|
||||
|
||||
@@ -162,7 +162,7 @@ class ActivityManager {
|
||||
|
||||
private extension ActivityManager {
|
||||
|
||||
func makeSelectFeedActivity(feed: Feed) -> NSUserActivity {
|
||||
func makeSelectFeedActivity(feed: FeedProtocol) -> NSUserActivity {
|
||||
let activity = NSUserActivity(activityType: ActivityType.selectFeed.rawValue)
|
||||
|
||||
let localizedText = NSLocalizedString("activity.title.see-article-in.folder.%@", comment: "See articles in “%@”")
|
||||
@@ -187,7 +187,7 @@ private extension ActivityManager {
|
||||
return activity
|
||||
}
|
||||
|
||||
func makeReadArticleActivity(feed: Feed?, article: Article) -> NSUserActivity {
|
||||
func makeReadArticleActivity(feed: FeedProtocol?, article: Article) -> NSUserActivity {
|
||||
let activity = NSUserActivity(activityType: ActivityType.readArticle.rawValue)
|
||||
activity.title = ArticleStringFormatter.truncatedTitle(article)
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class IconImageCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForFeed(_ feed: Feed) -> IconImage? {
|
||||
func imageForFeed(_ feed: FeedProtocol) -> IconImage? {
|
||||
guard let feedID = feed.feedID else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
protocol PseudoFeed: AnyObject, Feed, SmallIconProvider, PasteboardWriterOwner {
|
||||
protocol PseudoFeed: AnyObject, FeedProtocol, SmallIconProvider, PasteboardWriterOwner {
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
protocol PseudoFeed: AnyObject, Feed, SmallIconProvider {
|
||||
protocol PseudoFeed: AnyObject, FeedProtocol, SmallIconProvider {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ final class SmartFeedsController: DisplayNameProvider, ContainerIdentifiable {
|
||||
public static let shared = SmartFeedsController()
|
||||
let nameForDisplay = NSLocalizedString("smartfeeds.title", comment: "Smart Feeds group title")
|
||||
|
||||
var smartFeeds = [Feed]()
|
||||
var smartFeeds = [FeedProtocol]()
|
||||
let todayFeed = SmartFeed(delegate: TodayFeedDelegate())
|
||||
let unreadFeed = UnreadFeed()
|
||||
let starredFeed = SmartFeed(delegate: StarredFeedDelegate())
|
||||
|
||||
@@ -81,7 +81,7 @@ final class FetchRequestOperation {
|
||||
}
|
||||
|
||||
for fetcher in fetchers {
|
||||
if (fetcher as? Feed)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true {
|
||||
if (fetcher as? FeedProtocol)?.readFiltered(readFilterEnabledTable: readFilterEnabledTable) ?? true {
|
||||
fetcher.fetchUnreadArticlesAsync { articleSetResult in
|
||||
let articles = (try? articleSetResult.get()) ?? Set<Article>()
|
||||
process(articles)
|
||||
|
||||
@@ -51,7 +51,7 @@ extension MasterFeedViewController: UITableViewDropDelegate {
|
||||
}
|
||||
|
||||
guard let correctDestNode = coordinator.nodeFor(correctedIndexPath),
|
||||
let correctDestFeed = correctDestNode.representedObject as? Feed,
|
||||
let correctDestFeed = correctDestNode.representedObject as? FeedProtocol,
|
||||
let correctDestAccount = correctDestFeed.account else {
|
||||
return UITableViewDropProposal(operation: .forbidden)
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner, Ma
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? FeedProtocol else {
|
||||
return nil
|
||||
}
|
||||
if feed is WebFeed {
|
||||
@@ -794,7 +794,7 @@ private extension MasterFeedViewController {
|
||||
cell.isDisclosureAvailable = false
|
||||
}
|
||||
|
||||
if let feed = node.representedObject as? Feed {
|
||||
if let feed = node.representedObject as? FeedProtocol {
|
||||
cell.name = feed.nameForDisplay
|
||||
cell.unreadCount = feed.unreadCount
|
||||
cell.itemIsInFolder = false
|
||||
@@ -821,7 +821,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func configureIcon(_ cell: MasterFeedTableViewCell, _ indexPath: IndexPath) {
|
||||
guard let node = coordinator.nodeFor(indexPath), let feed = node.representedObject as? Feed, let feedID = feed.feedID else {
|
||||
guard let node = coordinator.nodeFor(indexPath), let feed = node.representedObject as? FeedProtocol, let feedID = feed.feedID else {
|
||||
return
|
||||
}
|
||||
cell.iconImage = IconImageCache.shared.imageFor(feedID)
|
||||
@@ -841,8 +841,8 @@ private extension MasterFeedViewController {
|
||||
func applyToCellsForRepresentedObject(_ representedObject: AnyObject, _ completion: (MasterFeedTableViewCell, IndexPath) -> Void) {
|
||||
applyToAvailableCells { (cell, indexPath) in
|
||||
if let node = coordinator.nodeFor(indexPath),
|
||||
let representedFeed = representedObject as? Feed,
|
||||
let candidate = node.representedObject as? Feed,
|
||||
let representedFeed = representedObject as? FeedProtocol,
|
||||
let candidate = node.representedObject as? FeedProtocol,
|
||||
representedFeed.feedID == candidate.feedID {
|
||||
completion(cell, indexPath)
|
||||
}
|
||||
@@ -1169,7 +1169,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func markAllAsReadAction(indexPath: IndexPath) -> UIAction? {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? FeedProtocol,
|
||||
let contentView = self.tableView.cellForRow(at: indexPath)?.contentView,
|
||||
feed.unreadCount > 0 else {
|
||||
return nil
|
||||
@@ -1188,7 +1188,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func catchUpActionMenu(indexPath: IndexPath) -> UIMenu? {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed,
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? FeedProtocol,
|
||||
let contentView = self.tableView.cellForRow(at: indexPath)?.contentView,
|
||||
feed.unreadCount > 0 else {
|
||||
return nil
|
||||
@@ -1396,7 +1396,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
|
||||
func rename(indexPath: IndexPath) {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return }
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? FeedProtocol else { return }
|
||||
|
||||
let formatString = NSLocalizedString("alert.title.rename-feed.%@", comment: "Rename feed. The variable provided is the feed name. In English: Rename “%@”")
|
||||
let title = NSString.localizedStringWithFormat(formatString as NSString, feed.nameForDisplay) as String
|
||||
@@ -1450,7 +1450,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func delete(indexPath: IndexPath) {
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? Feed else { return }
|
||||
guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? FeedProtocol else { return }
|
||||
|
||||
let title: String
|
||||
let message: String
|
||||
|
||||
@@ -42,7 +42,7 @@ struct FeedNode: Hashable {
|
||||
var feedID: FeedIdentifier
|
||||
|
||||
init?(_ node: Node) {
|
||||
guard let feed = node.representedObject as? Feed else { return nil }
|
||||
guard let feed = node.representedObject as? FeedProtocol else { return nil }
|
||||
|
||||
self.node = node
|
||||
self.feedID = feed.feedID!
|
||||
@@ -86,7 +86,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
// Flattened tree structure for the Sidebar
|
||||
private var shadowTable = [(sectionID: String, feedNodes: [FeedNode])]()
|
||||
|
||||
private(set) var preSearchTimelineFeed: Feed?
|
||||
private(set) var preSearchTimelineFeed: FeedProtocol?
|
||||
private var lastSearchString = ""
|
||||
private var lastSearchScope: SearchScope? = nil
|
||||
private var isSearching: Bool = false
|
||||
@@ -167,7 +167,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
}
|
||||
|
||||
private var exceptionArticleFetcher: ArticleFetcher?
|
||||
private(set) var timelineFeed: Feed?
|
||||
private(set) var timelineFeed: FeedProtocol?
|
||||
|
||||
var timelineMiddleIndexPath: IndexPath?
|
||||
|
||||
@@ -620,7 +620,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
|
||||
func nodeFor(feedID: FeedIdentifier) -> Node? {
|
||||
return treeController.rootNode.descendantNode(where: { node in
|
||||
if let feed = node.representedObject as? Feed {
|
||||
if let feed = node.representedObject as? FeedProtocol {
|
||||
return feed.feedID == feedID
|
||||
} else {
|
||||
return false
|
||||
@@ -776,7 +776,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
return indexPathFor(node)
|
||||
}
|
||||
|
||||
func selectFeed(_ feed: Feed?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) {
|
||||
func selectFeed(_ feed: FeedProtocol?, animations: Animations = [], deselectArticle: Bool = true, completion: (() -> Void)? = nil) {
|
||||
let indexPath: IndexPath? = {
|
||||
if let feed = feed, let indexPath = indexPathFor(feed as AnyObject) {
|
||||
return indexPath
|
||||
@@ -800,7 +800,7 @@ final class SceneCoordinator: NSObject, UndoableCommandRunner, Logging {
|
||||
selectArticle(nil)
|
||||
}
|
||||
|
||||
if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? Feed {
|
||||
if let ip = indexPath, let node = nodeFor(ip), let feed = node.representedObject as? FeedProtocol {
|
||||
|
||||
self.activityManager.selecting(feed: feed)
|
||||
self.rootSplitViewController.show(.supplementary)
|
||||
@@ -1476,7 +1476,7 @@ private extension SceneCoordinator {
|
||||
articleDictionaryNeedsUpdate = false
|
||||
}
|
||||
|
||||
func ensureFeedIsAvailableToSelect(_ feed: Feed, completion: @escaping () -> Void) {
|
||||
func ensureFeedIsAvailableToSelect(_ feed: FeedProtocol, completion: @escaping () -> Void) {
|
||||
addToFilterExeptionsIfNecessary(feed)
|
||||
addShadowTableToFilterExceptions()
|
||||
|
||||
@@ -1486,7 +1486,7 @@ private extension SceneCoordinator {
|
||||
})
|
||||
}
|
||||
|
||||
func addToFilterExeptionsIfNecessary(_ feed: Feed?) {
|
||||
func addToFilterExeptionsIfNecessary(_ feed: FeedProtocol?) {
|
||||
if isReadFeedsFiltered, let feedID = feed?.feedID {
|
||||
if feed is SmartFeed {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
@@ -1503,7 +1503,7 @@ private extension SceneCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
func addParentFolderToFilterExceptions(_ feed: Feed) {
|
||||
func addParentFolderToFilterExceptions(_ feed: FeedProtocol) {
|
||||
guard let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject),
|
||||
let folder = node.parent?.representedObject as? Folder,
|
||||
let folderFeedID = folder.feedID else {
|
||||
@@ -1516,7 +1516,7 @@ private extension SceneCoordinator {
|
||||
func addShadowTableToFilterExceptions() {
|
||||
for section in shadowTable {
|
||||
for feedNode in section.feedNodes {
|
||||
if let feed = feedNode.node.representedObject as? Feed, let feedID = feed.feedID {
|
||||
if let feed = feedNode.node.representedObject as? FeedProtocol, let feedID = feed.feedID {
|
||||
treeControllerDelegate.addFilterException(feedID)
|
||||
}
|
||||
}
|
||||
@@ -1648,10 +1648,10 @@ private extension SceneCoordinator {
|
||||
return ShadowTableChanges(deletes: deletes, inserts: inserts, moves: moves, rowChanges: changes)
|
||||
}
|
||||
|
||||
func shadowTableContains(_ feed: Feed) -> Bool {
|
||||
func shadowTableContains(_ feed: FeedProtocol) -> Bool {
|
||||
for section in shadowTable {
|
||||
for feedNode in section.feedNodes {
|
||||
if let nodeFeed = feedNode.node.representedObject as? Feed, nodeFeed.feedID == feed.feedID {
|
||||
if let nodeFeed = feedNode.node.representedObject as? FeedProtocol, nodeFeed.feedID == feed.feedID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1665,7 +1665,7 @@ private extension SceneCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
func setTimelineFeed(_ feed: Feed?, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
func setTimelineFeed(_ feed: FeedProtocol?, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
timelineFeed = feed
|
||||
|
||||
fetchAndReplaceArticlesAsync(animated: animated) {
|
||||
|
||||
Reference in New Issue
Block a user