From 3b5ed8d5a3a90def6085a2096168d8e971cc2c59 Mon Sep 17 00:00:00 2001 From: Maurice Carrier Date: Mon, 20 Nov 2023 16:11:00 -0500 Subject: [PATCH 1/3] working implementation --- .../UI/EpubSearchView/EPUBSearchView.swift | 38 ++++++++++++--- .../EpubSearchView/EPUBSearchViewModel.swift | 47 ++++++++++++------- 2 files changed, 63 insertions(+), 22 deletions(-) diff --git a/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift b/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift index 27a1d2707..0c66e9633 100644 --- a/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift +++ b/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift @@ -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() @@ -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 } } @@ -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(groupedByChapterTitle(viewModel.results), 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) { @@ -84,7 +83,34 @@ struct EPUBSearchView: View { } private func shouldFetchMoreResults(for locator: Locator) -> Bool { - viewModel.results.last?.href == locator.href + if let lastHref = viewModel.results.last?.href { + return locator.href == lastHref + } + return false + } + + private func groupedByChapterTitle(_ results: [Locator]) -> [(title: String, locators: [Locator])] { + var groupedResults: [String: [Locator]] = [:] + + for locator in results { + let titleKey = locator.title ?? locator.href // Use title or href as a key + + if groupedResults[titleKey] == nil { + groupedResults[titleKey] = [] + } + + let isDuplicate = groupedResults[titleKey]!.contains { existingLocator in + existingLocator.href == locator.href && + existingLocator.locations.progression == locator.locations.progression && + existingLocator.locations.totalProgression == locator.locations.totalProgression + } + + if !isDuplicate { + groupedResults[titleKey]!.append(locator) + } + } + + return groupedResults.map { (title: $0.value.first?.title ?? "", locators: $0.value) } } private func groupedByChapterName(_ results: [Locator]) -> [(key: String, value: [Locator])] { diff --git a/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift b/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift index 3b22eb2b0..9af23ab8e 100644 --- a/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift +++ b/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift @@ -58,26 +58,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) } @@ -85,8 +79,29 @@ 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) + } + } + + self.state = .idle(iterator, isFetching: false) + } + + 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 { From 4b2fd80ed8a801ca6833039d1cda60d7f0c759b8 Mon Sep 17 00:00:00 2001 From: Maurice Carrier Date: Tue, 21 Nov 2023 11:26:01 -0500 Subject: [PATCH 2/3] Correct search issues --- Palace.xcodeproj/project.pbxproj | 8 +-- .../UI/EpubSearchView/EPUBSearchView.swift | 54 +++---------------- .../EpubSearchView/EPUBSearchViewModel.swift | 35 +++++++++++- 3 files changed, 43 insertions(+), 54 deletions(-) diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index ce622ff66..b086d80de 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -4654,7 +4654,7 @@ CODE_SIGN_IDENTITY = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 218; + CURRENT_PROJECT_VERSION = 219; DEVELOPMENT_TEAM = 88CBA74T8K; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; @@ -4711,7 +4711,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 218; + CURRENT_PROJECT_VERSION = 219; DEVELOPMENT_TEAM = 88CBA74T8K; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; @@ -4895,7 +4895,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 218; + CURRENT_PROJECT_VERSION = 219; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; @@ -4955,7 +4955,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 218; + CURRENT_PROJECT_VERSION = 219; DEVELOPMENT_TEAM = 88CBA74T8K; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; diff --git a/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift b/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift index 0c66e9633..3cf375c01 100644 --- a/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift +++ b/Palace/Reader2/UI/EpubSearchView/EPUBSearchView.swift @@ -55,7 +55,7 @@ struct EPUBSearchView: View { @ViewBuilder private var listView: some View { ZStack { List { - ForEach(groupedByChapterTitle(viewModel.results), id: \.title) { section in + ForEach(viewModel.groupedResults, id: \.title) { section in Section(header: sectionHeaderView(title: section.title)) { ForEach(section.locators, id: \.self) { locator in rowView(locator) @@ -81,56 +81,14 @@ struct EPUBSearchView: View { } } } - + private func shouldFetchMoreResults(for locator: Locator) -> Bool { - if let lastHref = viewModel.results.last?.href { - return locator.href == lastHref + if let lastSection = viewModel.groupedResults.last, + let lastLocator = lastSection.locators.last { + return locator.href == lastLocator.href } return false } - - private func groupedByChapterTitle(_ results: [Locator]) -> [(title: String, locators: [Locator])] { - var groupedResults: [String: [Locator]] = [:] - - for locator in results { - let titleKey = locator.title ?? locator.href // Use title or href as a key - - if groupedResults[titleKey] == nil { - groupedResults[titleKey] = [] - } - - let isDuplicate = groupedResults[titleKey]!.contains { existingLocator in - existingLocator.href == locator.href && - existingLocator.locations.progression == locator.locations.progression && - existingLocator.locations.totalProgression == locator.locations.totalProgression - } - - if !isDuplicate { - groupedResults[titleKey]!.append(locator) - } - } - - return groupedResults.map { (title: $0.value.first?.title ?? "", locators: $0.value) } - } - - 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 sectionHeaderView(title: String) -> some View { Text(title.uppercased( )) @@ -149,7 +107,7 @@ struct EPUBSearchView: View { EmptyView() } } - + private func rowView(_ locator: Locator) -> some View { let text = locator.text.sanitized() diff --git a/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift b/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift index 9af23ab8e..919051288 100644 --- a/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift +++ b/Palace/Reader2/UI/EpubSearchView/EPUBSearchViewModel.swift @@ -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? @@ -79,7 +80,7 @@ final class EPUBSearchViewModel: ObservableObject { state = .loadingNext(iterator, cancellable) } - + private func handleNewCollection(_ iterator: SearchIterator, collection: _LocatorCollection?) { guard let collection = collection else { state = .end @@ -92,9 +93,39 @@ final class EPUBSearchViewModel: ObservableObject { } } + 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 && From 90ca881ed814117ef365f5695c4afa5353a8b3fc Mon Sep 17 00:00:00 2001 From: Maurice Carrier Date: Tue, 21 Nov 2023 12:27:22 -0500 Subject: [PATCH 3/3] Update project.pbxproj --- Palace.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index b086d80de..cef241486 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -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"; @@ -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";