From 59c6d31ca434ff71bcda4f0b7dfc347ca9206df6 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 13:58:56 +0100 Subject: [PATCH 1/9] [WIP] Remove CoreData for Tags/Accounts in Search (IOS-196) - Add basic, json-based persistence (it's WIP and pragmatic aka dirty, see FileManager+SearchHistory) --- Mastodon.xcodeproj/project.pbxproj | 24 ++++ .../FileManager+SearchHistory.swift | 54 ++++++++ .../Persistence/Model/SearchHistory.swift | 15 +++ .../Provider/DataSourceFacade+Hashtag.swift | 9 +- .../DataSourceFacade+SearchHistory.swift | 125 +++++------------- .../Provider/DataSourceProvider.swift | 9 +- .../SearchResultOverviewCoordinator.swift | 39 +++--- .../SearchDetailViewController.swift | 14 +- .../SearchHistory/SearchHistoryItem.swift | 7 +- .../SearchHistory/SearchHistorySection.swift | 50 ++++--- ...oryViewController+DataSourceProvider.swift | 8 +- .../SearchHistoryViewController.swift | 50 +++---- .../SearchHistoryViewModel+Diffable.swift | 48 +++---- .../SearchHistoryViewModel.swift | 7 +- .../SearchResult/SearchResultItem.swift | 2 +- .../SearchResult/SearchResultSection.swift | 28 ++-- ...ultViewController+DataSourceProvider.swift | 13 +- .../SearchResultViewModel+Diffable.swift | 15 ++- .../SearchResultViewModel+State.swift | 46 +++++-- .../SearchResult/SearchResultViewModel.swift | 10 +- .../Persistence/Persistence.swift | 11 +- .../View/Content/CondensedUserView.swift | 39 ------ 22 files changed, 303 insertions(+), 320 deletions(-) create mode 100644 Mastodon/Persistence/FileManager+SearchHistory.swift create mode 100644 Mastodon/Persistence/Model/SearchHistory.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 2b9d89174..a5fc9be7f 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -161,6 +161,8 @@ D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D886FBD229DF710F00272017 /* WelcomeSeparatorView.swift */; }; D8916DC029211BE500124085 /* ContentSizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8916DBF29211BE500124085 /* ContentSizedTableView.swift */; }; D8A6AB6C291C5136003AB663 /* MastodonLoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8A6AB6B291C5136003AB663 /* MastodonLoginViewController.swift */; }; + D8AC98762B0F61680045EC2B /* FileManager+SearchHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AC98752B0F61680045EC2B /* FileManager+SearchHistory.swift */; }; + D8AC98792B0F622B0045EC2B /* SearchHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8AC98782B0F622B0045EC2B /* SearchHistory.swift */; }; D8B5E4EE2A4EB8930008970C /* NotificationSettingTableViewToggleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4ED2A4EB8920008970C /* NotificationSettingTableViewToggleCell.swift */; }; D8B5E4F02A4EB8A00008970C /* NotificationSettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4EF2A4EB8A00008970C /* NotificationSettingTableViewCell.swift */; }; D8B5E4F22A4EBCF90008970C /* NotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */; }; @@ -832,6 +834,8 @@ D8A6FE6429325F5900666A47 /* StringsConvertor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = StringsConvertor; sourceTree = ""; }; D8A6FE6529325F5900666A47 /* ios-infoPlist.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "ios-infoPlist.json"; sourceTree = ""; }; D8A6FE6629325F5900666A47 /* Localizable.stringsdict */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; path = Localizable.stringsdict; sourceTree = ""; }; + D8AC98752B0F61680045EC2B /* FileManager+SearchHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+SearchHistory.swift"; sourceTree = ""; }; + D8AC98782B0F622B0045EC2B /* SearchHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHistory.swift; sourceTree = ""; }; D8B5E4ED2A4EB8920008970C /* NotificationSettingTableViewToggleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSettingTableViewToggleCell.swift; sourceTree = ""; }; D8B5E4EF2A4EB8A00008970C /* NotificationSettingTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingTableViewCell.swift; sourceTree = ""; }; D8B5E4F12A4EBCF90008970C /* NotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettings.swift; sourceTree = ""; }; @@ -1889,6 +1893,23 @@ path = Localization; sourceTree = ""; }; + D8AC98742B0F615E0045EC2B /* Persistence */ = { + isa = PBXGroup; + children = ( + D8AC98772B0F62230045EC2B /* Model */, + D8AC98752B0F61680045EC2B /* FileManager+SearchHistory.swift */, + ); + path = Persistence; + sourceTree = ""; + }; + D8AC98772B0F62230045EC2B /* Model */ = { + isa = PBXGroup; + children = ( + D8AC98782B0F622B0045EC2B /* SearchHistory.swift */, + ); + path = Model; + sourceTree = ""; + }; D8E5C347296DB896007E76A7 /* Edit History */ = { isa = PBXGroup; children = ( @@ -2174,6 +2195,7 @@ DB427DD425BAA00100D1B89D /* Mastodon */ = { isa = PBXGroup; children = ( + D8AC98742B0F615E0045EC2B /* Persistence */, DB89BA1025C10FF5008580ED /* Mastodon.entitlements */, DB427DE325BAA00100D1B89D /* Info.plist */, 2D76319C25C151DE00929FB9 /* Diffable */, @@ -3842,6 +3864,7 @@ 2A1FE47C2938BB2600784BF1 /* FollowedTagsViewModel+DiffableDataSource.swift in Sources */, DB852D1C26FB021500FC9D81 /* RootSplitViewController.swift in Sources */, DB697DD1278F4871004EF2F7 /* AutoGenerateTableViewDelegate.swift in Sources */, + D8AC98792B0F622B0045EC2B /* SearchHistory.swift in Sources */, DB02CDBF2625AE5000D0A2AF /* AdaptiveUserInterfaceStyleBarButtonItem.swift in Sources */, DB3E6FFA2807C47900B035AE /* DiscoveryForYouViewModel+Diffable.swift in Sources */, DB1FD44425F26CCC004CFCFC /* PickServerSection.swift in Sources */, @@ -3849,6 +3872,7 @@ DB6180E626391B550018D199 /* MediaPreviewTransitionController.swift in Sources */, DB0FCB922796DE19006C02E2 /* TrendSectionHeaderCollectionReusableView.swift in Sources */, DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */, + D8AC98762B0F61680045EC2B /* FileManager+SearchHistory.swift in Sources */, DB63F779279ABF9C00455B82 /* DataSourceFacade+Reblog.swift in Sources */, DB4F0963269ED06300D62E92 /* SearchResultViewController.swift in Sources */, DB603111279EB38500A935FE /* DataSourceFacade+Mute.swift in Sources */, diff --git a/Mastodon/Persistence/FileManager+SearchHistory.swift b/Mastodon/Persistence/FileManager+SearchHistory.swift new file mode 100644 index 000000000..5d31ececc --- /dev/null +++ b/Mastodon/Persistence/FileManager+SearchHistory.swift @@ -0,0 +1,54 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import MastodonCore + +extension FileManager { + func searchItems(forUser userID: String) throws -> [Persistence.SearchHistory.Item] { + guard let path = documentsDirectory()?.appending(path: Persistence.searchHistory.filename).appendingPathExtension("json"), + let data = try? Data(contentsOf: path) + else { return [] } + + let jsonDecoder = JSONDecoder() + jsonDecoder.dateDecodingStrategy = .iso8601 + + do { + let searchItems = try jsonDecoder.decode([Persistence.SearchHistory.Item].self, from: data) + .filter { $0.userID == userID } + .sorted { $0.updatedAt < $1.updatedAt } + + return searchItems + } catch { + return [] + } + } + + func addSearchItem(_ newSearchItem: Persistence.SearchHistory.Item) throws { + guard let path = documentsDirectory()?.appending(path: Persistence.searchHistory.filename).appendingPathExtension("json") else { return } + + var searchItems = (try? searchItems(forUser: newSearchItem.userID)) ?? [] + + searchItems.append(newSearchItem) + + let jsonEncoder = JSONEncoder() + jsonEncoder.dateEncodingStrategy = .iso8601 + do { + let data = try jsonEncoder.encode(searchItems) + try data.write(to: path) + } catch { + debugPrint(error.localizedDescription) + } + } + + func removeSearchHistory() { + guard let path = documentsDirectory()?.appending(path: Persistence.searchHistory.filename).appendingPathExtension("json") else { return } + + try? removeItem(at: path) + } +} + +extension FileManager { + func documentsDirectory() -> URL? { + return self.urls(for: .documentDirectory, in: .userDomainMask).first + } +} diff --git a/Mastodon/Persistence/Model/SearchHistory.swift b/Mastodon/Persistence/Model/SearchHistory.swift new file mode 100644 index 000000000..fc5e992c1 --- /dev/null +++ b/Mastodon/Persistence/Model/SearchHistory.swift @@ -0,0 +1,15 @@ +// Copyright © 2023 Mastodon gGmbH. All rights reserved. + +import Foundation +import MastodonCore +import MastodonSDK + +extension Persistence.SearchHistory { + struct Item: Codable { + let updatedAt: Date + let userID: Mastodon.Entity.Account.ID + + let account: Mastodon.Entity.Account? + let hashtag: Mastodon.Entity.Tag? + } +} diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift index 2422adf6c..b0a0c982e 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+Hashtag.swift @@ -14,14 +14,9 @@ extension DataSourceFacade { @MainActor static func coordinateToHashtagScene( provider: DataSourceProvider & AuthContextProvider, - tag: DataSourceItem.TagKind + tag: Mastodon.Entity.Tag ) async { - switch tag { - case .entity(let entity): - await coordinateToHashtagScene(provider: provider, tag: entity) - case .record(let record): - await coordinateToHashtagScene(provider: provider, tag: record) - } + await coordinateToHashtagScene(provider: provider, tag: tag) } @MainActor diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift index 31206c262..5fd9c1681 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+SearchHistory.swift @@ -11,107 +11,42 @@ import MastodonCore import UIKit extension DataSourceFacade { - + static func responseToCreateSearchHistory( provider: ViewControllerWithDependencies & AuthContextProvider, item: DataSourceItem ) async { switch item { - - case .status, .account(_, _): - break // not create search history for status - case .user(let record): - let authenticationBox = provider.authContext.mastodonAuthenticationBox - let managedObjectContext = provider.context.backgroundManagedObjectContext - - try? await managedObjectContext.performChanges { - guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } - guard let user = record.object(in: managedObjectContext) else { return } - _ = Persistence.SearchHistory.createOrMerge( - in: managedObjectContext, - context: Persistence.SearchHistory.PersistContext( - entity: .user(user), - me: me, - now: Date() - ) + case .account(account: let account, relationship: _): + let now = Date() + let userID = provider.authContext.mastodonAuthenticationBox.userID + let searchEntry = Persistence.SearchHistory.Item( + updatedAt: now, + userID: userID, + account: account, + hashtag: nil ) - } // end try? await managedObjectContext.performChanges { … } - case .hashtag(let tag): - let authenticationBox = provider.authContext.mastodonAuthenticationBox - let managedObjectContext = provider.context.backgroundManagedObjectContext - switch tag { - case .entity(let entity): - try? await managedObjectContext.performChanges { - guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } - - let now = Date() - - let result = Persistence.Tag.createOrMerge( - in: managedObjectContext, - context: Persistence.Tag.PersistContext( - domain: authenticationBox.domain, - entity: entity, - me: me, - networkDate: now - ) - ) - - _ = Persistence.SearchHistory.createOrMerge( - in: managedObjectContext, - context: Persistence.SearchHistory.PersistContext( - entity: .hashtag(result.tag), - me: me, - now: now - ) - ) - } // end try? await managedObjectContext.performChanges { … } - case .record(let record): - try? await managedObjectContext.performChanges { - let authenticationBox = provider.authContext.mastodonAuthenticationBox - guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return } - guard let tag = record.object(in: managedObjectContext) else { return } - - let now = Date() - - _ = Persistence.SearchHistory.createOrMerge( - in: managedObjectContext, - context: Persistence.SearchHistory.PersistContext( - entity: .hashtag(tag), - me: me, - now: now - ) - ) - } // end try? await managedObjectContext.performChanges { … } - } // end switch tag { … } - case .notification: - assertionFailure() - } // end switch item { … } - } // end func - -} - -extension DataSourceFacade { - - static func responseToDeleteSearchHistory( - provider: DataSourceProvider & AuthContextProvider - ) async throws { - let authenticationBox = provider.authContext.mastodonAuthenticationBox - let managedObjectContext = provider.context.backgroundManagedObjectContext - - try await managedObjectContext.performChanges { - guard let _ = authenticationBox.authentication.user(in: managedObjectContext) else { return } - let request = SearchHistory.sortedFetchRequest - request.predicate = SearchHistory.predicate( - domain: authenticationBox.domain, - userID: authenticationBox.userID - ) - let searchHistories = managedObjectContext.safeFetch(request) - - for searchHistory in searchHistories { - managedObjectContext.delete(searchHistory) - } - } // end try await managedObjectContext.performChanges { … } - } // end func + try? FileManager.default.addSearchItem(searchEntry) + case .hashtag(let tag): + let now = Date() + let userID = provider.authContext.mastodonAuthenticationBox.userID + let searchEntry = Persistence.SearchHistory.Item( + updatedAt: now, + userID: userID, + account: nil, + hashtag: tag + ) + + try? FileManager.default.addSearchItem(searchEntry) + case .status: + break + case .user(_): + break + case .notification: + break + + } + } } diff --git a/Mastodon/Protocol/Provider/DataSourceProvider.swift b/Mastodon/Protocol/Provider/DataSourceProvider.swift index b92aadcef..fb1892bdf 100644 --- a/Mastodon/Protocol/Provider/DataSourceProvider.swift +++ b/Mastodon/Protocol/Provider/DataSourceProvider.swift @@ -14,18 +14,11 @@ import class CoreDataStack.Notification enum DataSourceItem: Hashable { case status(record: ManagedObjectRecord) case user(record: ManagedObjectRecord) - case hashtag(tag: TagKind) + case hashtag(tag: Mastodon.Entity.Tag) case notification(record: ManagedObjectRecord) case account(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) } -extension DataSourceItem { - enum TagKind: Hashable { - case entity(Mastodon.Entity.Tag) - case record(ManagedObjectRecord) - } -} - extension DataSourceItem { struct Source { let collectionViewCell: UICollectionViewCell? diff --git a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultOverviewCoordinator.swift b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultOverviewCoordinator.swift index 66ec904f7..7bc2fa65d 100644 --- a/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultOverviewCoordinator.swift +++ b/Mastodon/Scene/Search/SearchDetail/Search Results Overview/SearchResultOverviewCoordinator.swift @@ -5,6 +5,10 @@ import MastodonCore import MastodonSDK import MastodonLocalization +protocol SearchResultOverviewCoordinatorDelegate: AnyObject { + func newSearchHistoryItemAdded(_ coordinator: SearchResultOverviewCoordinator) +} + class SearchResultOverviewCoordinator: Coordinator { let overviewViewController: SearchResultsOverviewTableViewController @@ -12,6 +16,8 @@ class SearchResultOverviewCoordinator: Coordinator { let context: AppContext let authContext: AuthContext + weak var delegate: SearchResultOverviewCoordinatorDelegate? + var activeTask: Task? init(appContext: AppContext, authContext: AuthContext, sceneCoordinator: SceneCoordinator) { @@ -37,13 +43,13 @@ extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControl func showPosts(_ viewController: SearchResultsOverviewTableViewController, tag: Mastodon.Entity.Tag) { Task { - await DataSourceFacade.coordinateToHashtagScene( - provider: viewController, - tag: tag - ) + await DataSourceFacade.coordinateToHashtagScene(provider: viewController, + tag: tag) await DataSourceFacade.responseToCreateSearchHistory(provider: viewController, - item: .hashtag(tag: .entity(tag))) + item: .hashtag(tag: tag)) + + delegate?.newSearchHistoryItemAdded(self) } } @@ -111,27 +117,14 @@ extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControl } func showProfile(_ viewController: SearchResultsOverviewTableViewController, for account: Mastodon.Entity.Account) { - let managedObjectContext = context.managedObjectContext - let domain = authContext.mastodonAuthenticationBox.domain - Task { - let user = try await managedObjectContext.perform { - return Persistence.MastodonUser.fetch(in: managedObjectContext, - context: Persistence.MastodonUser.PersistContext( - domain: domain, - entity: account, - cache: nil, - networkDate: Date() - )) - } + await DataSourceFacade.coordinateToProfileScene(provider: viewController, + account: account) - if let user { - await DataSourceFacade.coordinateToProfileScene(provider: viewController, - user: user.asRecord) + await DataSourceFacade.responseToCreateSearchHistory(provider: viewController, + item: .account(account: account, relationship: nil)) - await DataSourceFacade.responseToCreateSearchHistory(provider: viewController, - item: .user(record: user.asRecord)) - } + delegate?.newSearchHistoryItemAdded(self) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift index d88c93b71..349e59ce8 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchDetailViewController.swift @@ -73,13 +73,7 @@ final class SearchDetailViewController: UIViewController, NeedsDependency { return searchBar }() - private(set) lazy var searchHistoryViewController: SearchHistoryViewController = { - let searchHistoryViewController = SearchHistoryViewController() - searchHistoryViewController.context = context - searchHistoryViewController.coordinator = coordinator - searchHistoryViewController.viewModel = SearchHistoryViewModel(context: context, authContext: viewModel.authContext) - return searchHistoryViewController - }() + private var searchHistoryViewController: SearchHistoryViewController private(set) lazy var searchResultsOverviewViewController: SearchResultsOverviewTableViewController = { return searchResultOverviewCoordinator.overviewViewController @@ -92,8 +86,14 @@ final class SearchDetailViewController: UIViewController, NeedsDependency { self.coordinator = sceneCoordinator self.searchResultOverviewCoordinator = SearchResultOverviewCoordinator(appContext: appContext, authContext: authContext, sceneCoordinator: sceneCoordinator) + self.searchHistoryViewController = SearchHistoryViewController() + searchHistoryViewController.context = appContext + searchHistoryViewController.coordinator = sceneCoordinator + searchHistoryViewController.viewModel = SearchHistoryViewModel(context: appContext, authContext: authContext) super.init(nibName: nil, bundle: nil) + + searchResultOverviewCoordinator.delegate = searchHistoryViewController } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryItem.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryItem.swift index ae156a81f..f4d1afc26 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryItem.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryItem.swift @@ -6,10 +6,9 @@ // import Foundation -import CoreData -import CoreDataStack +import MastodonSDK enum SearchHistoryItem: Hashable { - case hashtag(ManagedObjectRecord) - case user(ManagedObjectRecord) + case hashtag(Mastodon.Entity.Tag) + case account(Mastodon.Entity.Account) } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift index b2dc5b8e4..17b4d4cbf 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistorySection.swift @@ -9,6 +9,7 @@ import UIKit import CoreDataStack import MastodonCore import MastodonAsset +import MastodonSDK enum SearchHistorySection: Hashable { case main @@ -28,47 +29,44 @@ extension SearchHistorySection { configuration: Configuration ) -> UICollectionViewDiffableDataSource { - let userCellRegister = UICollectionView.CellRegistration> { cell, indexPath, item in - context.managedObjectContext.performAndWait { - guard let user = item.object(in: context.managedObjectContext) else { return } - cell.condensedUserView.configure(with: user) - } + let userCellRegister = UICollectionView.CellRegistration { cell, indexPath, account in + + cell.condensedUserView.configure(with: account) } - - let hashtagCellRegister = UICollectionView.CellRegistration> { cell, indexPath, item in - context.managedObjectContext.performAndWait { - guard let hashtag = item.object(in: context.managedObjectContext) else { return } - var contentConfiguration = cell.defaultContentConfiguration() - contentConfiguration.image = UIImage(systemName: "magnifyingglass") - contentConfiguration.imageProperties.tintColor = Asset.Colors.Brand.blurple.color - contentConfiguration.text = "#" + hashtag.name - cell.contentConfiguration = contentConfiguration - } - + + let hashtagCellRegister = UICollectionView.CellRegistration { cell, indexPath, hashtag in + + var contentConfiguration = cell.defaultContentConfiguration() + contentConfiguration.image = UIImage(systemName: "magnifyingglass") + contentConfiguration.imageProperties.tintColor = Asset.Colors.Brand.blurple.color + contentConfiguration.text = "#" + hashtag.name + cell.contentConfiguration = contentConfiguration + var backgroundConfiguration = UIBackgroundConfiguration.listGroupedCell() backgroundConfiguration.backgroundColorTransformer = .init { [weak cell] _ in guard let state = cell?.configurationState else { return .secondarySystemGroupedBackground } - + if state.isHighlighted || state.isSelected { return SystemTheme.tableViewCellSelectionBackgroundColor } return .secondarySystemGroupedBackground } + cell.backgroundConfiguration = backgroundConfiguration } - + let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { collectionView, indexPath, item in switch item { - case .user(let record): - return collectionView.dequeueConfiguredReusableCell( - using: userCellRegister, - for: indexPath, item: record) - case .hashtag(let record): - return collectionView.dequeueConfiguredReusableCell( - using: hashtagCellRegister, - for: indexPath, item: record) + case .account(let account): + return collectionView.dequeueConfiguredReusableCell( + using: userCellRegister, + for: indexPath, item: account) + case .hashtag(let tag): + return collectionView.dequeueConfiguredReusableCell( + using: hashtagCellRegister, + for: indexPath, item: tag) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController+DataSourceProvider.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController+DataSourceProvider.swift index a1bae2638..8ec211193 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController+DataSourceProvider.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController+DataSourceProvider.swift @@ -21,10 +21,10 @@ extension SearchHistoryViewController: DataSourceProvider { } switch item { - case .user(let record): - return .user(record: record) - case .hashtag(let record): - return .hashtag(tag: .record(record)) + case .account(let account): + return .account(account: account, relationship: nil) + case .hashtag(let tag): + return .hashtag(tag: tag) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift index 156a25620..ca46a7f1a 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift @@ -27,6 +27,7 @@ final class SearchHistoryViewController: UIViewController, NeedsDependency { configuration.headerMode = .supplementary let layout = UICollectionViewCompositionalLayout.list(using: configuration) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.keyboardDismissMode = .onDrag return collectionView }() } @@ -56,32 +57,30 @@ extension SearchHistoryViewController: UICollectionViewDelegate { defer { collectionView.deselectItem(at: indexPath, animated: true) } - + Task { let source = DataSourceItem.Source(indexPath: indexPath) guard let item = await item(from: source) else { return } - + await DataSourceFacade.responseToCreateSearchHistory( provider: self, item: item ) - + switch item { - case .user(let record): - await DataSourceFacade.coordinateToProfileScene( - provider: self, - user: record - ) - case .hashtag(let record): - await DataSourceFacade.coordinateToHashtagScene( - provider: self, - tag: record - ) - default: - assertionFailure() - break + case .account(account: let account, relationship: _): + await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) + + case .hashtag(let tag): + await DataSourceFacade.coordinateToHashtagScene( + provider: self, + tag: tag + ) + default: + assertionFailure() + break } } } @@ -99,14 +98,15 @@ extension SearchHistoryViewController: SearchHistorySectionHeaderCollectionReusa _ searchHistorySectionHeaderCollectionReusableView: SearchHistorySectionHeaderCollectionReusableView, clearButtonDidPressed button: UIButton ) { - Task { - try await DataSourceFacade.responseToDeleteSearchHistory( - provider: self - ) - - await MainActor.run { - button.isEnabled = false - } - } + FileManager.default.removeSearchHistory() + viewModel.items = [] + } +} + +//MARK: - SearchResultOverviewCoordinatorDelegate +extension SearchHistoryViewController: SearchResultOverviewCoordinatorDelegate { + func newSearchHistoryItemAdded(_ coordinator: SearchResultOverviewCoordinator) { + let userID = authContext.mastodonAuthenticationBox.userID + viewModel.items = (try? FileManager.default.searchItems(forUser: userID)) ?? [] } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift index a8af8e845..f908588a3 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel+Diffable.swift @@ -26,42 +26,30 @@ extension SearchHistoryViewModel { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.main]) diffableDataSource?.apply(snapshot, animatingDifferences: false) - - searchHistoryFetchedResultController.$records + + $items .receive(on: DispatchQueue.main) - .sink { [weak self] records in + .sink { [weak self] items in + guard let self = self else { return } guard let diffableDataSource = self.diffableDataSource else { return } - - Task { - do { - let managedObjectContext = self.context.managedObjectContext - let items: [SearchHistoryItem] = try await managedObjectContext.perform { - var items: [SearchHistoryItem] = [] - for record in records { - guard let searchHistory = record.object(in: managedObjectContext) else { continue } - if let user = searchHistory.account { - items.append(.user(.init(objectID: user.objectID))) - } else if let hashtag = searchHistory.hashtag { - items.append(.hashtag(.init(objectID: hashtag.objectID))) - } - } - - return items - } - - let mostRecentItems = Array(items.prefix(10)) - var snapshot = NSDiffableDataSourceSnapshot() - snapshot.appendSections([.main]) - snapshot.appendItems(mostRecentItems, toSection: .main) - await diffableDataSource.apply(snapshot, animatingDifferences: true) - } catch { - // do nothing + let searchItems: [SearchHistoryItem] = items.compactMap { + if let account = $0.account { + return .account(account) + } else if let tag = $0.hashtag { + return .hashtag(tag) + } else { + return nil } - } // end Task + } + + let mostRecentItems = Array(searchItems.prefix(10)) + var snapshot = NSDiffableDataSourceSnapshot() + snapshot.appendSections([.main]) + snapshot.appendItems(mostRecentItems, toSection: .main) + diffableDataSource.apply(snapshot, animatingDifferences: true) } .store(in: &disposeBag) } - } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift index d1360efc4..63f1cd0e8 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewModel.swift @@ -16,7 +16,7 @@ final class SearchHistoryViewModel { // input let context: AppContext let authContext: AuthContext - let searchHistoryFetchedResultController: SearchHistoryFetchedResultController + @Published public var items: [Persistence.SearchHistory.Item] // output var diffableDataSource: UICollectionViewDiffableDataSource? @@ -24,10 +24,7 @@ final class SearchHistoryViewModel { init(context: AppContext, authContext: AuthContext) { self.context = context self.authContext = authContext - self.searchHistoryFetchedResultController = SearchHistoryFetchedResultController(managedObjectContext: context.managedObjectContext) - - searchHistoryFetchedResultController.domain.value = authContext.mastodonAuthenticationBox.domain - searchHistoryFetchedResultController.userID.value = authContext.mastodonAuthenticationBox.userID + self.items = (try? FileManager.default.searchItems(forUser: authContext.mastodonAuthenticationBox.userID)) ?? [] } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultItem.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultItem.swift index 813836925..21f0d4b72 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultItem.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultItem.swift @@ -11,7 +11,7 @@ import CoreDataStack import MastodonSDK enum SearchResultItem: Hashable { - case user(ManagedObjectRecord) + case account(Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?) case status(ManagedObjectRecord) case hashtag(tag: Mastodon.Entity.Tag) case bottomLoader(attribute: BottomLoaderAttribute) diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift index 32a913587..87b554e34 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultSection.swift @@ -41,23 +41,21 @@ extension SearchResultSection { return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in switch item { - case .user(let record): - let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as! UserTableViewCell - context.managedObjectContext.performAndWait { - guard let user = record.object(in: context.managedObjectContext) else { return } - configure( - context: context, - authContext: authContext, + case .account(let account, let relationship): + let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as! UserTableViewCell + + guard let me = authContext.mastodonAuthenticationBox.authentication.user(in: context.managedObjectContext) else { return cell } + + cell.userView.setButtonState(.loading) + cell.configure( + me: me, tableView: tableView, - cell: cell, - viewModel: UserTableViewCell.ViewModel(user: user, - followedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followingUserIds.eraseToAnyPublisher(), - blockedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$blockedUserIds.eraseToAnyPublisher(), - followRequestedUsers: authContext.mastodonAuthenticationBox.inMemoryCache.$followRequestedUserIDs.eraseToAnyPublisher()), - configuration: configuration + account: account, + relationship: relationship, + delegate: configuration.userTableViewCellDelegate ) - } - return cell + + return cell case .status(let record): let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell context.managedObjectContext.performAndWait { diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift index 5e9d9e2db..ecd9270e3 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewController+DataSourceProvider.swift @@ -21,12 +21,12 @@ extension SearchResultViewController: DataSourceProvider { } switch item { - case .user(let record): - return .user(record: record) + case .account(let account, let relationship): + return .account(account: account, relationship: relationship) case .status(let record): return .status(record: record) - case .hashtag(let entity): - return .hashtag(tag: .entity(entity)) + case .hashtag(let tag): + return .hashtag(tag: tag) default: return nil } @@ -52,9 +52,8 @@ extension SearchResultViewController { ) switch item { - case .account(account: _, relationship: _): - // do nothing - break + case .account(let account, relationship: _): + await DataSourceFacade.coordinateToProfileScene(provider: self, account: account) case .status(let status): await DataSourceFacade.coordinateToStatusThreadScene( provider: self, diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift index 5b74ba8aa..e08ab0275 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+Diffable.swift @@ -7,6 +7,7 @@ import UIKit import Combine +import MastodonSDK extension SearchResultViewModel { @@ -33,13 +34,19 @@ extension SearchResultViewModel { Publishers.CombineLatest3( statusFetchedResultsController.$records, - userFetchedResultsController.$records, + $accounts, $hashtags ) - .map { statusRecords, userRecords, hashtags in + .map { statusRecords, accounts, hashtags in var items: [SearchResultItem] = [] - - let userItems = userRecords.map { SearchResultItem.user($0) } + + let accountsWithRelationship: [(account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?)] = accounts.compactMap { account in + guard let relationship = self.relationships.first(where: {$0.id == account.id }) else { return (account: account, relationship: nil)} + + return (account: account, relationship: relationship) + } + + let userItems = accountsWithRelationship.map { SearchResultItem.account($0.account, relationship: $0.relationship) } items.append(contentsOf: userItems) let hashtagItems = hashtags.map { SearchResultItem.hashtag(tag: $0) } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift index e332b13d9..fef37ce04 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel+State.swift @@ -113,20 +113,26 @@ extension SearchResultViewModel.State { Task { do { - let response = try await viewModel.context.apiService.search( + let searchResults = try await viewModel.context.apiService.search( query: query, authenticationBox: viewModel.authContext.mastodonAuthenticationBox - ) - + ).value + // discard result when request not the latest one guard id == self.latestLoadingToken else { return } // discard result when state is not Loading guard stateMachine.currentState is Loading else { return } - let userIDs = response.value.accounts.map { $0.id } - let statusIDs = response.value.statuses.map { $0.id } + let accounts = searchResults.accounts - let isNoMore = userIDs.isEmpty && statusIDs.isEmpty + let relationships = try await viewModel.context.apiService.relationship( + forAccounts: accounts, + authenticationBox: viewModel.authContext.mastodonAuthenticationBox + ).value + + let statusIDs = searchResults.statuses.map { $0.id } + + let isNoMore = accounts.isEmpty && statusIDs.isEmpty if viewModel.searchScope == .all || isNoMore { await enter(state: NoMore.self) @@ -136,20 +142,34 @@ extension SearchResultViewModel.State { // reset data source when the search is refresh if offset == nil { - viewModel.userFetchedResultsController.userIDs = [] + viewModel.relationships = [] + viewModel.accounts = [] viewModel.statusFetchedResultsController.statusIDs = [] viewModel.hashtags = [] } - viewModel.userFetchedResultsController.append(userIDs: userIDs) + // due to combine relationships must be set first + + var existingRelationships = viewModel.relationships + for hashtag in relationships where !existingRelationships.contains(hashtag) { + existingRelationships.append(hashtag) + } + viewModel.relationships = existingRelationships + viewModel.statusFetchedResultsController.append(statusIDs: statusIDs) - var hashtags = viewModel.hashtags - for hashtag in response.value.hashtags where !hashtags.contains(hashtag) { - hashtags.append(hashtag) + var existingHashtags = viewModel.hashtags + for hashtag in searchResults.hashtags where !existingHashtags.contains(hashtag) { + existingHashtags.append(hashtag) } - viewModel.hashtags = hashtags - + viewModel.hashtags = existingHashtags + + var existingAccounts = viewModel.accounts + for hashtag in searchResults.accounts where !existingAccounts.contains(hashtag) { + existingAccounts.append(hashtag) + } + viewModel.accounts = existingAccounts + } catch { await enter(state: Fail.self) } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift index c3ddc2f0a..c7459ebbe 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchResult/SearchResultViewModel.swift @@ -22,7 +22,8 @@ final class SearchResultViewModel { let searchScope: SearchScope let searchText: String @Published var hashtags: [Mastodon.Entity.Tag] = [] - let userFetchedResultsController: UserFetchedResultsController + @Published var accounts: [Mastodon.Entity.Account] = [] + var relationships: [Mastodon.Entity.Relationship] = [] let statusFetchedResultsController: StatusFetchedResultsController let listBatchFetchViewModel = ListBatchFetchViewModel() @@ -50,12 +51,9 @@ final class SearchResultViewModel { self.authContext = authContext self.searchScope = searchScope self.searchText = searchText + self.accounts = [] + self.relationships = [] - self.userFetchedResultsController = UserFetchedResultsController( - managedObjectContext: context.managedObjectContext, - domain: authContext.mastodonAuthenticationBox.domain, - additionalPredicate: nil - ) self.statusFetchedResultsController = StatusFetchedResultsController( managedObjectContext: context.managedObjectContext, domain: authContext.mastodonAuthenticationBox.domain, diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift index 3a36dec41..b99cca17e 100644 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift @@ -8,7 +8,16 @@ import Foundation -public enum Persistence { } +public enum Persistence { + case searchHistory + + public var filename: String { + switch self { + case .searchHistory: + return "search_history" + } + } +} extension Persistence { diff --git a/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift b/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift index 48089be64..c19d8f2ed 100644 --- a/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift +++ b/MastodonSDK/Sources/MastodonUI/View/Content/CondensedUserView.swift @@ -124,45 +124,6 @@ public class CondensedUserView: UIView { avatarImageView.prepareForReuse() } - public func configure(with user: MastodonUser) { - let displayNameMetaContent: MetaContent - do { - let content = MastodonContent(content: user.displayNameWithFallback, emojis: user.emojis.asDictionary) - displayNameMetaContent = try MastodonMetaContent.convert(document: content) - } catch { - displayNameMetaContent = PlaintextMetaContent(string: user.displayNameWithFallback) - } - - displayNameLabel.configure(content: displayNameMetaContent) - acctLabel.text = user.acct - followersLabel.attributedText = NSAttributedString( - format: NSAttributedString(string: L10n.Common.UserList.followersCount("%@"), attributes: [.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .regular))]), - args: NSAttributedString(string: Self.metricFormatter.string(from: Int(user.followersCount)) ?? user.followersCount.formatted(), attributes: [.font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .systemFont(ofSize: 15, weight: .bold))]) - ) - - avatarImageView.setImage(url: user.avatarImageURL()) - - if let verifiedLink = user.verifiedLink?.value { - verifiedLinkImageView.image = UIImage(systemName: "checkmark") - verifiedLinkImageView.tintColor = Asset.Colors.Brand.blurple.color - - let verifiedLinkMetaContent: MetaContent - do { - let mastodonContent = MastodonContent(content: verifiedLink, emojis: [:]) - verifiedLinkMetaContent = try MastodonMetaContent.convert(document: mastodonContent) - } catch { - verifiedLinkMetaContent = PlaintextMetaContent(string: verifiedLink) - } - - verifiedLinkLabel.configure(content: verifiedLinkMetaContent) - } else { - verifiedLinkImageView.image = UIImage(systemName: "questionmark.circle") - verifiedLinkImageView.tintColor = .secondaryLabel - - verifiedLinkLabel.configure(content: PlaintextMetaContent(string: L10n.Common.UserList.noVerifiedLink)) - } - } - public func configure(with account: Mastodon.Entity.Account, showFollowers: Bool = true) { let displayNameMetaContent: MetaContent do { From 6b0fe642624f0149cbff1af4d6ce55a91a04861a Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 14:28:55 +0100 Subject: [PATCH 2/9] Replace items instead of adding them (IOS-196) --- Mastodon/Persistence/FileManager+SearchHistory.swift | 4 ++++ Mastodon/Persistence/Model/SearchHistory.swift | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Mastodon/Persistence/FileManager+SearchHistory.swift b/Mastodon/Persistence/FileManager+SearchHistory.swift index 5d31ececc..a9da200fd 100644 --- a/Mastodon/Persistence/FileManager+SearchHistory.swift +++ b/Mastodon/Persistence/FileManager+SearchHistory.swift @@ -28,6 +28,10 @@ extension FileManager { var searchItems = (try? searchItems(forUser: newSearchItem.userID)) ?? [] + if let index = searchItems.firstIndex(of: newSearchItem) { + searchItems.remove(at: index) + } + searchItems.append(newSearchItem) let jsonEncoder = JSONEncoder() diff --git a/Mastodon/Persistence/Model/SearchHistory.swift b/Mastodon/Persistence/Model/SearchHistory.swift index fc5e992c1..f477a93ad 100644 --- a/Mastodon/Persistence/Model/SearchHistory.swift +++ b/Mastodon/Persistence/Model/SearchHistory.swift @@ -5,11 +5,21 @@ import MastodonCore import MastodonSDK extension Persistence.SearchHistory { - struct Item: Codable { + struct Item: Codable, Hashable, Equatable { let updatedAt: Date let userID: Mastodon.Entity.Account.ID let account: Mastodon.Entity.Account? let hashtag: Mastodon.Entity.Tag? + + func hash(into hasher: inout Hasher) { + hasher.combine(userID) + hasher.combine(account) + hasher.combine(hashtag) + } + + public static func == (lhs: Persistence.SearchHistory.Item, rhs: Persistence.SearchHistory.Item) -> Bool { + return lhs.account == rhs.account && lhs.hashtag == rhs.hashtag + } } } From 9c662f4d78d7051499c1c4266b219df7ffa57ef6 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 14:30:46 +0100 Subject: [PATCH 3/9] Remove FetchedResultsController for SearchHistory (IOS-196) --- ...SearchHistoryFetchedResultController.swift | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 MastodonSDK/Sources/MastodonCore/FetchedResultsController/SearchHistoryFetchedResultController.swift diff --git a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/SearchHistoryFetchedResultController.swift b/MastodonSDK/Sources/MastodonCore/FetchedResultsController/SearchHistoryFetchedResultController.swift deleted file mode 100644 index f8376a9c0..000000000 --- a/MastodonSDK/Sources/MastodonCore/FetchedResultsController/SearchHistoryFetchedResultController.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// SearchHistoryFetchedResultController.swift -// Mastodon -// -// Created by MainasuK Cirno on 2021-7-15. -// - -import UIKit -import Combine -import CoreData -import CoreDataStack -import MastodonSDK - -public final class SearchHistoryFetchedResultController: NSObject { - - var disposeBag = Set() - - public let fetchedResultsController: NSFetchedResultsController - public let domain = CurrentValueSubject(nil) - public let userID = CurrentValueSubject(nil) - - // output - let _objectIDs = CurrentValueSubject<[NSManagedObjectID], Never>([]) - @Published public private(set) var records: [ManagedObjectRecord] = [] - - public init(managedObjectContext: NSManagedObjectContext) { - self.fetchedResultsController = { - let fetchRequest = SearchHistory.sortedFetchRequest - fetchRequest.returnsObjectsAsFaults = false - fetchRequest.fetchBatchSize = 20 - let controller = NSFetchedResultsController( - fetchRequest: fetchRequest, - managedObjectContext: managedObjectContext, - sectionNameKeyPath: nil, - cacheName: nil - ) - - return controller - }() - super.init() - - // debounce output to prevent UI update issues - _objectIDs - .throttle(for: 0.1, scheduler: DispatchQueue.main, latest: true) - .map { objectIDs in objectIDs.map { ManagedObjectRecord(objectID: $0) } } - .assign(to: &$records) - - fetchedResultsController.delegate = self - - Publishers.CombineLatest( - self.domain, - self.userID - ) - .receive(on: DispatchQueue.main) - .sink { [weak self] domain, userID in - guard let self = self else { return } - let predicates = [SearchHistory.predicate(domain: domain ?? "", userID: userID ?? "")] - self.fetchedResultsController.fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicates) - do { - try self.fetchedResultsController.performFetch() - } catch { - assertionFailure(error.localizedDescription) - } - } - .store(in: &disposeBag) - } - -} - -// MARK: - NSFetchedResultsControllerDelegate -extension SearchHistoryFetchedResultController: NSFetchedResultsControllerDelegate { - public func controller(_ controller: NSFetchedResultsController, didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) { - - let objects = fetchedResultsController.fetchedObjects ?? [] - self._objectIDs.value = objects.map { $0.objectID } - } -} From 1e780481d183cae483ea7336d9065d6b530eadf4 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 14:50:49 +0100 Subject: [PATCH 4/9] Remove SearchHistory from Core Data (IOS-196) --- .../FileManager+SearchHistory.swift | 2 +- .../CoreData 9.xcdatamodel/contents | 13 -- .../Entity/Mastodon/MastodonUser.swift | 23 --- .../Entity/Mastodon/SearchHistory.swift | 158 ------------------ .../Entity/Mastodon/Status.swift | 1 - .../CoreDataStack/Entity/Mastodon/Tag.swift | 45 ----- .../Persistence+SearchHistory.swift | 113 ------------- 7 files changed, 1 insertion(+), 354 deletions(-) delete mode 100644 MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/SearchHistory.swift delete mode 100644 MastodonSDK/Sources/MastodonCore/Persistence/Persistence+SearchHistory.swift diff --git a/Mastodon/Persistence/FileManager+SearchHistory.swift b/Mastodon/Persistence/FileManager+SearchHistory.swift index a9da200fd..e84de6abe 100644 --- a/Mastodon/Persistence/FileManager+SearchHistory.swift +++ b/Mastodon/Persistence/FileManager+SearchHistory.swift @@ -15,7 +15,7 @@ extension FileManager { do { let searchItems = try jsonDecoder.decode([Persistence.SearchHistory.Item].self, from: data) .filter { $0.userID == userID } - .sorted { $0.updatedAt < $1.updatedAt } + .sorted { $0.updatedAt > $1.updatedAt } return searchItems } catch { diff --git a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 9.xcdatamodel/contents b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 9.xcdatamodel/contents index c5bbd1485..062ae9d7b 100644 --- a/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 9.xcdatamodel/contents +++ b/MastodonSDK/Sources/CoreDataStack/CoreData.xcdatamodeld/CoreData 9.xcdatamodel/contents @@ -125,7 +125,6 @@ - @@ -176,16 +175,6 @@ - - - - - - - - - - @@ -236,7 +225,6 @@ - @@ -271,6 +259,5 @@ - \ No newline at end of file diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift index 83f94fd22..31ed535a9 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/MastodonUser.swift @@ -68,7 +68,6 @@ final public class MastodonUser: NSManagedObject { // one-to-many relationship @NSManaged public private(set) var statuses: Set @NSManaged public private(set) var notifications: Set - @NSManaged public private(set) var searchHistories: Set // many-to-many relationship @NSManaged public private(set) var favourite: Set @@ -216,28 +215,6 @@ extension MastodonUser { } - -extension MastodonUser { - - public func findSearchHistory( - domain: String, - userID: MastodonUser.ID - ) -> SearchHistory? { - return searchHistories.first { searchHistory in - return searchHistory.domain == domain - && searchHistory.userID == userID - } - } - - public func findSearchHistory(for user: MastodonUser) -> SearchHistory? { - return searchHistories.first { searchHistory in - return searchHistory.domain == user.domain - && searchHistory.userID == user.id - } - } - -} - // MARK: - AutoGenerateProperty extension MastodonUser: AutoGenerateProperty { // sourcery:inline:MastodonUser.AutoGenerateProperty diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/SearchHistory.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/SearchHistory.swift deleted file mode 100644 index c3c6d28c3..000000000 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/SearchHistory.swift +++ /dev/null @@ -1,158 +0,0 @@ -// -// SearchHistory.swift -// CoreDataStack -// -// Created by sxiaojian on 2021/4/7. -// - -import Foundation -import CoreData - -public final class SearchHistory: NSManagedObject { - public typealias ID = UUID - - // sourcery: autoGenerateProperty - @NSManaged public private(set) var identifier: ID - // sourcery: autoGenerateProperty - @NSManaged public private(set) var domain: String - // sourcery: autoGenerateProperty - @NSManaged public private(set) var userID: MastodonUser.ID - // sourcery: autoGenerateProperty - @NSManaged public private(set) var createAt: Date - // sourcery: autoUpdatableObject, autoGenerateProperty - @NSManaged public private(set) var updatedAt: Date - - // many-to-one relationship - // sourcery: autoGenerateRelationship - @NSManaged public private(set) var account: MastodonUser? - // sourcery: autoGenerateRelationship - @NSManaged public private(set) var hashtag: Tag? - // sourcery: autoGenerateRelationship - @NSManaged public private(set) var status: Status? - -} - -extension SearchHistory { - @discardableResult - public static func insert( - into context: NSManagedObjectContext, - property: Property, - relationship: Relationship - ) -> SearchHistory { - let object: SearchHistory = context.insertObject() - - object.configure(property: property) - object.configure(relationship: relationship) - - return object - } -} - -extension SearchHistory: Managed { - public static var defaultSortDescriptors: [NSSortDescriptor] { - return [NSSortDescriptor(keyPath: \SearchHistory.updatedAt, ascending: false)] - } -} - -extension SearchHistory { - static func predicate(domain: String) -> NSPredicate { - return NSPredicate(format: "%K == %@", #keyPath(SearchHistory.domain), domain) - } - - static func predicate(userID: String) -> NSPredicate { - return NSPredicate(format: "%K == %@", #keyPath(SearchHistory.userID), userID) - } - - public static func predicate(domain: String, userID: String) -> NSPredicate { - return NSCompoundPredicate(andPredicateWithSubpredicates: [ - predicate(domain: domain), - predicate(userID: userID) - ]) - } -} - -// MARK: - AutoGenerateProperty -extension SearchHistory: AutoGenerateProperty { - // sourcery:inline:SearchHistory.AutoGenerateProperty - - // Generated using Sourcery - // DO NOT EDIT - public struct Property { - public let identifier: ID - public let domain: String - public let userID: MastodonUser.ID - public let createAt: Date - public let updatedAt: Date - - public init( - identifier: ID, - domain: String, - userID: MastodonUser.ID, - createAt: Date, - updatedAt: Date - ) { - self.identifier = identifier - self.domain = domain - self.userID = userID - self.createAt = createAt - self.updatedAt = updatedAt - } - } - - public func configure(property: Property) { - self.identifier = property.identifier - self.domain = property.domain - self.userID = property.userID - self.createAt = property.createAt - self.updatedAt = property.updatedAt - } - - public func update(property: Property) { - update(updatedAt: property.updatedAt) - } - // sourcery:end -} - -// MARK: - AutoGenerateRelationship -extension SearchHistory: AutoGenerateRelationship { - // sourcery:inline:SearchHistory.AutoGenerateRelationship - - // Generated using Sourcery - // DO NOT EDIT - public struct Relationship { - public let account: MastodonUser? - public let hashtag: Tag? - public let status: Status? - - public init( - account: MastodonUser?, - hashtag: Tag?, - status: Status? - ) { - self.account = account - self.hashtag = hashtag - self.status = status - } - } - - public func configure(relationship: Relationship) { - self.account = relationship.account - self.hashtag = relationship.hashtag - self.status = relationship.status - } - // sourcery:end -} - -// MARK: - AutoUpdatableObject -extension SearchHistory: AutoUpdatableObject { - // sourcery:inline:SearchHistory.AutoUpdatableObject - - // Generated using Sourcery - // DO NOT EDIT - public func update(updatedAt: Date) { - if self.updatedAt != updatedAt { - self.updatedAt = updatedAt - } - } - // sourcery:end -} diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift index 1bdd9410a..1b457f085 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Status.swift @@ -98,7 +98,6 @@ public final class Status: NSManagedObject { @NSManaged public private(set) var reblogFrom: Set @NSManaged public private(set) var replyFrom: Set @NSManaged public private(set) var notifications: Set - @NSManaged public private(set) var searchHistories: Set // sourcery: autoUpdatableObject, autoGenerateProperty @NSManaged public private(set) var updatedAt: Date diff --git a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift index 8332f3d4c..d95c9dcb3 100644 --- a/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift +++ b/MastodonSDK/Sources/CoreDataStack/Entity/Mastodon/Tag.swift @@ -31,9 +31,6 @@ public final class Tag: NSManagedObject { // many-to-many relationship @NSManaged public private(set) var followedBy: Set - - // one-to-many relationship - @NSManaged public private(set) var searchHistories: Set } extension Tag { @@ -216,45 +213,3 @@ extension Tag: AutoUpdatableObject { } } - - -extension Tag { - - public func findSearchHistory(domain: String, userID: MastodonUser.ID) -> SearchHistory? { - return searchHistories.first { searchHistory in - return searchHistory.domain == domain - && searchHistory.userID == userID - } - } - - public func findSearchHistory(for user: MastodonUser) -> SearchHistory? { - return searchHistories.first { searchHistory in - return searchHistory.domain == user.domain - && searchHistory.userID == user.id - } - } - -} - -public extension Tag { -// func updateHistory(index: Int, day: Date, uses: String, account: String) { -// let histories = self.histories.sorted { -// $0.createAt.compare($1.createAt) == .orderedAscending -// } -// guard index < histories.count else { return } -// let history = histories[index] -// history.update(day: day) -// history.update(uses: uses) -// history.update(accounts: account) -// } -// -// func appendHistory(history: History) { -// self.mutableSetValue(forKeyPath: #keyPath(Tag.histories)).add(history) -// } -// -// func update(url: String) { -// if self.url != url { -// self.url = url -// } -// } -} diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+SearchHistory.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+SearchHistory.swift deleted file mode 100644 index ef47448cc..000000000 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence+SearchHistory.swift +++ /dev/null @@ -1,113 +0,0 @@ -// -// Persistence+SearchHistory.swift -// Mastodon -// -// Created by MainasuK on 2022-1-20. -// - -import CoreData -import CoreDataStack -import Foundation -import MastodonSDK - -extension Persistence.SearchHistory { - - public struct PersistContext { - public let entity: Entity - public let me: MastodonUser - public let now: Date - public init( - entity: Entity, - me: MastodonUser, - now: Date - ) { - self.entity = entity - self.me = me - self.now = now - } - - public enum Entity: Hashable { - case user(MastodonUser) - case hashtag(Tag) - } - } - - public struct PersistResult { - public let searchHistory: SearchHistory - public let isNewInsertion: Bool - - public init( - searchHistory: SearchHistory, - isNewInsertion: Bool - ) { - self.searchHistory = searchHistory - self.isNewInsertion = isNewInsertion - } - } - - public static func createOrMerge( - in managedObjectContext: NSManagedObjectContext, - context: PersistContext - ) -> PersistResult { - if let old = fetch(in: managedObjectContext, context: context) { - update(searchHistory: old, context: context) - return PersistResult(searchHistory: old, isNewInsertion: false) - } else { - let object = create(in: managedObjectContext, context: context) - return PersistResult(searchHistory: object, isNewInsertion: true) - } - } - -} - -extension Persistence.SearchHistory { - - public static func fetch( - in managedObjectContext: NSManagedObjectContext, - context: PersistContext - ) -> SearchHistory? { - switch context.entity { - case .user(let user): - return user.findSearchHistory(for: context.me) - case .hashtag(let hashtag): - return hashtag.findSearchHistory(for: context.me) - } - } - - @discardableResult - public static func create( - in managedObjectContext: NSManagedObjectContext, - context: PersistContext - ) -> SearchHistory { - let property = SearchHistory.Property( - identifier: UUID(), - domain: context.me.domain, - userID: context.me.id, - createAt: context.now, - updatedAt: context.now - ) - let relationship: SearchHistory.Relationship = { - switch context.entity { - case .user(let user): - return SearchHistory.Relationship(account: user, hashtag: nil, status: nil) - case .hashtag(let hashtag): - return SearchHistory.Relationship(account: nil, hashtag: hashtag, status: nil) - } - }() - let searchHistory = SearchHistory.insert( - into: managedObjectContext, - property: property, - relationship: relationship - ) - update(searchHistory: searchHistory, context: context) - return searchHistory - } - - private static func update( - searchHistory: SearchHistory, - context: PersistContext - ) { - searchHistory.update(updatedAt: context.now) - } - -} From 1514e5a2c2b561bc5a30478c8906ab1f21c6ff70 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 15:07:37 +0100 Subject: [PATCH 5/9] Slight refactor paths (IOS-196) --- .../FileManager+SearchHistory.swift | 21 ++++++++++++------- .../Persistence/Persistence.swift | 8 ++++++- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Mastodon/Persistence/FileManager+SearchHistory.swift b/Mastodon/Persistence/FileManager+SearchHistory.swift index e84de6abe..cd352dfbf 100644 --- a/Mastodon/Persistence/FileManager+SearchHistory.swift +++ b/Mastodon/Persistence/FileManager+SearchHistory.swift @@ -5,9 +5,11 @@ import MastodonCore extension FileManager { func searchItems(forUser userID: String) throws -> [Persistence.SearchHistory.Item] { - guard let path = documentsDirectory()?.appending(path: Persistence.searchHistory.filename).appendingPathExtension("json"), - let data = try? Data(contentsOf: path) - else { return [] } + guard let documentsDirectory else { return [] } + + let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) + + guard let data = try? Data(contentsOf: searchHistoryPath) else { return [] } let jsonDecoder = JSONDecoder() jsonDecoder.dateDecodingStrategy = .iso8601 @@ -24,7 +26,7 @@ extension FileManager { } func addSearchItem(_ newSearchItem: Persistence.SearchHistory.Item) throws { - guard let path = documentsDirectory()?.appending(path: Persistence.searchHistory.filename).appendingPathExtension("json") else { return } + guard let documentsDirectory else { return } var searchItems = (try? searchItems(forUser: newSearchItem.userID)) ?? [] @@ -38,21 +40,24 @@ extension FileManager { jsonEncoder.dateEncodingStrategy = .iso8601 do { let data = try jsonEncoder.encode(searchItems) - try data.write(to: path) + + let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) + try data.write(to: searchHistoryPath) } catch { debugPrint(error.localizedDescription) } } func removeSearchHistory() { - guard let path = documentsDirectory()?.appending(path: Persistence.searchHistory.filename).appendingPathExtension("json") else { return } + guard let documentsDirectory else { return } - try? removeItem(at: path) + let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) + try? removeItem(at: searchHistoryPath) } } extension FileManager { - func documentsDirectory() -> URL? { + public var documentsDirectory: URL? { return self.urls(for: .documentDirectory, in: .userDomainMask).first } } diff --git a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift index b99cca17e..8445d87ba 100644 --- a/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift +++ b/MastodonSDK/Sources/MastodonCore/Persistence/Persistence.swift @@ -11,12 +11,18 @@ import Foundation public enum Persistence { case searchHistory - public var filename: String { + private var filename: String { switch self { case .searchHistory: return "search_history" } } + + public func filepath(baseURL: URL) -> URL { + baseURL + .appending(path: filename) + .appendingPathExtension("json") + } } From a44d4eed470ebbce5e5e137cba457f988774cb5d Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 15:22:50 +0100 Subject: [PATCH 6/9] Make search work with different accounts (IOS-196) --- Mastodon/Persistence/FileManager+SearchHistory.swift | 7 +++++-- Mastodon/Persistence/Model/SearchHistory.swift | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Mastodon/Persistence/FileManager+SearchHistory.swift b/Mastodon/Persistence/FileManager+SearchHistory.swift index cd352dfbf..45abe98b2 100644 --- a/Mastodon/Persistence/FileManager+SearchHistory.swift +++ b/Mastodon/Persistence/FileManager+SearchHistory.swift @@ -5,6 +5,10 @@ import MastodonCore extension FileManager { func searchItems(forUser userID: String) throws -> [Persistence.SearchHistory.Item] { + return try searchItems().filter { $0.userID == userID } + } + + func searchItems() throws -> [Persistence.SearchHistory.Item] { guard let documentsDirectory else { return [] } let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) @@ -16,7 +20,6 @@ extension FileManager { do { let searchItems = try jsonDecoder.decode([Persistence.SearchHistory.Item].self, from: data) - .filter { $0.userID == userID } .sorted { $0.updatedAt > $1.updatedAt } return searchItems @@ -28,7 +31,7 @@ extension FileManager { func addSearchItem(_ newSearchItem: Persistence.SearchHistory.Item) throws { guard let documentsDirectory else { return } - var searchItems = (try? searchItems(forUser: newSearchItem.userID)) ?? [] + var searchItems = (try? searchItems()) ?? [] if let index = searchItems.firstIndex(of: newSearchItem) { searchItems.remove(at: index) diff --git a/Mastodon/Persistence/Model/SearchHistory.swift b/Mastodon/Persistence/Model/SearchHistory.swift index f477a93ad..057536747 100644 --- a/Mastodon/Persistence/Model/SearchHistory.swift +++ b/Mastodon/Persistence/Model/SearchHistory.swift @@ -19,7 +19,7 @@ extension Persistence.SearchHistory { } public static func == (lhs: Persistence.SearchHistory.Item, rhs: Persistence.SearchHistory.Item) -> Bool { - return lhs.account == rhs.account && lhs.hashtag == rhs.hashtag + return lhs.account == rhs.account && lhs.hashtag == rhs.hashtag && lhs.userID == rhs.userID } } } From 361ad357db596d0c1d5ab8348625ac802b1b23c3 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 15:33:28 +0100 Subject: [PATCH 7/9] Clean search-history for one user only (IOS-196) --- .../FileManager+SearchHistory.swift | 19 ++++++++++++++----- .../SearchHistoryViewController.swift | 4 +++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Mastodon/Persistence/FileManager+SearchHistory.swift b/Mastodon/Persistence/FileManager+SearchHistory.swift index 45abe98b2..8c1cabd1d 100644 --- a/Mastodon/Persistence/FileManager+SearchHistory.swift +++ b/Mastodon/Persistence/FileManager+SearchHistory.swift @@ -39,23 +39,32 @@ extension FileManager { searchItems.append(newSearchItem) + storeJSON(searchItems, .searchHistory) + } + + private func storeJSON(_ encodable: Encodable, _ persistence: Persistence) { + guard let documentsDirectory else { return } + let jsonEncoder = JSONEncoder() jsonEncoder.dateEncodingStrategy = .iso8601 do { - let data = try jsonEncoder.encode(searchItems) + let data = try jsonEncoder.encode(encodable) - let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) + let searchHistoryPath = persistence.filepath(baseURL: documentsDirectory) try data.write(to: searchHistoryPath) } catch { debugPrint(error.localizedDescription) } + } - func removeSearchHistory() { + func removeSearchHistory(forUser userID: String) { guard let documentsDirectory else { return } - let searchHistoryPath = Persistence.searchHistory.filepath(baseURL: documentsDirectory) - try? removeItem(at: searchHistoryPath) + var searchItems = (try? searchItems()) ?? [] + let newSearchItems = searchItems.filter { $0.userID != userID } + + storeJSON(newSearchItems, .searchHistory) } } diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift index ca46a7f1a..a2c696c3b 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift @@ -98,7 +98,9 @@ extension SearchHistoryViewController: SearchHistorySectionHeaderCollectionReusa _ searchHistorySectionHeaderCollectionReusableView: SearchHistorySectionHeaderCollectionReusableView, clearButtonDidPressed button: UIButton ) { - FileManager.default.removeSearchHistory() + let userID = authContext.mastodonAuthenticationBox.userID + + FileManager.default.removeSearchHistory(forUser: userID) viewModel.items = [] } } From 315d15b256ac462e9fad681b9d0b323e741e8c6d Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Thu, 23 Nov 2023 15:47:51 +0100 Subject: [PATCH 8/9] Fix warnings --- .../Protocol/Provider/DataSourceFacade+UserView.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift index a90bdd0a8..a34c2ad4a 100644 --- a/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift +++ b/Mastodon/Protocol/Provider/DataSourceFacade+UserView.swift @@ -72,7 +72,7 @@ extension DataSourceFacade { ) async throws { switch buttonState { case .follow: - try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, user: user ) @@ -80,21 +80,21 @@ extension DataSourceFacade { dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.append(user.id) case .request: - try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, user: user ) dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followRequestedUserIDs.append(user.id) case .unfollow: - try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, user: user ) dependency.authContext.mastodonAuthenticationBox.inMemoryCache.followingUserIds.removeAll(where: { $0 == user.id }) case .blocked: - try await DataSourceFacade.responseToUserBlockAction( + try await DataSourceFacade.responseToUserBlockAction( dependency: dependency, user: user ) @@ -102,7 +102,7 @@ extension DataSourceFacade { dependency.authContext.mastodonAuthenticationBox.inMemoryCache.blockedUserIds.append(user.id) case .pending: - try await DataSourceFacade.responseToUserFollowAction( + _ = try await DataSourceFacade.responseToUserFollowAction( dependency: dependency, user: user ) From 7d8af4ef358c9a4225c3c5c01262046d04d0d165 Mon Sep 17 00:00:00 2001 From: Nathan Mattes Date: Mon, 27 Nov 2023 14:56:38 +0100 Subject: [PATCH 9/9] Reload search-results when entering the screen (IOS-196) --- .../SearchHistory/SearchHistoryViewController.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift index a2c696c3b..afddfbbac 100644 --- a/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift +++ b/Mastodon/Scene/Search/SearchDetail/SearchHistory/SearchHistoryViewController.swift @@ -48,6 +48,11 @@ extension SearchHistoryViewController { searchHistorySectionHeaderCollectionReusableViewDelegate: self ) } + + override func viewWillAppear(_ animated: Bool) { + let userID = authContext.mastodonAuthenticationBox.userID + viewModel.items = (try? FileManager.default.searchItems(forUser: userID)) ?? [] + } } // MARK: - UICollectionViewDelegate