#import "MWMSearch.h" #import "MWMFrameworkListener.h" #import "MWMFrameworkObservers.h" #import "SearchResult+Core.h" #import "SwiftBridge.h" #include #include #include "platform/network_policy.hpp" namespace { using Observer = id; using Observers = NSHashTable; } // namespace @interface MWMSearch () @property(nonatomic) NSUInteger suggestionsCount; @property(nonatomic) SearchMode searchMode; @property(nonatomic) BOOL textChanged; @property(nonatomic) Observers * observers; @property(nonatomic) NSUInteger lastSearchTimestamp; @property(nonatomic) SearchIndex * itemsIndex; @property(nonatomic) NSInteger searchCount; @end @implementation MWMSearch { std::string m_query; std::string m_locale; bool m_isCategory; search::Results m_everywhereResults; search::Results m_viewportResults; } #pragma mark - Instance + (MWMSearch *)manager { static MWMSearch *manager; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ manager = [[self alloc] initManager]; }); return manager; } - (instancetype)initManager { self = [super init]; if (self) { _observers = [Observers weakObjectsHashTable]; [MWMFrameworkListener addObserver:self]; } return self; } - (void)searchEverywhere { self.lastSearchTimestamp += 1; NSUInteger const timestamp = self.lastSearchTimestamp; search::EverywhereSearchParams params{ m_query, m_locale, {} /* default timeout */, m_isCategory, // m_onResults [self, timestamp](search::Results results, std::vector productInfo) { // Store the flag first, because we will make move next. bool const isEndMarker = results.IsEndMarker(); if (timestamp == self.lastSearchTimestamp) { self.suggestionsCount = results.GetSuggestsCount(); self->m_everywhereResults = std::move(results); [self onSearchResultsUpdated]; } if (isEndMarker) self.searchCount -= 1; } }; GetFramework().GetSearchAPI().SearchEverywhere(std::move(params)); self.searchCount += 1; } - (void)searchInViewport { search::ViewportSearchParams params { m_query, m_locale, {} /* default timeout */, m_isCategory, // m_onStarted {}, // m_onCompleted [self](search::Results results) { if (!results.IsEndMarker()) return; if (!results.IsEndedCancelled()) self->m_viewportResults = std::move(results); } }; GetFramework().GetSearchAPI().SearchInViewport(std::move(params)); } - (void)update { if (m_query.empty()) return; switch (self.searchMode) { case SearchModeEverywhere: [self searchEverywhere]; break; case SearchModeViewport: [self searchInViewport]; break; case SearchModeEverywhereAndViewport: [self searchEverywhere]; [self searchInViewport]; break; } } #pragma mark - Add/Remove Observers + (void)addObserver:(id)observer { [[MWMSearch manager].observers addObject:observer]; } + (void)removeObserver:(id)observer { [[MWMSearch manager].observers removeObject:observer]; } #pragma mark - Methods + (void)saveQuery:(SearchQuery *)query { if (!query.text || query.text.length == 0) return; std::string locale = (!query.locale || query.locale == 0) ? [MWMSearch manager]->m_locale : query.locale.UTF8String; std::string text = query.text.UTF8String; GetFramework().GetSearchAPI().SaveSearchQuery({std::move(locale), std::move(text)}); } + (void)searchQuery:(SearchQuery *)query { if (!query.text) return; MWMSearch *manager = [MWMSearch manager]; if (query.locale.length != 0) manager->m_locale = query.locale.UTF8String; // Pass input query as-is without any normalization (precomposedStringWithCompatibilityMapping). // Otherwise № -> No, and it's unexpectable for the search index. manager->m_query = query.text.UTF8String; manager->m_isCategory = (query.source == SearchTextSourceCategory); manager.textChanged = YES; [manager reset]; [manager update]; } + (void)showResultAtIndex:(NSUInteger)index { auto const & result = [MWMSearch manager]->m_everywhereResults[index]; GetFramework().StopLocationFollow(); GetFramework().SelectSearchResult(result, true); } + (SearchResult *)resultWithContainerIndex:(NSUInteger)index { SearchResult * result = [[SearchResult alloc] initWithResult:[MWMSearch manager]->m_everywhereResults[index] itemType:[MWMSearch resultTypeWithRow:index] index:index]; return result; } + (NSArray *)getResults { NSMutableArray * results = [[NSMutableArray alloc] initWithCapacity:MWMSearch.resultsCount]; for (NSUInteger i = 0; i < MWMSearch.resultsCount; ++i) { SearchResult * result = [MWMSearch resultWithContainerIndex:i]; [results addObject:result]; } return [results copy]; } + (SearchItemType)resultTypeWithRow:(NSUInteger)row { auto itemsIndex = [MWMSearch manager].itemsIndex; return [itemsIndex resultTypeWithRow:row]; } + (NSUInteger)containerIndexWithRow:(NSUInteger)row { auto itemsIndex = [MWMSearch manager].itemsIndex; return [itemsIndex resultContainerIndexWithRow:row]; } - (void)reset { self.lastSearchTimestamp += 1; GetFramework().GetSearchAPI().CancelAllSearches(); m_everywhereResults.Clear(); m_viewportResults.Clear(); [self onSearchResultsUpdated]; } + (void)clear { auto manager = [MWMSearch manager]; manager->m_query.clear(); manager.suggestionsCount = 0; [manager reset]; } + (SearchMode)searchMode { return [MWMSearch manager].searchMode; } + (void)setSearchMode:(SearchMode)mode { MWMSearch * manager = [MWMSearch manager]; if (manager.searchMode == mode) return; manager.searchMode = mode; [manager update]; } + (NSUInteger)suggestionsCount { return [MWMSearch manager].suggestionsCount; } + (NSUInteger)resultsCount { return [MWMSearch manager].itemsIndex.count; } - (void)updateItemsIndexWithBannerReload:(BOOL)reloadBanner { auto const resultsCount = self->m_everywhereResults.GetCount(); auto const itemsIndex = [[SearchIndex alloc] initWithSuggestionsCount:self.suggestionsCount resultsCount:resultsCount]; [itemsIndex build]; self.itemsIndex = itemsIndex; } #pragma mark - Notifications - (void)onSearchStarted { for (Observer observer in self.observers) { if ([observer respondsToSelector:@selector(onSearchStarted)]) [observer onSearchStarted]; } } - (void)onSearchCompleted { [self updateItemsIndexWithBannerReload:YES]; for (Observer observer in self.observers) { if ([observer respondsToSelector:@selector(onSearchCompleted)]) [observer onSearchCompleted]; } } - (void)onSearchResultsUpdated { [self updateItemsIndexWithBannerReload:NO]; for (Observer observer in self.observers) { if ([observer respondsToSelector:@selector(onSearchResultsUpdated)]) [observer onSearchResultsUpdated]; } } #pragma mark - MWMFrameworkDrapeObserver - (void)processViewportChangedEvent { if (!GetFramework().GetSearchAPI().IsViewportSearchActive()) return; BOOL const isSearchCompleted = self.searchCount == 0; if (!isSearchCompleted) return; switch (self.searchMode) { case SearchModeEverywhere: case SearchModeViewport: break; case SearchModeEverywhereAndViewport: [self searchEverywhere]; break; } } #pragma mark - Properties - (void)setSearchCount:(NSInteger)searchCount { NSAssert((searchCount >= 0) && ((_searchCount == searchCount - 1) || (_searchCount == searchCount + 1)), @"Invalid search count update"); if (searchCount > 0) [self onSearchStarted]; else if (searchCount == 0) [self onSearchCompleted]; _searchCount = searchCount; } @end