mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-22 00:01:00 +00:00
More results footers
This commit is contained in:
parent
cb6032bf4f
commit
93e4fa7496
19 changed files with 154 additions and 28 deletions
|
@ -508,7 +508,8 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func publisher(results: Results) -> AnyPublisher<[CollectionSection], Error> {
|
// swiftlint:disable:next function_body_length
|
||||||
|
func publisher(results: Results, limit: Int?) -> AnyPublisher<[CollectionSection], Error> {
|
||||||
let accountIds = results.accounts.map(\.id)
|
let accountIds = results.accounts.map(\.id)
|
||||||
let statusIds = results.statuses.map(\.id)
|
let statusIds = results.statuses.map(\.id)
|
||||||
|
|
||||||
|
@ -518,12 +519,18 @@ public extension ContentDatabase {
|
||||||
.fetchAll)
|
.fetchAll)
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.publisher(in: databaseWriter)
|
.publisher(in: databaseWriter)
|
||||||
.map {
|
.map { infos -> [CollectionItem] in
|
||||||
$0.sorted {
|
var accounts = infos.sorted {
|
||||||
accountIds.firstIndex(of: $0.record.id) ?? 0
|
accountIds.firstIndex(of: $0.record.id) ?? 0
|
||||||
< accountIds.firstIndex(of: $1.record.id) ?? 0
|
< accountIds.firstIndex(of: $1.record.id) ?? 0
|
||||||
}
|
}
|
||||||
.map { CollectionItem.account(.init(info: $0)) }
|
.map { CollectionItem.account(.init(info: $0)) }
|
||||||
|
|
||||||
|
if let limit = limit, accounts.count >= limit {
|
||||||
|
accounts.append(.moreResults(.init(scope: .accounts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
let statusesPublisher = ValueObservation.tracking(
|
let statusesPublisher = ValueObservation.tracking(
|
||||||
|
@ -532,8 +539,8 @@ public extension ContentDatabase {
|
||||||
.fetchAll)
|
.fetchAll)
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.publisher(in: databaseWriter)
|
.publisher(in: databaseWriter)
|
||||||
.map {
|
.map { infos -> [CollectionItem] in
|
||||||
$0.sorted {
|
var statuses = infos.sorted {
|
||||||
statusIds.firstIndex(of: $0.record.id) ?? 0
|
statusIds.firstIndex(of: $0.record.id) ?? 0
|
||||||
< statusIds.firstIndex(of: $1.record.id) ?? 0
|
< statusIds.firstIndex(of: $1.record.id) ?? 0
|
||||||
}
|
}
|
||||||
|
@ -543,13 +550,25 @@ public extension ContentDatabase {
|
||||||
.init(showContentToggled: $0.showContentToggled,
|
.init(showContentToggled: $0.showContentToggled,
|
||||||
showAttachmentsToggled: $0.showAttachmentsToggled))
|
showAttachmentsToggled: $0.showAttachmentsToggled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let limit = limit, statuses.count >= limit {
|
||||||
|
statuses.append(.moreResults(.init(scope: .statuses)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hashtags = results.hashtags.map(CollectionItem.tag)
|
||||||
|
|
||||||
|
if let limit = limit, hashtags.count >= limit {
|
||||||
|
hashtags.append(.moreResults(.init(scope: .tags)))
|
||||||
|
}
|
||||||
|
|
||||||
return accountsPublisher.combineLatest(statusesPublisher)
|
return accountsPublisher.combineLatest(statusesPublisher)
|
||||||
.map { accounts, statuses in
|
.map { accounts, statuses in
|
||||||
[.init(items: accounts, titleLocalizedStringKey: "search.scope.accounts"),
|
[.init(items: accounts, titleLocalizedStringKey: "search.scope.accounts"),
|
||||||
.init(items: statuses, titleLocalizedStringKey: "search.scope.statuses"),
|
.init(items: statuses, titleLocalizedStringKey: "search.scope.statuses"),
|
||||||
.init(items: results.hashtags.map(CollectionItem.tag), titleLocalizedStringKey: "search.scope.tags")]
|
.init(items: hashtags, titleLocalizedStringKey: "search.scope.tags")]
|
||||||
.filter { !$0.items.isEmpty }
|
.filter { !$0.items.isEmpty }
|
||||||
}
|
}
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
|
|
|
@ -9,6 +9,7 @@ public enum CollectionItem: Hashable {
|
||||||
case notification(MastodonNotification, StatusConfiguration?)
|
case notification(MastodonNotification, StatusConfiguration?)
|
||||||
case conversation(Conversation)
|
case conversation(Conversation)
|
||||||
case tag(Tag)
|
case tag(Tag)
|
||||||
|
case moreResults(MoreResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension CollectionItem {
|
public extension CollectionItem {
|
||||||
|
@ -51,6 +52,8 @@ public extension CollectionItem {
|
||||||
return conversation.id
|
return conversation.id
|
||||||
case let .tag(tag):
|
case let .tag(tag):
|
||||||
return tag.name
|
return tag.name
|
||||||
|
case .moreResults:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
DB/Sources/DB/Entities/MoreResults.swift
Normal file
7
DB/Sources/DB/Entities/MoreResults.swift
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct MoreResults: Hashable {
|
||||||
|
public let scope: SearchScope
|
||||||
|
}
|
10
DB/Sources/DB/Entities/SearchScope.swift
Normal file
10
DB/Sources/DB/Entities/SearchScope.swift
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum SearchScope: Int, CaseIterable {
|
||||||
|
case all
|
||||||
|
case accounts
|
||||||
|
case statuses
|
||||||
|
case tags
|
||||||
|
}
|
|
@ -30,6 +30,13 @@ final class TableViewDataSource: UITableViewDiffableDataSource<CollectionSection
|
||||||
conversationListCell.viewModel = conversationViewModel
|
conversationListCell.viewModel = conversationViewModel
|
||||||
case let (tagTableViewCell as TagTableViewCell, tagViewModel as TagViewModel):
|
case let (tagTableViewCell as TagTableViewCell, tagViewModel as TagViewModel):
|
||||||
tagTableViewCell.viewModel = tagViewModel
|
tagTableViewCell.viewModel = tagViewModel
|
||||||
|
case let (_, moreResultsViewModel as MoreResultsViewModel):
|
||||||
|
var configuration = cell.defaultContentConfiguration()
|
||||||
|
|
||||||
|
configuration.text = moreResultsViewModel.scope.moreDescription
|
||||||
|
|
||||||
|
cell.contentConfiguration = configuration
|
||||||
|
cell.accessoryType = .disclosureIndicator
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ extension CollectionItem {
|
||||||
LoadMoreCell.self,
|
LoadMoreCell.self,
|
||||||
NotificationListCell.self,
|
NotificationListCell.self,
|
||||||
ConversationListCell.self,
|
ConversationListCell.self,
|
||||||
TagTableViewCell.self]
|
TagTableViewCell.self,
|
||||||
|
UITableViewCell.self]
|
||||||
|
|
||||||
var cellClass: AnyClass {
|
var cellClass: AnyClass {
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -26,6 +27,8 @@ extension CollectionItem {
|
||||||
return ConversationListCell.self
|
return ConversationListCell.self
|
||||||
case .tag:
|
case .tag:
|
||||||
return TagTableViewCell.self
|
return TagTableViewCell.self
|
||||||
|
case .moreResults:
|
||||||
|
return UITableViewCell.self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +57,8 @@ extension CollectionItem {
|
||||||
conversation: conversation)
|
conversation: conversation)
|
||||||
case let .tag(tag):
|
case let .tag(tag):
|
||||||
return TagView.estimatedHeight(width: width, tag: tag)
|
return TagView.estimatedHeight(width: width, tag: tag)
|
||||||
|
case .moreResults:
|
||||||
|
return UITableView.automaticDimension
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
extension SearchViewModel.Scope {
|
extension SearchScope {
|
||||||
var title: String {
|
var title: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .all:
|
case .all:
|
||||||
|
@ -16,4 +16,17 @@ extension SearchViewModel.Scope {
|
||||||
return NSLocalizedString("search.scope.tags", comment: "")
|
return NSLocalizedString("search.scope.tags", comment: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var moreDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .all:
|
||||||
|
return nil
|
||||||
|
case .accounts:
|
||||||
|
return NSLocalizedString("more-results.accounts", comment: "")
|
||||||
|
case .statuses:
|
||||||
|
return NSLocalizedString("more-results.statuses", comment: "")
|
||||||
|
case .tags:
|
||||||
|
return NSLocalizedString("more-results.tags", comment: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -160,6 +160,9 @@
|
||||||
"filter.context.thread" = "Conversations";
|
"filter.context.thread" = "Conversations";
|
||||||
"filter.context.account" = "Profiles";
|
"filter.context.account" = "Profiles";
|
||||||
"filter.context.unknown" = "Unknown context";
|
"filter.context.unknown" = "Unknown context";
|
||||||
|
"more-results.accounts" = "More people";
|
||||||
|
"more-results.statuses" = "More posts";
|
||||||
|
"more-results.tags" = "More hashtags";
|
||||||
"notifications" = "Notifications";
|
"notifications" = "Notifications";
|
||||||
"notifications.reblogged-your-status" = "%@ boosted your status";
|
"notifications.reblogged-your-status" = "%@ boosted your status";
|
||||||
"notifications.favourited-your-status" = "%@ favorited your status";
|
"notifications.favourited-your-status" = "%@ favorited your status";
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
D08E52EE257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
||||||
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
D08E52EF257D757100FA2C5F /* CompositionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52ED257D757100FA2C5F /* CompositionView.swift */; };
|
||||||
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; };
|
D08E52F8257D78BE00FA2C5F /* ViewConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0EA59472522B8B600804347 /* ViewConstants.swift */; };
|
||||||
D097F41B25BE3E1A00859F2C /* SearchViewModel+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D097F41A25BE3E1A00859F2C /* SearchViewModel+Extensions.swift */; };
|
D097F41B25BE3E1A00859F2C /* SearchScope+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */; };
|
||||||
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
D0A1F4F7252E7D4B004435BF /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */; };
|
||||||
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
|
D0A7AC7325748BFF00E4E8AB /* ReportStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */; };
|
||||||
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
D0B32F50250B373600311912 /* RegistrationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B32F4F250B373600311912 /* RegistrationView.swift */; };
|
||||||
|
@ -269,7 +269,7 @@
|
||||||
D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareErrorViewController.swift; sourceTree = "<group>"; };
|
D08E52C6257C7AEE00FA2C5F /* ShareErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareErrorViewController.swift; sourceTree = "<group>"; };
|
||||||
D08E52D1257C811200FA2C5F /* ShareExtensionError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShareExtensionError+Extensions.swift"; sourceTree = "<group>"; };
|
D08E52D1257C811200FA2C5F /* ShareExtensionError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ShareExtensionError+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D08E52ED257D757100FA2C5F /* CompositionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionView.swift; sourceTree = "<group>"; };
|
D08E52ED257D757100FA2C5F /* CompositionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionView.swift; sourceTree = "<group>"; };
|
||||||
D097F41A25BE3E1A00859F2C /* SearchViewModel+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchViewModel+Extensions.swift"; sourceTree = "<group>"; };
|
D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SearchScope+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
D0A1F4F6252E7D4B004435BF /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = "<group>"; };
|
||||||
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
D0A7AC7225748BFF00E4E8AB /* ReportStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportStatusView.swift; sourceTree = "<group>"; };
|
||||||
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
D0AD03552505814D0085A466 /* Base16 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Base16; sourceTree = "<group>"; };
|
||||||
|
@ -606,7 +606,7 @@
|
||||||
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
D0C7D46B24F76169001EBDBB /* NSMutableAttributedString+Extensions.swift */,
|
||||||
D07EC7CE25B13921006DF726 /* PickerEmoji+Extensions.swift */,
|
D07EC7CE25B13921006DF726 /* PickerEmoji+Extensions.swift */,
|
||||||
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
|
D0B5FE9A251583DB00478838 /* ProfileCollection+Extensions.swift */,
|
||||||
D097F41A25BE3E1A00859F2C /* SearchViewModel+Extensions.swift */,
|
D097F41A25BE3E1A00859F2C /* SearchScope+Extensions.swift */,
|
||||||
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */,
|
D0849C7E25903C4900A5EBCC /* Status+Extensions.swift */,
|
||||||
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
D0C7D46A24F76169001EBDBB /* String+Extensions.swift */,
|
||||||
D07EC81025B232C2006DF726 /* SystemEmoji+Extensions.swift */,
|
D07EC81025B232C2006DF726 /* SystemEmoji+Extensions.swift */,
|
||||||
|
@ -864,7 +864,7 @@
|
||||||
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
D08B8D622540DE3B00B1EBEF /* ZoomTransitionController.swift in Sources */,
|
||||||
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
D0F0B12E251A97E400942152 /* TableViewController.swift in Sources */,
|
||||||
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
D0DD50CB256B1F24004A04F7 /* ReportView.swift in Sources */,
|
||||||
D097F41B25BE3E1A00859F2C /* SearchViewModel+Extensions.swift in Sources */,
|
D097F41B25BE3E1A00859F2C /* SearchScope+Extensions.swift in Sources */,
|
||||||
D035F8B325B9616000DC75ED /* Timeline+Extensions.swift in Sources */,
|
D035F8B325B9616000DC75ED /* Timeline+Extensions.swift in Sources */,
|
||||||
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
D0FE1C8F253686F9003EF1EB /* PlayerView.swift in Sources */,
|
||||||
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
D0CE9F87258B076900E3A6B6 /* AttachmentUploadView.swift in Sources */,
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import DB
|
||||||
|
|
||||||
|
public typealias MoreResults = DB.MoreResults
|
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import DB
|
||||||
|
|
||||||
|
public typealias SearchScope = DB.SearchScope
|
|
@ -10,6 +10,7 @@ public enum Navigation {
|
||||||
case url(URL)
|
case url(URL)
|
||||||
case collection(CollectionService)
|
case collection(CollectionService)
|
||||||
case profile(ProfileService)
|
case profile(ProfileService)
|
||||||
|
case searchScope(SearchScope)
|
||||||
case webfingerStart
|
case webfingerStart
|
||||||
case webfingerEnd
|
case webfingerEnd
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,12 @@ public struct SearchService {
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
|
||||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
sections = resultsSubject.scan(.empty) {
|
sections = resultsSubject.scan((.empty, nil)) {
|
||||||
let (results, search) = $1
|
let (results, search) = $1
|
||||||
|
|
||||||
return search.offset == nil ? results : $0.appending(results)
|
return (search.offset == nil ? results : $0.0.appending(results), search.limit)
|
||||||
}
|
}
|
||||||
.flatMap(contentDatabase.publisher(results:)).eraseToAnyPublisher()
|
.flatMap(contentDatabase.publisher(results:limit:)).eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// Copyright © 2021 Metabolist. All rights reserved.
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
import UIKit
|
import UIKit
|
||||||
import ViewModels
|
import ViewModels
|
||||||
|
|
||||||
|
@ -7,6 +8,7 @@ final class ExploreViewController: UICollectionViewController {
|
||||||
private let viewModel: ExploreViewModel
|
private let viewModel: ExploreViewModel
|
||||||
private let rootViewModel: RootViewModel
|
private let rootViewModel: RootViewModel
|
||||||
private let identification: Identification
|
private let identification: Identification
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(viewModel: ExploreViewModel, rootViewModel: RootViewModel, identification: Identification) {
|
init(viewModel: ExploreViewModel, rootViewModel: RootViewModel, identification: Identification) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
|
@ -40,15 +42,29 @@ final class ExploreViewController: UICollectionViewController {
|
||||||
|
|
||||||
let searchController = UISearchController(searchResultsController: searchResultsController)
|
let searchController = UISearchController(searchResultsController: searchResultsController)
|
||||||
|
|
||||||
searchController.searchBar.scopeButtonTitles = SearchViewModel.Scope.allCases.map(\.title)
|
searchController.searchBar.scopeButtonTitles = SearchScope.allCases.map(\.title)
|
||||||
searchController.searchResultsUpdater = self
|
searchController.searchResultsUpdater = self
|
||||||
navigationItem.searchController = searchController
|
navigationItem.searchController = searchController
|
||||||
|
|
||||||
|
viewModel.searchViewModel.events.sink { [weak self] in
|
||||||
|
if case let .navigation(navigation) = $0,
|
||||||
|
case let .searchScope(scope) = navigation {
|
||||||
|
searchController.searchBar.selectedScopeButtonIndex = scope.rawValue
|
||||||
|
self?.updateSearchResults(for: searchController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExploreViewController: UISearchResultsUpdating {
|
extension ExploreViewController: UISearchResultsUpdating {
|
||||||
func updateSearchResults(for searchController: UISearchController) {
|
func updateSearchResults(for searchController: UISearchController) {
|
||||||
if let scope = SearchViewModel.Scope(rawValue: searchController.searchBar.selectedScopeButtonIndex) {
|
if let scope = SearchScope(rawValue: searchController.searchBar.selectedScopeButtonIndex) {
|
||||||
|
if scope != viewModel.searchViewModel.scope,
|
||||||
|
let scrollView = searchController.searchResultsController?.view as? UIScrollView {
|
||||||
|
scrollView.setContentOffset(.init(x: 0, y: -scrollView.safeAreaInsets.top), animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.searchViewModel.scope = scope
|
viewModel.searchViewModel.scope = scope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -372,6 +372,8 @@ private extension TableViewController {
|
||||||
}
|
}
|
||||||
case let .url(url):
|
case let .url(url):
|
||||||
present(SFSafariViewController(url: url), animated: true)
|
present(SFSafariViewController(url: url), animated: true)
|
||||||
|
case .searchScope:
|
||||||
|
break
|
||||||
case .webfingerStart:
|
case .webfingerStart:
|
||||||
webfingerIndicatorView.startAnimating()
|
webfingerIndicatorView.startAnimating()
|
||||||
case .webfingerEnd:
|
case .webfingerEnd:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Foundation
|
||||||
import Mastodon
|
import Mastodon
|
||||||
import ServiceLayer
|
import ServiceLayer
|
||||||
|
|
||||||
|
// swiftlint:disable file_length
|
||||||
public class CollectionItemsViewModel: ObservableObject {
|
public class CollectionItemsViewModel: ObservableObject {
|
||||||
@Published public var alertItem: AlertItem?
|
@Published public var alertItem: AlertItem?
|
||||||
public private(set) var nextPageMaxId: String?
|
public private(set) var nextPageMaxId: String?
|
||||||
|
@ -166,6 +167,8 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
.navigation(.collection(collectionService
|
.navigation(.collection(collectionService
|
||||||
.navigationService
|
.navigationService
|
||||||
.timelineService(timeline: .tag(tag.name)))))
|
.timelineService(timeline: .tag(tag.name)))))
|
||||||
|
case let .moreResults(moreResults):
|
||||||
|
eventsSubject.send(.navigation(.searchScope(moreResults.scope)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,6 +280,16 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
|
|
||||||
cache(viewModel: viewModel, forItem: item)
|
cache(viewModel: viewModel, forItem: item)
|
||||||
|
|
||||||
|
return viewModel
|
||||||
|
case let .moreResults(moreResults):
|
||||||
|
if let cachedViewModel = cachedViewModel {
|
||||||
|
return cachedViewModel
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewModel = MoreResultsViewModel(moreResults: moreResults)
|
||||||
|
|
||||||
|
cache(viewModel: viewModel, forItem: item)
|
||||||
|
|
||||||
return viewModel
|
return viewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,3 +418,4 @@ private extension CollectionItemsViewModel {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// swiftlint:enable file_length
|
||||||
|
|
5
ViewModels/Sources/ViewModels/Entities/SearchScope.swift
Normal file
5
ViewModels/Sources/ViewModels/Entities/SearchScope.swift
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public typealias SearchScope = ServiceLayer.SearchScope
|
20
ViewModels/Sources/ViewModels/MoreResultsViewModel.swift
Normal file
20
ViewModels/Sources/ViewModels/MoreResultsViewModel.swift
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright © 2020 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import ServiceLayer
|
||||||
|
|
||||||
|
public final class MoreResultsViewModel: ObservableObject, CollectionItemViewModel {
|
||||||
|
public var events: AnyPublisher<AnyPublisher<CollectionItemEvent, Error>, Never>
|
||||||
|
|
||||||
|
private let moreResults: MoreResults
|
||||||
|
private let eventsSubject = PassthroughSubject<AnyPublisher<CollectionItemEvent, Error>, Never>()
|
||||||
|
|
||||||
|
init(moreResults: MoreResults) {
|
||||||
|
self.moreResults = moreResults
|
||||||
|
events = eventsSubject.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension MoreResultsViewModel {
|
||||||
|
var scope: SearchScope { moreResults.scope }
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import ServiceLayer
|
||||||
|
|
||||||
public final class SearchViewModel: CollectionItemsViewModel {
|
public final class SearchViewModel: CollectionItemsViewModel {
|
||||||
@Published public var query = ""
|
@Published public var query = ""
|
||||||
@Published public var scope = Scope.all
|
@Published public var scope = SearchScope.all
|
||||||
|
|
||||||
private let searchService: SearchService
|
private let searchService: SearchService
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
@ -45,20 +45,11 @@ public final class SearchViewModel: CollectionItemsViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension SearchViewModel {
|
|
||||||
enum Scope: Int, CaseIterable {
|
|
||||||
case all
|
|
||||||
case accounts
|
|
||||||
case statuses
|
|
||||||
case tags
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension SearchViewModel {
|
private extension SearchViewModel {
|
||||||
static let throttleInterval: TimeInterval = 0.5
|
static let throttleInterval: TimeInterval = 0.5
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension SearchViewModel.Scope {
|
private extension SearchScope {
|
||||||
var type: Search.SearchType? {
|
var type: Search.SearchType? {
|
||||||
switch self {
|
switch self {
|
||||||
case .all:
|
case .all:
|
||||||
|
|
Loading…
Reference in a new issue