Refactor navigation-logic into a coordinator (IOS-141)
This commit is contained in:
parent
fa6b3fed24
commit
c1b80a73c2
|
@ -152,6 +152,7 @@
|
||||||
D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; };
|
D8BE30B32A179E26006B8270 /* SuggestionAccountTableViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */; };
|
||||||
D8BEBCB62A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */; };
|
D8BEBCB62A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */; };
|
||||||
D8D688F62AB869CB000F651A /* SearchResultsProfileTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */; };
|
D8D688F62AB869CB000F651A /* SearchResultsProfileTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */; };
|
||||||
|
D8D688F92AB8B970000F651A /* SearchResultOverviewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */; };
|
||||||
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; };
|
D8E5C346296DAB84007E76A7 /* DataSourceFacade+Status+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */; };
|
||||||
D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; };
|
D8E5C349296DB8A3007E76A7 /* StatusEditHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */; };
|
||||||
D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */; };
|
D8F0372C29D232730027DE2E /* HashtagIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */; };
|
||||||
|
@ -804,6 +805,7 @@
|
||||||
D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = "<group>"; };
|
D8BE30B22A179E26006B8270 /* SuggestionAccountTableViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionAccountTableViewFooter.swift; sourceTree = "<group>"; };
|
||||||
D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
D8BEBCB52A1B7FFD0004F475 /* SuggestionAccountTableViewCell+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SuggestionAccountTableViewCell+ViewModel.swift"; sourceTree = "<group>"; };
|
||||||
D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsProfileTableViewCell.swift; sourceTree = "<group>"; };
|
D8D688F52AB869CB000F651A /* SearchResultsProfileTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsProfileTableViewCell.swift; sourceTree = "<group>"; };
|
||||||
|
D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultOverviewCoordinator.swift; sourceTree = "<group>"; };
|
||||||
D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = "<group>"; };
|
D8E5C345296DAB84007E76A7 /* DataSourceFacade+Status+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DataSourceFacade+Status+History.swift"; sourceTree = "<group>"; };
|
||||||
D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = "<group>"; };
|
D8E5C348296DB8A3007E76A7 /* StatusEditHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusEditHistoryViewController.swift; sourceTree = "<group>"; };
|
||||||
D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagIntentHandler.swift; sourceTree = "<group>"; };
|
D8F0372B29D232730027DE2E /* HashtagIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagIntentHandler.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1803,6 +1805,7 @@
|
||||||
D81A22792AB47B8400905D71 /* Cells */,
|
D81A22792AB47B8400905D71 /* Cells */,
|
||||||
D81A22742AB4643200905D71 /* SearchResultsOverviewTableViewController.swift */,
|
D81A22742AB4643200905D71 /* SearchResultsOverviewTableViewController.swift */,
|
||||||
D81A22772AB4782400905D71 /* SearchResultOverviewSection.swift */,
|
D81A22772AB4782400905D71 /* SearchResultOverviewSection.swift */,
|
||||||
|
D8D688F82AB8B970000F651A /* SearchResultOverviewCoordinator.swift */,
|
||||||
);
|
);
|
||||||
path = "Search Results Overview";
|
path = "Search Results Overview";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3806,6 +3809,7 @@
|
||||||
DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */,
|
DB63F77B279ACAE500455B82 /* DataSourceFacade+Favorite.swift in Sources */,
|
||||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
||||||
2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */,
|
2DAC9E46262FC9FD0062E1A6 /* SuggestionAccountTableViewCell.swift in Sources */,
|
||||||
|
D8D688F92AB8B970000F651A /* SearchResultOverviewCoordinator.swift in Sources */,
|
||||||
DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */,
|
DB4FFC2C269EC39600D62E92 /* SearchTransitionController.swift in Sources */,
|
||||||
2A3D9B7E29A8F33A00F30313 /* StatusHistoryView.swift in Sources */,
|
2A3D9B7E29A8F33A00F30313 /* StatusHistoryView.swift in Sources */,
|
||||||
D81A227B2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift in Sources */,
|
D81A227B2AB47B9A00905D71 /* SearchResultDefaultSectionTableViewCell.swift in Sources */,
|
||||||
|
|
|
@ -417,7 +417,7 @@ private extension SceneCoordinator {
|
||||||
|
|
||||||
|
|
||||||
case .searchDetail(let viewModel):
|
case .searchDetail(let viewModel):
|
||||||
let _viewController = SearchDetailViewController()
|
let _viewController = SearchDetailViewController(appContext: appContext, sceneCoordinator: self, authContext: viewModel.authContext)
|
||||||
_viewController.viewModel = viewModel
|
_viewController.viewModel = viewModel
|
||||||
viewController = _viewController
|
viewController = _viewController
|
||||||
case .searchResult(let viewModel):
|
case .searchResult(let viewModel):
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MastodonCore
|
||||||
|
import MastodonSDK
|
||||||
|
import MastodonLocalization
|
||||||
|
|
||||||
|
protocol Coordinator {
|
||||||
|
func start()
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchResultOverviewCoordinator: Coordinator {
|
||||||
|
|
||||||
|
let overviewViewController: SearchResultsOverviewTableViewController
|
||||||
|
let sceneCoordinator: SceneCoordinator
|
||||||
|
let context: AppContext
|
||||||
|
let authContext: AuthContext
|
||||||
|
|
||||||
|
var activeTask: Task<Void, Never>?
|
||||||
|
|
||||||
|
init(appContext: AppContext, authContext: AuthContext, sceneCoordinator: SceneCoordinator) {
|
||||||
|
self.sceneCoordinator = sceneCoordinator
|
||||||
|
self.context = appContext
|
||||||
|
self.authContext = authContext
|
||||||
|
|
||||||
|
overviewViewController = SearchResultsOverviewTableViewController(appContext: appContext, authContext: authContext, sceneCoordinator: sceneCoordinator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func start() {
|
||||||
|
overviewViewController.delegate = self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SearchResultOverviewCoordinator: SearchResultsOverviewTableViewControllerDelegate {
|
||||||
|
@MainActor
|
||||||
|
func searchForPosts(_ viewController: SearchResultsOverviewTableViewController, withSearchText searchText: String) {
|
||||||
|
let searchResultViewModel = SearchResultViewModel(context: context, authContext: authContext, searchScope: .posts)
|
||||||
|
searchResultViewModel.searchText.value = searchText
|
||||||
|
|
||||||
|
sceneCoordinator.present(scene: .searchResult(viewModel: searchResultViewModel), transition: .show)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPosts(_ viewController: SearchResultsOverviewTableViewController, tag: Mastodon.Entity.Tag) {
|
||||||
|
Task {
|
||||||
|
await DataSourceFacade.coordinateToHashtagScene(
|
||||||
|
provider: viewController,
|
||||||
|
tag: tag
|
||||||
|
)
|
||||||
|
|
||||||
|
await DataSourceFacade.responseToCreateSearchHistory(provider: viewController,
|
||||||
|
item: .hashtag(tag: .entity(tag)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func searchForPeople(_ viewController: SearchResultsOverviewTableViewController, withName searchText: String) {
|
||||||
|
let searchResultViewModel = SearchResultViewModel(context: context, authContext: authContext, searchScope: .people)
|
||||||
|
searchResultViewModel.searchText.value = searchText
|
||||||
|
|
||||||
|
sceneCoordinator.present(scene: .searchResult(viewModel: searchResultViewModel), transition: .show)
|
||||||
|
}
|
||||||
|
|
||||||
|
func goTo(_ viewController: SearchResultsOverviewTableViewController, urlString: String) {
|
||||||
|
|
||||||
|
let query = Mastodon.API.V2.Search.Query(
|
||||||
|
q: urlString,
|
||||||
|
type: .default,
|
||||||
|
resolve: true
|
||||||
|
)
|
||||||
|
|
||||||
|
let authContext = self.authContext
|
||||||
|
let managedObjectContext = context.managedObjectContext
|
||||||
|
|
||||||
|
Task {
|
||||||
|
let searchResult = try await context.apiService.search(
|
||||||
|
query: query,
|
||||||
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
).value
|
||||||
|
|
||||||
|
if let account = searchResult.accounts.first {
|
||||||
|
showProfile(viewController, for: account)
|
||||||
|
} else if let status = searchResult.statuses.first {
|
||||||
|
|
||||||
|
let status = try await managedObjectContext.perform {
|
||||||
|
return Persistence.Status.fetch(in: managedObjectContext, context: Persistence.Status.PersistContext(
|
||||||
|
domain: authContext.mastodonAuthenticationBox.domain,
|
||||||
|
entity: status,
|
||||||
|
me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user,
|
||||||
|
statusCache: nil,
|
||||||
|
userCache: nil,
|
||||||
|
networkDate: Date()))
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let status else { return }
|
||||||
|
|
||||||
|
await DataSourceFacade.coordinateToStatusThreadScene(
|
||||||
|
provider: viewController,
|
||||||
|
target: .status, // remove reblog wrapper
|
||||||
|
status: status.asRecord
|
||||||
|
)
|
||||||
|
} else if let url = URL(string: urlString) {
|
||||||
|
let prefixedURL: URL?
|
||||||
|
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
|
||||||
|
if components.scheme == nil {
|
||||||
|
components.scheme = "https"
|
||||||
|
}
|
||||||
|
prefixedURL = components.url
|
||||||
|
} else {
|
||||||
|
prefixedURL = url
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let prefixedURL else { return }
|
||||||
|
|
||||||
|
await sceneCoordinator.present(scene: .safari(url: prefixedURL), transition: .safariPresent(animated: true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let user {
|
||||||
|
await DataSourceFacade.coordinateToProfileScene(provider: viewController,
|
||||||
|
user: user.asRecord)
|
||||||
|
|
||||||
|
await DataSourceFacade.responseToCreateSearchHistory(provider: viewController,
|
||||||
|
item: .user(record: user.asRecord))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchForPerson(_ viewController: SearchResultsOverviewTableViewController, username: String, domain: String) {
|
||||||
|
let acct = "\(username)@\(domain)"
|
||||||
|
let query = Mastodon.API.V2.Search.Query(
|
||||||
|
q: acct,
|
||||||
|
type: .default,
|
||||||
|
resolve: true
|
||||||
|
)
|
||||||
|
|
||||||
|
Task {
|
||||||
|
let searchResult = try await context.apiService.search(
|
||||||
|
query: query,
|
||||||
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
|
).value
|
||||||
|
|
||||||
|
if let account = searchResult.accounts.first(where: { $0.acctWithDomainIfMissing(domain).lowercased() == acct.lowercased() }) {
|
||||||
|
showProfile(viewController, for: account)
|
||||||
|
} else {
|
||||||
|
await MainActor.run {
|
||||||
|
let alertTitle = L10n.Scene.Search.Searching.NoUser.title
|
||||||
|
let alertMessage = L10n.Scene.Search.Searching.NoUser.message(username, domain)
|
||||||
|
|
||||||
|
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
|
||||||
|
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
||||||
|
alertController.addAction(okAction)
|
||||||
|
sceneCoordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,22 +5,32 @@ import MastodonCore
|
||||||
import MastodonSDK
|
import MastodonSDK
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
|
|
||||||
// we could move lots of this stuff to a coordinator, it's too much for work a viewcontroller
|
protocol SearchResultsOverviewTableViewControllerDelegate: AnyObject {
|
||||||
|
func goTo(_ viewController: SearchResultsOverviewTableViewController, urlString: String)
|
||||||
|
func showPosts(_ viewController: SearchResultsOverviewTableViewController, tag: Mastodon.Entity.Tag)
|
||||||
|
func searchForPosts(_ viewController: SearchResultsOverviewTableViewController, withSearchText searchText: String)
|
||||||
|
func searchForPeople(_ viewController: SearchResultsOverviewTableViewController, withName searchText: String)
|
||||||
|
func showProfile(_ viewController: SearchResultsOverviewTableViewController, for account: Mastodon.Entity.Account)
|
||||||
|
func searchForPerson(_ viewController: SearchResultsOverviewTableViewController, username: String, domain: String)
|
||||||
|
}
|
||||||
|
|
||||||
class SearchResultsOverviewTableViewController: UIViewController, NeedsDependency, AuthContextProvider {
|
class SearchResultsOverviewTableViewController: UIViewController, NeedsDependency, AuthContextProvider {
|
||||||
var context: AppContext!
|
|
||||||
let authContext: AuthContext
|
let authContext: AuthContext
|
||||||
|
var context: AppContext!
|
||||||
var coordinator: SceneCoordinator!
|
var coordinator: SceneCoordinator!
|
||||||
|
|
||||||
private let tableView: UITableView
|
private let tableView: UITableView
|
||||||
var dataSource: UITableViewDiffableDataSource<SearchResultOverviewSection, SearchResultOverviewItem>?
|
var dataSource: UITableViewDiffableDataSource<SearchResultOverviewSection, SearchResultOverviewItem>?
|
||||||
|
|
||||||
|
weak var delegate: SearchResultsOverviewTableViewControllerDelegate?
|
||||||
|
|
||||||
var activeTask: Task<Void, Never>?
|
var activeTask: Task<Void, Never>?
|
||||||
|
|
||||||
init(appContext: AppContext, authContext: AuthContext, coordinator: SceneCoordinator) {
|
init(appContext: AppContext, authContext: AuthContext, sceneCoordinator: SceneCoordinator) {
|
||||||
|
|
||||||
self.context = appContext
|
|
||||||
self.authContext = authContext
|
self.authContext = authContext
|
||||||
self.coordinator = coordinator
|
self.context = appContext
|
||||||
|
self.coordinator = sceneCoordinator
|
||||||
|
|
||||||
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
tableView = UITableView(frame: .zero, style: .insetGrouped)
|
||||||
tableView.translatesAutoresizingMaskIntoConstraints = false
|
tableView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -160,145 +170,6 @@ class SearchResultsOverviewTableViewController: UIViewController, NeedsDependenc
|
||||||
|
|
||||||
activeTask = searchTask
|
activeTask = searchTask
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: - Actions
|
|
||||||
|
|
||||||
func showPosts(tag: Mastodon.Entity.Tag) {
|
|
||||||
Task {
|
|
||||||
await DataSourceFacade.coordinateToHashtagScene(
|
|
||||||
provider: self,
|
|
||||||
tag: tag
|
|
||||||
)
|
|
||||||
|
|
||||||
await DataSourceFacade.responseToCreateSearchHistory(provider: self,
|
|
||||||
item: .hashtag(tag: .entity(tag)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func showProfile(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()
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let user {
|
|
||||||
await DataSourceFacade.coordinateToProfileScene(provider:self,
|
|
||||||
user: user.asRecord)
|
|
||||||
|
|
||||||
await DataSourceFacade.responseToCreateSearchHistory(provider: self,
|
|
||||||
item: .user(record: user.asRecord))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchForPeople(withName searchText: String) {
|
|
||||||
let searchResultViewModel = SearchResultViewModel(context: context, authContext: authContext, searchScope: .people)
|
|
||||||
searchResultViewModel.searchText.value = searchText
|
|
||||||
|
|
||||||
coordinator.present(scene: .searchResult(viewModel: searchResultViewModel), transition: .show)
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchForPosts(withSearchText searchText: String) {
|
|
||||||
let searchResultViewModel = SearchResultViewModel(context: context, authContext: authContext, searchScope: .posts)
|
|
||||||
searchResultViewModel.searchText.value = searchText
|
|
||||||
|
|
||||||
coordinator.present(scene: .searchResult(viewModel: searchResultViewModel), transition: .show)
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchForPerson(username: String, domain: String) {
|
|
||||||
let acct = "\(username)@\(domain)"
|
|
||||||
let query = Mastodon.API.V2.Search.Query(
|
|
||||||
q: acct,
|
|
||||||
type: .default,
|
|
||||||
resolve: true
|
|
||||||
)
|
|
||||||
|
|
||||||
Task {
|
|
||||||
let searchResult = try await context.apiService.search(
|
|
||||||
query: query,
|
|
||||||
authenticationBox: authContext.mastodonAuthenticationBox
|
|
||||||
).value
|
|
||||||
|
|
||||||
if let account = searchResult.accounts.first(where: { $0.acctWithDomainIfMissing(domain).lowercased() == acct.lowercased() }) {
|
|
||||||
showProfile(for: account)
|
|
||||||
} else {
|
|
||||||
await MainActor.run {
|
|
||||||
let alertTitle = L10n.Scene.Search.Searching.NoUser.title
|
|
||||||
let alertMessage = L10n.Scene.Search.Searching.NoUser.message(username, domain)
|
|
||||||
|
|
||||||
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
|
|
||||||
let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default)
|
|
||||||
alertController.addAction(okAction)
|
|
||||||
coordinator.present(scene: .alertController(alertController: alertController), transition: .alertController(animated: true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func goTo(link: String) {
|
|
||||||
|
|
||||||
let query = Mastodon.API.V2.Search.Query(
|
|
||||||
q: link,
|
|
||||||
type: .default,
|
|
||||||
resolve: true
|
|
||||||
)
|
|
||||||
|
|
||||||
let authContext = self.authContext
|
|
||||||
let managedObjectContext = context.managedObjectContext
|
|
||||||
|
|
||||||
Task {
|
|
||||||
let searchResult = try await context.apiService.search(
|
|
||||||
query: query,
|
|
||||||
authenticationBox: authContext.mastodonAuthenticationBox
|
|
||||||
).value
|
|
||||||
|
|
||||||
if let account = searchResult.accounts.first {
|
|
||||||
showProfile(for: account)
|
|
||||||
} else if let status = searchResult.statuses.first {
|
|
||||||
|
|
||||||
let status = try await managedObjectContext.perform {
|
|
||||||
return Persistence.Status.fetch(in: managedObjectContext, context: Persistence.Status.PersistContext(
|
|
||||||
domain: authContext.mastodonAuthenticationBox.domain,
|
|
||||||
entity: status,
|
|
||||||
me: authContext.mastodonAuthenticationBox.authenticationRecord.object(in: managedObjectContext)?.user,
|
|
||||||
statusCache: nil,
|
|
||||||
userCache: nil,
|
|
||||||
networkDate: Date()))
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let status else { return }
|
|
||||||
|
|
||||||
await DataSourceFacade.coordinateToStatusThreadScene(
|
|
||||||
provider: self,
|
|
||||||
target: .status, // remove reblog wrapper
|
|
||||||
status: status.asRecord
|
|
||||||
)
|
|
||||||
} else if var url = URL(string: link) {
|
|
||||||
let prefixedURL: URL?
|
|
||||||
if var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
|
|
||||||
if components.scheme == nil {
|
|
||||||
components.scheme = "https"
|
|
||||||
}
|
|
||||||
prefixedURL = components.url
|
|
||||||
} else {
|
|
||||||
prefixedURL = url
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let prefixedURL else { return }
|
|
||||||
|
|
||||||
coordinator.present(scene: .safari(url: prefixedURL), transition: .safariPresent(animated: true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: UITableViewDelegate
|
//MARK: UITableViewDelegate
|
||||||
|
@ -313,21 +184,21 @@ extension SearchResultsOverviewTableViewController: UITableViewDelegate {
|
||||||
case .default(let defaultSectionEntry):
|
case .default(let defaultSectionEntry):
|
||||||
switch defaultSectionEntry {
|
switch defaultSectionEntry {
|
||||||
case .posts(let searchText):
|
case .posts(let searchText):
|
||||||
searchForPosts(withSearchText: searchText)
|
delegate?.searchForPosts(self, withSearchText: searchText)
|
||||||
case .people(let searchText):
|
case .people(let searchText):
|
||||||
searchForPeople(withName: searchText)
|
delegate?.searchForPeople(self, withName: searchText)
|
||||||
case .profile(let username, let domain):
|
case .profile(let username, let domain):
|
||||||
searchForPerson(username: username, domain: domain)
|
delegate?.searchForPerson(self, username: username, domain: domain)
|
||||||
case .openLink(let urlString):
|
case .openLink(let urlString):
|
||||||
goTo(link: urlString)
|
delegate?.goTo(self, urlString: urlString)
|
||||||
}
|
}
|
||||||
case .suggestion(let suggestionSectionEntry):
|
case .suggestion(let suggestionSectionEntry):
|
||||||
switch suggestionSectionEntry {
|
switch suggestionSectionEntry {
|
||||||
|
|
||||||
case .hashtag(let tag):
|
case .hashtag(let tag):
|
||||||
showPosts(tag: tag)
|
delegate?.showPosts(self, tag: tag)
|
||||||
case .profile(let account):
|
case .profile(let account):
|
||||||
showProfile(for: account)
|
delegate?.showProfile(self, for: account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ final class SearchDetailViewController: UIViewController, NeedsDependency {
|
||||||
|
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
var observations = Set<NSKeyValueObservation>()
|
var observations = Set<NSKeyValueObservation>()
|
||||||
|
let searchResultOverviewCoordinator: SearchResultOverviewCoordinator
|
||||||
|
|
||||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||||
|
@ -82,11 +83,28 @@ final class SearchDetailViewController: UIViewController, NeedsDependency {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private(set) lazy var searchResultsOverviewViewController: SearchResultsOverviewTableViewController = {
|
private(set) lazy var searchResultsOverviewViewController: SearchResultsOverviewTableViewController = {
|
||||||
let searchResultsOverviewViewController = SearchResultsOverviewTableViewController(appContext: context, authContext: viewModel.authContext, coordinator: coordinator)
|
return searchResultOverviewCoordinator.overviewViewController
|
||||||
return searchResultsOverviewViewController
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
//MARK: - init
|
||||||
|
|
||||||
|
init(appContext: AppContext, sceneCoordinator: SceneCoordinator, authContext: AuthContext) {
|
||||||
|
self.context = appContext
|
||||||
|
self.coordinator = sceneCoordinator
|
||||||
|
|
||||||
|
self.searchResultOverviewCoordinator = SearchResultOverviewCoordinator(appContext: appContext, authContext: authContext, sceneCoordinator: sceneCoordinator)
|
||||||
|
|
||||||
|
super.init(nibName: nil, bundle: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
|
//MARK: - UIViewController
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
|
|
||||||
|
searchResultOverviewCoordinator.start()
|
||||||
|
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
setupBackgroundColor(theme: ThemeService.shared.currentTheme.value)
|
||||||
|
|
|
@ -11,7 +11,7 @@ import MastodonAsset
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
|
||||||
protocol SearchHistorySectionHeaderCollectionReusableViewDelegate: AnyObject, UserViewDelegate {
|
protocol SearchHistorySectionHeaderCollectionReusableViewDelegate: AnyObject {
|
||||||
func searchHistorySectionHeaderCollectionReusableView(_ searchHistorySectionHeaderCollectionReusableView: SearchHistorySectionHeaderCollectionReusableView, clearButtonDidPressed button: UIButton)
|
func searchHistorySectionHeaderCollectionReusableView(_ searchHistorySectionHeaderCollectionReusableView: SearchHistorySectionHeaderCollectionReusableView, clearButtonDidPressed button: UIButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -125,5 +125,3 @@ extension SearchHistoryViewController: SearchHistorySectionHeaderCollectionReusa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SearchHistoryViewController: UserTableViewCellDelegate {}
|
|
||||||
|
|
Loading…
Reference in New Issue