mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
Show up to three hashtags and up to three users (IOS-141)
This commit is contained in:
parent
2e384f3cb5
commit
ed11d01267
@ -87,11 +87,8 @@ extension HomeTimelineViewModel {
|
||||
|
||||
let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
|
||||
if !hasChanges && !self.hasPendingStatusEditReload {
|
||||
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): snapshot not changes")
|
||||
self.didLoadLatest.send()
|
||||
return
|
||||
} else {
|
||||
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): snapshot has changes")
|
||||
}
|
||||
|
||||
guard let difference = self.calculateReloadSnapshotDifference(
|
||||
@ -101,7 +98,6 @@ extension HomeTimelineViewModel {
|
||||
) else {
|
||||
self.updateSnapshotUsingReloadData(snapshot: newSnapshot)
|
||||
self.didLoadLatest.send()
|
||||
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): applied new snapshot")
|
||||
return
|
||||
}
|
||||
|
||||
@ -111,7 +107,6 @@ extension HomeTimelineViewModel {
|
||||
contentOffset.y = tableView.contentOffset.y - difference.sourceDistanceToTableViewTopEdge
|
||||
tableView.setContentOffset(contentOffset, animated: false)
|
||||
self.didLoadLatest.send()
|
||||
self.logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): applied new snapshot")
|
||||
self.hasPendingStatusEditReload = false
|
||||
} // end Task
|
||||
}
|
||||
|
@ -48,24 +48,37 @@ enum SearchResultOverviewItem: Hashable {
|
||||
}
|
||||
|
||||
enum SuggestionSectionEntry: Hashable {
|
||||
//TODO: Use User instead
|
||||
case hashtag(tag: Mastodon.Entity.Tag)
|
||||
case profile(ManagedObjectRecord<MastodonUser>)
|
||||
case profile(user: Mastodon.Entity.Account)
|
||||
|
||||
var title: String? {
|
||||
if case let .hashtag(tag) = self {
|
||||
return tag.name
|
||||
} else {
|
||||
return nil
|
||||
switch self {
|
||||
|
||||
case .hashtag(tag: let tag):
|
||||
return tag.name
|
||||
case .profile(user: let user):
|
||||
return "\(user.displayName) — \(user.acct)"
|
||||
}
|
||||
// if case let .hashtag(tag) = self {
|
||||
// return tag.name
|
||||
// } else {
|
||||
// return nil
|
||||
// }
|
||||
}
|
||||
|
||||
var icon: UIImage? {
|
||||
if case let .hashtag(tag) = self {
|
||||
return UIImage(systemName: "number")
|
||||
} else {
|
||||
return nil
|
||||
switch self {
|
||||
|
||||
case .hashtag(tag: _):
|
||||
return UIImage(systemName: "number")
|
||||
|
||||
case .profile(user: _):
|
||||
return UIImage(systemName: "person.circle")
|
||||
|
||||
}
|
||||
// if case let .hashtag(tag) = self {
|
||||
// } else {
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ class SearchResultsOverviewTableViewController: UIViewController {
|
||||
var dataSource: UITableViewDiffableDataSource<SearchResultOverviewSection, SearchResultOverviewItem>?
|
||||
|
||||
weak var delegate: SearchResultsOverviewTableViewControllerDeleagte?
|
||||
var activeTask: Task<Void, Never>?
|
||||
|
||||
init(appContext: AppContext, authContext: AuthContext) {
|
||||
|
||||
@ -48,22 +49,29 @@ class SearchResultsOverviewTableViewController: UIViewController {
|
||||
return cell
|
||||
|
||||
case .suggestion(let suggestion):
|
||||
switch suggestion {
|
||||
|
||||
case .hashtag(let hashtag):
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultDefaultSectionTableViewCell.reuseIdentifier, for: indexPath) as? SearchResultDefaultSectionTableViewCell else { fatalError() }
|
||||
|
||||
cell.configure(item: .hashtag(tag: hashtag))
|
||||
return cell
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultDefaultSectionTableViewCell.reuseIdentifier, for: indexPath) as? SearchResultDefaultSectionTableViewCell else { fatalError() }
|
||||
|
||||
case .profile(let profile):
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as? UserTableViewCell else { fatalError() }
|
||||
|
||||
// cell.configure(me: <#T##MastodonUser?#>, tableView: <#T##UITableView#>, viewModel: <#T##UserTableViewCell.ViewModel#>, delegate: <#T##UserTableViewCellDelegate?#>)
|
||||
|
||||
return cell
|
||||
}
|
||||
cell.configure(item: suggestion)
|
||||
return cell
|
||||
|
||||
// switch suggestion {
|
||||
//
|
||||
// case .hashtag(let hashtag):
|
||||
// guard let cell = tableView.dequeueReusableCell(withIdentifier: SearchResultDefaultSectionTableViewCell.reuseIdentifier, for: indexPath) as? SearchResultDefaultSectionTableViewCell else { fatalError() }
|
||||
//
|
||||
// cell.configure(item: .hashtag(tag: hashtag))
|
||||
// return cell
|
||||
//
|
||||
// case .profile(let profile):
|
||||
// //TODO: Use `UserFetchedResultsController` or `Persistence.MastodonUser.fetch` ???
|
||||
// guard let cell = tableView.dequeueReusableCell(withIdentifier: UserTableViewCell.reuseIdentifier, for: indexPath) as? UserTableViewCell else { fatalError() }
|
||||
//
|
||||
//// cell.configure(me: <#T##MastodonUser?#>, tableView: <#T##UITableView#>, viewModel: <#T##UserTableViewCell.ViewModel#>, delegate: <#T##UserTableViewCellDelegate?#>)
|
||||
//
|
||||
// return cell
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,9 +87,20 @@ class SearchResultsOverviewTableViewController: UIViewController {
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||
|
||||
func showStandardSearch(for searchText: String) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
var snapshot = NSDiffableDataSourceSnapshot<SearchResultOverviewSection, SearchResultOverviewItem>()
|
||||
snapshot.appendSections([.default, .suggestions])
|
||||
dataSource?.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
func showStandardSearch(for searchText: String) {
|
||||
|
||||
guard let dataSource else { return }
|
||||
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .default))
|
||||
snapshot.appendItems([.default(.posts(searchText)),
|
||||
.default(.people(searchText)),
|
||||
.default(.profile(searchText, authContext.mastodonAuthenticationBox.domain))], toSection: .default)
|
||||
@ -90,18 +109,29 @@ class SearchResultsOverviewTableViewController: UIViewController {
|
||||
//TODO: Check if Mastodon-URL
|
||||
snapshot.appendItems([.default(.openLink(searchText))], toSection: .default)
|
||||
}
|
||||
dataSource?.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
func searchForSuggestions(for searchText: String) {
|
||||
|
||||
activeTask?.cancel()
|
||||
|
||||
guard let dataSource else { return }
|
||||
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .suggestions))
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
|
||||
guard searchText.isNotEmpty else { return }
|
||||
|
||||
let query = Mastodon.API.V2.Search.Query(
|
||||
q: searchText,
|
||||
type: .default,
|
||||
resolve: true
|
||||
)
|
||||
|
||||
Task {
|
||||
let searchTask = Task {
|
||||
do {
|
||||
let searchResult = try await appContext.apiService.search(
|
||||
query: query,
|
||||
@ -111,22 +141,29 @@ class SearchResultsOverviewTableViewController: UIViewController {
|
||||
let firstThreeHashtags = searchResult.hashtags.prefix(3)
|
||||
let firstThreeUsers = searchResult.accounts.prefix(3)
|
||||
|
||||
guard var snapshot = dataSource?.snapshot() else { return }
|
||||
var snapshot = dataSource.snapshot()
|
||||
|
||||
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: .suggestions))
|
||||
snapshot.appendItems(firstThreeHashtags.map { .suggestion(.hashtag(tag: $0)) }, toSection: .suggestions )
|
||||
// snapshot.appendItems(firstThreeUsers.map { .suggestion(.profile($0.displayName)) }, toSection: .suggestions )
|
||||
|
||||
await MainActor.run {
|
||||
dataSource?.apply(snapshot, animatingDifferences: false)
|
||||
if firstThreeHashtags.isNotEmpty {
|
||||
snapshot.appendItems(firstThreeHashtags.map { .suggestion(.hashtag(tag: $0)) }, toSection: .suggestions )
|
||||
}
|
||||
|
||||
if firstThreeUsers.isNotEmpty {
|
||||
snapshot.appendItems(firstThreeUsers.map { .suggestion(.profile(user: $0)) }, toSection: .suggestions )
|
||||
}
|
||||
|
||||
guard Task.isCancelled == false else { return }
|
||||
|
||||
await MainActor.run {
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
} catch {
|
||||
// do nothing
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
activeTask = searchTask
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,29 +131,6 @@ final class SearchDetailViewController: UIViewController, NeedsDependency {
|
||||
searchResultsOverviewViewController.view.pinToParent()
|
||||
}
|
||||
|
||||
// bind search trigger
|
||||
// "local" search
|
||||
viewModel.searchText
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] searchText in
|
||||
guard let self else { return }
|
||||
|
||||
self.searchResultsOverviewViewController.showStandardSearch(for: searchText)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// delayed search on server
|
||||
viewModel.searchText
|
||||
.removeDuplicates()
|
||||
.throttle(for: 0.5, scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] searchText in
|
||||
guard let self else { return }
|
||||
|
||||
self.searchResultsOverviewViewController.searchForSuggestions(for: searchText)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// bind search history display
|
||||
viewModel.searchText
|
||||
.removeDuplicates()
|
||||
@ -263,7 +240,11 @@ extension SearchDetailViewController {
|
||||
extension SearchDetailViewController: UISearchBarDelegate {
|
||||
|
||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||
viewModel.searchText.value = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let trimmedSearchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
viewModel.searchText.value = trimmedSearchText
|
||||
|
||||
searchResultsOverviewViewController.showStandardSearch(for: trimmedSearchText)
|
||||
searchResultsOverviewViewController.searchForSuggestions(for: trimmedSearchText)
|
||||
}
|
||||
|
||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||
|
@ -208,7 +208,6 @@ extension Mastodon.API {
|
||||
return try Mastodon.API.decoder.decode(type, from: data)
|
||||
} catch let decodeError {
|
||||
#if DEBUG
|
||||
os_log(.info, "%{public}s[%{public}ld], %{public}s: decode fail. content %s", ((#file as NSString).lastPathComponent), #line, #function, String(data: data, encoding: .utf8) ?? "<nil>")
|
||||
debugPrint(decodeError)
|
||||
#endif
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user