Skip to content

Commit a28d224

Browse files
authored
Enable remote search in Themes if custom themes are supported (#24262)
* Add search parameter to getThemesForBlog method * Add remote search functionality to theme synchronization * Update WordPressKit-iOS dependency to use branch for search parameter integration * Clear search parameter if empty in getThemesForBlog method * Enhance local and remote search logic in ThemeBrowserViewController.swift - Apply immediate local search when search text changes - Perform remote search for WordPress.com themes with 3+ characters - Update predicates to include local search conditions for custom themes - Refactor comments for clarity on search behavior and conditions * Add remote search reset functionality - Modify `ThemeBrowserViewController.swift` to include `resetRemoteSearch` method - Integrate `resetRemoteSearch` in search logic to reset search state upon search text changes - Adjust logic to handle shorter search queries that follow longer ones by resetting remote search - Ensure local results reload consistently after search operations * fix: comment * - Remove redundant NSParameterAssert for search in ThemeService.m - Update test to use nil instead of empty string for search in ThemeServiceTests.m * Refactor ThemeBrowserViewController access modifiers and simplify code - Change `fileprivate` to `private` for several functions and variables in ThemeBrowserViewController.swift - Simplify guard statement in `updateSearchName` function - Update comment in `customThemesBrowsePredicate` function * fix: Trailing Whitespace Violation * update: point back to wpios-edition for WordPressKit-iOS * fix: ThemeServiceTests
1 parent 8b76ab8 commit a28d224

File tree

5 files changed

+95
-16
lines changed

5 files changed

+95
-16
lines changed

WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

WordPress/Classes/Services/ThemeService.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ typedef void(^ThemeServiceFailureBlock)(NSError *error);
4848
*
4949
* @param blogId The blog to get the themes for. Cannot be nil.
5050
* @param page Results page to return.
51+
* @param search Search string to filter themes.
5152
* @param sync Whether to remove unsynced results.
5253
* @param success The success handler. Can be nil.
5354
* @param failure The failure handler. Can be nil.
@@ -56,6 +57,7 @@ typedef void(^ThemeServiceFailureBlock)(NSError *error);
5657
*/
5758
- (NSProgress *)getThemesForBlog:(Blog *)blog
5859
page:(NSInteger)page
60+
search:(NSString *)search
5961
sync:(BOOL)sync
6062
success:(ThemeServiceThemesRequestSuccessBlock)success
6163
failure:(ThemeServiceFailureBlock)failure;

WordPress/Classes/Services/ThemeService.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ - (NSProgress *)getActiveThemeForBlog:(Blog *)blog
153153

154154
- (NSProgress *)getThemesForBlog:(Blog *)blog
155155
page:(NSInteger)page
156+
search:(NSString *)search
156157
sync:(BOOL)sync
157158
success:(ThemeServiceThemesRequestSuccessBlock)success
158159
failure:(ThemeServiceFailureBlock)failure
@@ -161,6 +162,10 @@ - (NSProgress *)getThemesForBlog:(Blog *)blog
161162
NSAssert([self blogSupportsThemeServices:blog],
162163
@"Do not call this method on unsupported blogs, check with blogSupportsThemeServices first.");
163164

165+
if (search.length == 0) {
166+
search = nil;
167+
}
168+
164169
if (blog.wordPressComRestApi == nil) {
165170
return nil;
166171
}
@@ -169,6 +174,7 @@ - (NSProgress *)getThemesForBlog:(Blog *)blog
169174

170175
if ([blog supports:BlogFeatureCustomThemes]) {
171176
return [remote getWPThemesPage:page
177+
search:search
172178
freeOnly:![blog supports:BlogFeaturePremiumThemes]
173179
success:^(NSArray<RemoteTheme *> *remoteThemes, BOOL hasMore, NSInteger totalThemeCount) {
174180
NSArray * __block themeObjectIDs = nil;

WordPress/Classes/ViewRelated/Themes/ThemeBrowserViewController.swift

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -452,11 +452,12 @@ public protocol ThemePresenter: AnyObject {
452452
}
453453
}
454454

455-
fileprivate func syncThemePage(_ page: NSInteger, success: ((_ hasMore: Bool) -> Void)?, failure: ((_ error: NSError) -> Void)?) {
455+
private func syncThemePage(_ page: NSInteger, search: String, success: ((_ hasMore: Bool) -> Void)?, failure: ((_ error: NSError) -> Void)?) {
456456
assert(page > 0)
457457
themesSyncingPage = page
458458
_ = themeService.getThemesFor(blog,
459459
page: themesSyncingPage,
460+
search: search,
460461
sync: page == 1,
461462
success: {[weak self](themes: [Theme]?, hasMore: Bool, themeCount: NSInteger) in
462463
if let success {
@@ -509,7 +510,7 @@ public protocol ThemePresenter: AnyObject {
509510

510511
func syncHelper(_ syncHelper: WPContentSyncHelper, syncContentWithUserInteraction userInteraction: Bool, success: ((_ hasMore: Bool) -> Void)?, failure: ((_ error: NSError) -> Void)?) {
511512
if syncHelper == themesSyncHelper {
512-
syncThemePage(1, success: success, failure: failure)
513+
syncThemePage(1, search: searchName, success: success, failure: failure)
513514
} else if syncHelper == customThemesSyncHelper {
514515
syncCustomThemes(success: success, failure: failure)
515516
}
@@ -518,7 +519,7 @@ public protocol ThemePresenter: AnyObject {
518519
func syncHelper(_ syncHelper: WPContentSyncHelper, syncMoreWithSuccess success: ((_ hasMore: Bool) -> Void)?, failure: ((_ error: NSError) -> Void)?) {
519520
if syncHelper == themesSyncHelper {
520521
let nextPage = themesSyncingPage + 1
521-
syncThemePage(nextPage, success: success, failure: failure)
522+
syncThemePage(nextPage, search: searchName, success: success, failure: failure)
522523
}
523524
}
524525

@@ -657,11 +658,65 @@ public protocol ThemePresenter: AnyObject {
657658

658659
// MARK: - Search support
659660

661+
private var searchDebounceTimer: Timer?
662+
private let searchDebounceInterval: TimeInterval = 0.5
663+
664+
private func resetRemoteSearch() {
665+
themesSyncingPage = 0
666+
667+
if blog.supports(BlogFeature.customThemes) {
668+
themesSyncHelper.syncContent()
669+
}
670+
}
671+
660672
fileprivate func beginSearchFor(_ pattern: String) {
661673
searchController.isActive = true
662674
searchController.searchBar.text = pattern
663675

664-
searchName = pattern
676+
updateSearchName(pattern)
677+
}
678+
679+
private func updateSearchName(_ searchText: String) {
680+
// Cancel any existing timer
681+
searchDebounceTimer?.invalidate()
682+
683+
// If search text is empty, update immediately and reset remote search
684+
if searchText.isEmpty {
685+
self.searchName = searchText
686+
self.fetchThemes()
687+
self.resetRemoteSearch()
688+
self.reloadThemes()
689+
return
690+
}
691+
692+
// Check if we have a previously longer search that is now under 3 characters
693+
let previouslyHadRemoteSearch = self.searchName.count >= 3
694+
695+
// Create a new timer for debounce
696+
searchDebounceTimer = Timer.scheduledTimer(withTimeInterval: searchDebounceInterval, repeats: false) { [weak self] _ in
697+
guard let self else { return }
698+
self.searchName = searchText
699+
700+
// Apply local search immediately
701+
self.fetchThemes()
702+
703+
// Remote search only applies to WordPress.com themes and only if customThemes are supported.
704+
// The remote endpoint support search just for 3+ characters
705+
if self.blog.supports(BlogFeature.customThemes) {
706+
if searchText.count >= 3 {
707+
// Reset to first page when searching
708+
self.themesSyncingPage = 0
709+
self.themesSyncHelper.syncContent()
710+
} else if previouslyHadRemoteSearch {
711+
// If we previously had 3+ characters but now have less,
712+
// we need to reset the remote search results
713+
self.resetRemoteSearch()
714+
}
715+
}
716+
717+
// Always reload with local results
718+
self.reloadThemes()
719+
}
665720
}
666721

667722
// MARK: - UISearchControllerDelegate
@@ -682,6 +737,7 @@ public protocol ThemePresenter: AnyObject {
682737
hideSectionHeaders = false
683738
searchName = ""
684739
searchController.searchBar.text = ""
740+
resetRemoteSearch()
685741
}
686742

687743
open func didDismissSearchController(_ searchController: UISearchController) {
@@ -709,31 +765,44 @@ public protocol ThemePresenter: AnyObject {
709765
// MARK: - UISearchResultsUpdating
710766

711767
open func updateSearchResults(for searchController: UISearchController) {
712-
searchName = searchController.searchBar.text ?? ""
768+
updateSearchName(searchController.searchBar.text ?? "")
713769
}
714770

715771
// MARK: - NSFetchedResultsController helpers
716772

717-
fileprivate func searchNamePredicate() -> NSPredicate? {
718-
guard !searchName.isEmpty else {
719-
return nil
720-
}
721-
722-
return NSPredicate(format: "name contains[c] %@", searchName)
723-
}
724-
725773
fileprivate func browsePredicate() -> NSPredicate? {
726774
return browsePredicateThemesWithCustomValue(false)
727775
}
728776

729777
fileprivate func customThemesBrowsePredicate() -> NSPredicate? {
730-
return browsePredicateThemesWithCustomValue(true)
778+
let browsePredicate = browsePredicateThemesWithCustomValue(true)
779+
780+
// Search predicate for custom themes (local search only)
781+
if !searchName.isEmpty {
782+
let searchPredicate = NSPredicate(format: "name CONTAINS[cd] %@", searchName)
783+
if let existingPredicate = browsePredicate {
784+
return NSCompoundPredicate(andPredicateWithSubpredicates: [existingPredicate, searchPredicate])
785+
} else {
786+
return searchPredicate
787+
}
788+
}
789+
790+
return browsePredicate
731791
}
732792

733793
fileprivate func browsePredicateThemesWithCustomValue(_ custom: Bool) -> NSPredicate? {
734794
let blogPredicate = NSPredicate(format: "blog == %@ AND custom == %d", self.blog, custom ? 1 : 0)
735795

736-
let subpredicates = [blogPredicate, searchNamePredicate(), filterType.predicate].compactMap { $0 }
796+
let subpredicates = [blogPredicate, filterType.predicate].compactMap { $0 }
797+
798+
// For regular themes, add local search predicate if:
799+
// 1. Not using custom themes feature, or
800+
// 2. Search term is less than 3 characters (we'll only search locally for short terms)
801+
if !searchName.isEmpty && !custom && (!blog.supports(BlogFeature.customThemes) || searchName.count < 3) {
802+
let searchPredicate = NSPredicate(format: "name CONTAINS[cd] %@", searchName)
803+
return NSCompoundPredicate(andPredicateWithSubpredicates: subpredicates + [searchPredicate])
804+
}
805+
737806
switch subpredicates.count {
738807
case 1:
739808
return subpredicates[0]

WordPress/WordPressTest/ThemeServiceTests.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ - (void)testThatGetThemesForBlogWorks
112112
XCTAssertNoThrow(service = [[ThemeService alloc] initWithCoreDataStack:self.manager]);
113113
XCTAssertNoThrow([service getThemesForBlog:blog
114114
page:1
115+
search:nil
115116
sync:NO
116117
success:nil
117118
failure:nil]);
@@ -124,6 +125,7 @@ - (void)testThatGetThemesForBlogThrowsExceptionWithoutBlog
124125
XCTAssertNoThrow(service = [[ThemeService alloc] initWithCoreDataStack:self.manager]);
125126
XCTAssertThrows([service getThemesForBlog:nil
126127
page:1
128+
search:nil
127129
sync:NO
128130
success:nil
129131
failure:nil]);

0 commit comments

Comments
 (0)