Skip to content

Commit

Permalink
[PP-725] Improve epub search (#360)
Browse files Browse the repository at this point in the history
* working implementation

* Correct search issues

* Update project.pbxproj
  • Loading branch information
mauricecarrier7 authored Nov 22, 2023
1 parent 25fccf9 commit fc7b5d6
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 43 deletions.
4 changes: 2 additions & 2 deletions Palace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -4676,7 +4676,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.35;
MARKETING_VERSION = 1.0.36;
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
PRODUCT_MODULE_NAME = Palace;
PRODUCT_NAME = "Palace-noDRM";
Expand Down Expand Up @@ -4733,7 +4733,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.35;
MARKETING_VERSION = 1.0.36;
PRODUCT_BUNDLE_IDENTIFIER = org.thepalaceproject.palace;
PRODUCT_MODULE_NAME = Palace;
PRODUCT_NAME = "Palace-noDRM";
Expand Down
36 changes: 10 additions & 26 deletions Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct EPUBSearchView: View {
@ViewBuilder private var searchBar: some View {
HStack {
TextField("\(Strings.Generic.search)...", text: $searchQuery)
.focused($isSearchFieldFocused) // Bind the focus state to the text field
.focused($isSearchFieldFocused)
Button(action: {
searchQuery = ""
viewModel.cancelSearch()
Expand All @@ -37,7 +37,6 @@ struct EPUBSearchView: View {
.padding(.bottom)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// This delay ensures that the view is fully loaded before focusing
isSearchFieldFocused = true
}
}
Expand All @@ -56,9 +55,9 @@ struct EPUBSearchView: View {
@ViewBuilder private var listView: some View {
ZStack {
List {
ForEach(groupedByChapterName(viewModel.results), id: \.key) { key, locators in
Section(header: sectionHeaderView(title: key)) {
ForEach(locators, id: \.href) { locator in
ForEach(viewModel.groupedResults, id: \.title) { section in
Section(header: sectionHeaderView(title: section.title)) {
ForEach(section.locators, id: \.self) { locator in
rowView(locator)
.onAppear(perform: {
if shouldFetchMoreResults(for: locator) {
Expand All @@ -82,28 +81,13 @@ struct EPUBSearchView: View {
}
}
}

private func shouldFetchMoreResults(for locator: Locator) -> Bool {
viewModel.results.last?.href == locator.href
}

private func groupedByChapterName(_ results: [Locator]) -> [(key: String, value: [Locator])] {
let hasTitles = results.contains { $0.title != nil && $0.title != "" }

if !hasTitles {
return [("", results)]
}

let uniqueTitles = Array(Set(results.compactMap { $0.title })).sorted { title1, title2 in
results.firstIndex(where: { $0.title == title1 })! < results.firstIndex(where: { $0.title == title2 })!
}

return uniqueTitles.compactMap { title -> (key: String, value: [Locator])? in
if let items = results.filter({ $0.title == title }) as [Locator]?, !items.isEmpty {
return (key: title, value: items)
}
return nil
private func shouldFetchMoreResults(for locator: Locator) -> Bool {
if let lastSection = viewModel.groupedResults.last,
let lastLocator = lastSection.locators.last {
return locator.href == lastLocator.href
}
return false
}

private func sectionHeaderView(title: String) -> some View {
Expand All @@ -123,7 +107,7 @@ struct EPUBSearchView: View {
EmptyView()
}
}

private func rowView(_ locator: Locator) -> some View {
let text = locator.text.sanitized()

Expand Down
76 changes: 61 additions & 15 deletions Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ final class EPUBSearchViewModel: ObservableObject {

@Published private(set) var state: State = .empty
@Published private(set) var results: [Locator] = []

@Published private(set) var groupedResults: [(title: String, locators: [Locator])] = []

private var publication: Publication
weak var delegate: EPUBSearchDelegate?

Expand All @@ -58,26 +59,20 @@ final class EPUBSearchViewModel: ObservableObject {

state = .starting(cancellable)
}

func fetchNextBatch() {
guard case let .idle(iterator, _) = state else { return }
guard case let .idle(iterator, _) = state else {
return
}

state = .loadingNext(iterator, nil)

let cancellable = iterator.next { result in
let cancellable = iterator.next { [weak self] result in
guard let self = self else { return }

switch result {
case .success(let collection):
if let collection = collection {
for locator in collection.locators {
if !self.results.contains(where: { $0.href == locator.href }) {
self.results.append(locator)
}
}
self.state = .idle(iterator, isFetching: false)
} else {
self.state = .end
}

self.handleNewCollection(iterator, collection: collection)
case .failure(let error):
self.state = .failure(error)
}
Expand All @@ -86,7 +81,58 @@ final class EPUBSearchViewModel: ObservableObject {
state = .loadingNext(iterator, cancellable)
}

private func handleNewCollection(_ iterator: SearchIterator, collection: _LocatorCollection?) {
guard let collection = collection else {
state = .end
return
}

for newLocator in collection.locators {
if !isDuplicate(newLocator) {
self.results.append(newLocator)
}
}

groupResults()

self.state = .idle(iterator, isFetching: false)
}

private func groupResults() {
var groupedResults: [String: [Locator]] = [:]

for locator in results {
let titleKey = locator.title ?? locator.href

if !groupedResults.keys.contains(titleKey) {
groupedResults[titleKey] = []
}

if !groupedResults[titleKey]!.contains(where: { existingLocator in
existingLocator.href == locator.href &&
existingLocator.locations.progression == locator.locations.progression &&
existingLocator.locations.totalProgression == locator.locations.totalProgression
}) {
groupedResults[titleKey]!.append(locator)
}
}

self.groupedResults = groupedResults
.map { (title: $0.value.first?.title ?? "", locators: $0.value) }
.sorted { section1, section2 in
let href1 = section1.locators.first?.href.split(separator: "/").dropFirst(2).joined(separator: "/") ?? ""
let href2 = section2.locators.first?.href.split(separator: "/").dropFirst(2).joined(separator: "/") ?? ""
return href1 < href2
}
}

private func isDuplicate(_ locator: Locator) -> Bool {
return self.results.contains { existingLocator in
existingLocator.href == locator.href &&
existingLocator.locations.progression == locator.locations.progression &&
existingLocator.locations.totalProgression == locator.locations.totalProgression
}
}

func cancelSearch() {
switch state {
Expand Down

0 comments on commit fc7b5d6

Please sign in to comment.