Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
7
iphone/Maps/Core/Search/MWMSearch+CoreSpotlight.h
Normal file
7
iphone/Maps/Core/Search/MWMSearch+CoreSpotlight.h
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#import "MWMSearch.h"
|
||||
|
||||
@interface MWMSearch (CoreSpotlight)
|
||||
|
||||
+ (void)addCategoriesToSpotlight;
|
||||
|
||||
@end
|
||||
78
iphone/Maps/Core/Search/MWMSearch+CoreSpotlight.mm
Normal file
78
iphone/Maps/Core/Search/MWMSearch+CoreSpotlight.mm
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#import <CoreSpotlight/CoreSpotlight.h>
|
||||
#import <MobileCoreServices/MobileCoreServices.h>
|
||||
#import <CoreApi/Framework.h>
|
||||
#import <CoreApi/AppInfo.h>
|
||||
#import <CoreApi/MWMCommon.h>
|
||||
#import "MWMSearch+CoreSpotlight.h"
|
||||
#import "MWMSettings.h"
|
||||
|
||||
@implementation MWMSearch (CoreSpotlight)
|
||||
|
||||
+ (void)addCategoriesToSpotlight
|
||||
{
|
||||
if (isIOSVersionLessThan(9) || ![CSSearchableIndex isIndexingAvailable])
|
||||
return;
|
||||
|
||||
NSString * localeLanguageId = [[AppInfo sharedInfo] languageId];
|
||||
if ([localeLanguageId isEqualToString:[MWMSettings spotlightLocaleLanguageId]])
|
||||
return;
|
||||
|
||||
auto const & categories = GetFramework().GetDisplayedCategories();
|
||||
auto const & categoriesKeys = categories.GetKeys();
|
||||
NSMutableArray<CSSearchableItem *> * items = [@[] mutableCopy];
|
||||
|
||||
for (auto const & categoryKey : categoriesKeys)
|
||||
{
|
||||
CSSearchableItemAttributeSet * attrSet = [[CSSearchableItemAttributeSet alloc]
|
||||
initWithItemContentType: UTTypeItem.identifier];
|
||||
|
||||
NSString * categoryName = nil;
|
||||
NSMutableDictionary<NSString *, NSString *> * localizedStrings = [@{} mutableCopy];
|
||||
|
||||
categories.ForEachSynonym(categoryKey, [&localizedStrings, &localeLanguageId, &categoryName](
|
||||
std::string const & name, std::string const & locale) {
|
||||
NSString * nsName = @(name.c_str());
|
||||
NSString * nsLocale = @(locale.c_str());
|
||||
if ([localeLanguageId isEqualToString:nsLocale])
|
||||
categoryName = nsName;
|
||||
localizedStrings[nsLocale] = nsName;
|
||||
});
|
||||
attrSet.alternateNames = localizedStrings.allValues;
|
||||
attrSet.keywords = localizedStrings.allValues;
|
||||
attrSet.title = categoryName;
|
||||
attrSet.displayName = [[CSLocalizedString alloc] initWithLocalizedStrings:localizedStrings];
|
||||
|
||||
NSString * categoryKeyString = @(categoryKey.c_str());
|
||||
NSString * imageName = [NSString stringWithFormat:@"Search/Categories/%@", [categoryKeyString stringByReplacingOccurrencesOfString: @"category_" withString:@""]];
|
||||
UIImage * image = [UIImage imageNamed:imageName inBundle:nil compatibleWithTraitCollection:[UITraitCollection traitCollectionWithUserInterfaceStyle: UIUserInterfaceStyleLight]];
|
||||
UIGraphicsBeginImageContext(CGSizeMake(360, 360));
|
||||
[image drawInRect:CGRectMake(0, 0, 360, 360)];
|
||||
UIImage * resizedImage = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext() ;
|
||||
attrSet.thumbnailData = UIImagePNGRepresentation(resizedImage);
|
||||
|
||||
CSSearchableItem * item =
|
||||
[[CSSearchableItem alloc] initWithUniqueIdentifier:categoryKeyString
|
||||
domainIdentifier:@"comaps.app.categories"
|
||||
attributeSet:attrSet];
|
||||
[items addObject:item];
|
||||
}
|
||||
|
||||
[[CSSearchableIndex defaultSearchableIndex]
|
||||
indexSearchableItems:items
|
||||
completionHandler:^(NSError * _Nullable error) {
|
||||
if (error)
|
||||
{
|
||||
NSError * err = error;
|
||||
LOG(LERROR,
|
||||
("addCategoriesToSpotlight failed: ", err.localizedDescription.UTF8String));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(LINFO, ("addCategoriesToSpotlight succeded"));
|
||||
[MWMSettings setSpotlightLocaleLanguageId:localeLanguageId];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
58
iphone/Maps/Core/Search/MWMSearch.h
Normal file
58
iphone/Maps/Core/Search/MWMSearch.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#import "MWMSearchObserver.h"
|
||||
#import "SearchItemType.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, SearchTextSource) {
|
||||
SearchTextSourceTypedText,
|
||||
SearchTextSourceCategory,
|
||||
SearchTextSourceHistory,
|
||||
SearchTextSourceSuggestion,
|
||||
SearchTextSourceDeeplink
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, SearchMode) {
|
||||
SearchModeEverywhere,
|
||||
SearchModeViewport,
|
||||
SearchModeEverywhereAndViewport
|
||||
};
|
||||
|
||||
@class SearchResult;
|
||||
@class SearchQuery;
|
||||
|
||||
@protocol SearchManager
|
||||
|
||||
+ (void)addObserver:(id<MWMSearchObserver>)observer;
|
||||
+ (void)removeObserver:(id<MWMSearchObserver>)observer;
|
||||
|
||||
+ (void)saveQuery:(SearchQuery *)query;
|
||||
+ (void)searchQuery:(SearchQuery *)query;
|
||||
|
||||
+ (void)showResultAtIndex:(NSUInteger)index;
|
||||
+ (SearchMode)searchMode;
|
||||
+ (void)setSearchMode:(SearchMode)mode;
|
||||
|
||||
+ (NSArray<SearchResult *> *)getResults;
|
||||
|
||||
+ (void)clear;
|
||||
@end
|
||||
|
||||
NS_SWIFT_NAME(Search)
|
||||
@interface MWMSearch : NSObject<SearchManager>
|
||||
|
||||
+ (SearchItemType)resultTypeWithRow:(NSUInteger)row;
|
||||
+ (NSUInteger)containerIndexWithRow:(NSUInteger)row;
|
||||
+ (SearchResult *)resultWithContainerIndex:(NSUInteger)index;
|
||||
|
||||
+ (NSUInteger)suggestionsCount;
|
||||
+ (NSUInteger)resultsCount;
|
||||
|
||||
- (instancetype)init __attribute__((unavailable("call +manager instead")));
|
||||
- (instancetype)copy __attribute__((unavailable("call +manager instead")));
|
||||
- (instancetype)copyWithZone:(NSZone *)zone __attribute__((unavailable("call +manager instead")));
|
||||
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable("call +manager instead")));
|
||||
+ (instancetype)new __attribute__((unavailable("call +manager instead")));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
297
iphone/Maps/Core/Search/MWMSearch.mm
Normal file
297
iphone/Maps/Core/Search/MWMSearch.mm
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#import "MWMSearch.h"
|
||||
#import "MWMFrameworkListener.h"
|
||||
#import "MWMFrameworkObservers.h"
|
||||
#import "SearchResult+Core.h"
|
||||
#import "SwiftBridge.h"
|
||||
|
||||
#include <CoreApi/MWMTypes.h>
|
||||
#include <CoreApi/Framework.h>
|
||||
|
||||
#include "platform/network_policy.hpp"
|
||||
|
||||
namespace {
|
||||
using Observer = id<MWMSearchObserver>;
|
||||
using Observers = NSHashTable<Observer>;
|
||||
} // namespace
|
||||
|
||||
@interface MWMSearch () <MWMFrameworkDrapeObserver>
|
||||
|
||||
@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<search::ProductInfo> 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<MWMSearchObserver>)observer {
|
||||
[[MWMSearch manager].observers addObject:observer];
|
||||
}
|
||||
|
||||
+ (void)removeObserver:(id<MWMSearchObserver>)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<SearchResult *> *)getResults {
|
||||
NSMutableArray<SearchResult *> * 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
|
||||
8
iphone/Maps/Core/Search/MWMSearchObserver.h
Normal file
8
iphone/Maps/Core/Search/MWMSearchObserver.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
@protocol MWMSearchObserver<NSObject>
|
||||
|
||||
@optional
|
||||
- (void)onSearchStarted;
|
||||
- (void)onSearchCompleted;
|
||||
- (void)onSearchResultsUpdated;
|
||||
|
||||
@end
|
||||
39
iphone/Maps/Core/Search/SearchBanners.swift
Normal file
39
iphone/Maps/Core/Search/SearchBanners.swift
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@objc(MWMSearchBanners)
|
||||
final class SearchBanners: NSObject {
|
||||
private var banners: [MWMBanner] = []
|
||||
|
||||
weak var searchIndex: SearchIndex?
|
||||
|
||||
@objc init(searchIndex: SearchIndex) {
|
||||
self.searchIndex = searchIndex
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc func add(_ banner: MWMBanner) {
|
||||
guard let searchIndex = searchIndex else { return }
|
||||
banners.append(banner)
|
||||
let type: MWMSearchItemType
|
||||
let prefferedPosition: Int
|
||||
switch banner.mwmType {
|
||||
case .mopub:
|
||||
type = .mopub
|
||||
prefferedPosition = 2
|
||||
case .facebook:
|
||||
type = .facebook
|
||||
prefferedPosition = 2
|
||||
default:
|
||||
assert(false, "Unsupported banner type")
|
||||
type = .regular
|
||||
prefferedPosition = 0
|
||||
}
|
||||
searchIndex.addItem(type: type, prefferedPosition: prefferedPosition, containerIndex: banners.count - 1)
|
||||
}
|
||||
|
||||
@objc func banner(atIndex index: Int) -> MWMBanner {
|
||||
return banners[index]
|
||||
}
|
||||
|
||||
deinit {
|
||||
banners.forEach { BannersCache.cache.bannerIsOutOfScreen(coreBanner: $0) }
|
||||
}
|
||||
}
|
||||
87
iphone/Maps/Core/Search/SearchIndex.swift
Normal file
87
iphone/Maps/Core/Search/SearchIndex.swift
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
@objc
|
||||
final class SearchIndex: NSObject {
|
||||
fileprivate struct Item {
|
||||
let type: SearchItemType
|
||||
let containerIndex: Int
|
||||
}
|
||||
|
||||
fileprivate struct PositionItem {
|
||||
let item: Item
|
||||
var position: Int
|
||||
}
|
||||
|
||||
private var positionItems: [PositionItem] = []
|
||||
private var items: [Item] = []
|
||||
|
||||
@objc var count: Int {
|
||||
return items.count
|
||||
}
|
||||
|
||||
@objc init(suggestionsCount: Int, resultsCount: Int) {
|
||||
for index in 0 ..< resultsCount {
|
||||
let type: SearchItemType = index < suggestionsCount ? .suggestion : .regular
|
||||
let item = Item(type: type, containerIndex: index)
|
||||
positionItems.append(PositionItem(item: item, position: index))
|
||||
}
|
||||
super.init()
|
||||
}
|
||||
|
||||
func addItem(type: SearchItemType, prefferedPosition: Int, containerIndex: Int) {
|
||||
assert(type != .suggestion && type != .regular)
|
||||
let item = Item(type: type, containerIndex: containerIndex)
|
||||
positionItems.append(PositionItem(item: item, position: prefferedPosition))
|
||||
}
|
||||
|
||||
@objc func build() {
|
||||
positionItems.sort(by: >)
|
||||
var itemsDict: [Int: Item] = [:]
|
||||
positionItems.forEach { item in
|
||||
var position = item.position
|
||||
while itemsDict[position] != nil {
|
||||
position += 1
|
||||
}
|
||||
itemsDict[position] = item.item
|
||||
}
|
||||
|
||||
items.removeAll()
|
||||
let keys = itemsDict.keys.sorted()
|
||||
for index in 0 ..< keys.count {
|
||||
let key = keys[index]
|
||||
if index == key {
|
||||
items.append(itemsDict[key]!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func resultType(row: Int) -> SearchItemType {
|
||||
return items[row].type
|
||||
}
|
||||
|
||||
@objc func resultContainerIndex(row: Int) -> Int {
|
||||
return items[row].containerIndex
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchIndex.PositionItem: Equatable {
|
||||
static func ==(lhs: SearchIndex.PositionItem, rhs: SearchIndex.PositionItem) -> Bool {
|
||||
let lhsCache = lhs.item
|
||||
let rhsCache = rhs.item
|
||||
return lhsCache.type == rhsCache.type &&
|
||||
lhs.position == rhs.position &&
|
||||
lhsCache.containerIndex == rhsCache.containerIndex
|
||||
}
|
||||
}
|
||||
|
||||
extension SearchIndex.PositionItem: Comparable {
|
||||
static func <(lhs: SearchIndex.PositionItem, rhs: SearchIndex.PositionItem) -> Bool {
|
||||
let lhsCache = lhs.item
|
||||
let rhsCache = rhs.item
|
||||
guard lhsCache.type == rhsCache.type else {
|
||||
return lhsCache.type.rawValue < rhsCache.type.rawValue
|
||||
}
|
||||
guard lhs.position == rhs.position else {
|
||||
return lhs.position > rhs.position
|
||||
}
|
||||
return lhsCache.containerIndex < rhsCache.containerIndex
|
||||
}
|
||||
}
|
||||
4
iphone/Maps/Core/Search/SearchItemType.h
Normal file
4
iphone/Maps/Core/Search/SearchItemType.h
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
typedef NS_ENUM(NSInteger, SearchItemType) {
|
||||
SearchItemTypeRegular,
|
||||
SearchItemTypeSuggestion
|
||||
};
|
||||
13
iphone/Maps/Core/Search/SearchResult+Core.h
Normal file
13
iphone/Maps/Core/Search/SearchResult+Core.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#import "SearchResult.h"
|
||||
|
||||
#import "search/result.hpp"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SearchResult (Core)
|
||||
|
||||
- (instancetype)initWithResult:(const search::Result &)result itemType:(SearchItemType)itemType index:(NSUInteger)index;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
32
iphone/Maps/Core/Search/SearchResult.h
Normal file
32
iphone/Maps/Core/Search/SearchResult.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#import "SearchItemType.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SearchResult : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSUInteger index;
|
||||
@property (nonatomic, readonly) NSString * titleText;
|
||||
@property (nonatomic, readonly, nullable) NSString * branchText;
|
||||
@property (nonatomic, readonly) NSString * iconImageName;
|
||||
@property (nonatomic, readonly) NSString * addressText;
|
||||
@property (nonatomic, readonly) NSString * infoText;
|
||||
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
|
||||
@property (nonatomic, readonly) CGPoint point;
|
||||
@property (nonatomic, readonly, nullable) NSString * distanceText;
|
||||
@property (nonatomic, readonly, nullable) NSString * openStatusText;
|
||||
@property (nonatomic, readonly) UIColor * openStatusColor;
|
||||
@property (nonatomic, readonly) BOOL isPopularHidden;
|
||||
@property (nonatomic, readonly) NSString * suggestion;
|
||||
@property (nonatomic, readonly) BOOL isPureSuggest;
|
||||
@property (nonatomic, readonly) NSArray<NSValue *> * highlightRanges;
|
||||
@property (nonatomic, readonly) SearchItemType itemType;
|
||||
|
||||
/// This initializer is intended only for testing purposes.
|
||||
- (instancetype)initWithTitleText:(NSString *)titleText type:(SearchItemType)type suggestion:(NSString *)suggestion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
118
iphone/Maps/Core/Search/SearchResult.mm
Normal file
118
iphone/Maps/Core/Search/SearchResult.mm
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#import "SearchResult+Core.h"
|
||||
#import "CLLocation+Mercator.h"
|
||||
#import "MWMLocationManager.h"
|
||||
#import "SwiftBridge.h"
|
||||
|
||||
#import "platform/localization.hpp"
|
||||
#import "platform/distance.hpp"
|
||||
|
||||
#include "map/bookmark_helpers.hpp"
|
||||
|
||||
#import "geometry/mercator.hpp"
|
||||
|
||||
@implementation SearchResult
|
||||
|
||||
- (instancetype)initWithTitleText:(NSString *)titleText type:(SearchItemType)type suggestion:(NSString *)suggestion {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_titleText = titleText;
|
||||
_itemType = type;
|
||||
_suggestion = suggestion;
|
||||
};
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SearchResult(Core)
|
||||
|
||||
- (instancetype)initWithResult:(const search::Result &)result itemType:(SearchItemType)itemType index:(NSUInteger)index {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_index = index;
|
||||
_titleText = result.GetString().empty() ? @(result.GetLocalizedFeatureType().c_str()) : @(result.GetString().c_str());
|
||||
_addressText = @(result.GetAddress().c_str());
|
||||
_infoText = @(result.GetFeatureDescription().c_str());
|
||||
_branchText = result.GetBranch().empty() ? nil : @(result.GetBranch().c_str());
|
||||
if (result.IsSuggest())
|
||||
_suggestion = @(result.GetSuggestionString().c_str());
|
||||
|
||||
_distanceText = nil;
|
||||
if (result.HasPoint()) {
|
||||
auto const center = result.GetFeatureCenter();
|
||||
_point = CGPointMake(center.x, center.y);
|
||||
auto const [centerLat, centerLon] = mercator::ToLatLon(center);
|
||||
_coordinate = CLLocationCoordinate2DMake(centerLat, centerLon);
|
||||
|
||||
CLLocation * lastLocation = [MWMLocationManager lastLocation];
|
||||
if (lastLocation) {
|
||||
double const distanceM = mercator::DistanceOnEarth(lastLocation.mercator, center);
|
||||
std::string const distanceStr = platform::Distance::CreateFormatted(distanceM).ToString();
|
||||
_distanceText = @(distanceStr.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
switch (result.IsOpenNow()) {
|
||||
case osm::Yes: {
|
||||
const int minutes = result.GetMinutesUntilClosed();
|
||||
if (minutes < 60) { // less than 1 hour
|
||||
_openStatusColor = [UIColor colorNamed:@"Base Colors/Yellow Color"];
|
||||
NSString * time = [NSString stringWithFormat:@"%d %@", minutes, L(@"minute")];
|
||||
_openStatusText = [NSString stringWithFormat:L(@"closes_in"), time];
|
||||
} else {
|
||||
_openStatusColor = [UIColor colorNamed:@"Base Colors/Green Color"];
|
||||
_openStatusText = L(@"editor_time_open");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case osm::No: {
|
||||
const int minutes = result.GetMinutesUntilOpen();
|
||||
if (minutes < 15) { // less than 15 minutes
|
||||
_openStatusColor = [UIColor colorNamed:@"Base Colors/Yellow Color"];
|
||||
NSString * time = [NSString stringWithFormat:@"%d %@", minutes, L(@"minute")];
|
||||
_openStatusText = [NSString stringWithFormat:L(@"opens_in"), time];
|
||||
}
|
||||
else if (minutes < 60) { // less than an hour (but more than 15 mins)
|
||||
_openStatusColor = [UIColor colorNamed:@"Base Colors/Red Color"];
|
||||
NSString * time = [NSString stringWithFormat:@"%d %@", minutes, L(@"minute")];
|
||||
_openStatusText = [NSString stringWithFormat:L(@"opens_in"), time];
|
||||
}
|
||||
else { // opens later or schedule is unknown
|
||||
_openStatusColor = [UIColor colorNamed:@"Base Colors/Red Color"];
|
||||
_openStatusText = L(@"closed");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case osm::Unknown: {
|
||||
_openStatusText = nil;
|
||||
_openStatusColor = UIColor.clearColor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_isPopularHidden = YES; // Restore logic in the future when popularity is available.
|
||||
_isPureSuggest = result.GetResultType() == search::Result::Type::PureSuggest;
|
||||
|
||||
NSMutableArray<NSValue *> * ranges = [NSMutableArray array];
|
||||
size_t const rangesCount = result.GetHighlightRangesCount();
|
||||
for (size_t i = 0; i < rangesCount; ++i) {
|
||||
auto const &range = result.GetHighlightRange(i);
|
||||
NSRange nsRange = NSMakeRange(range.first, range.second);
|
||||
[ranges addObject:[NSValue valueWithRange:nsRange]];
|
||||
}
|
||||
_highlightRanges = [ranges copy];
|
||||
|
||||
_itemType = itemType;
|
||||
|
||||
if (result.GetResultType() == search::Result::Type::Feature) {
|
||||
auto const featureType = result.GetFeatureType();
|
||||
auto const bookmarkImage = GetBookmarkIconByFeatureType(featureType);
|
||||
_iconImageName = [NSString stringWithFormat:@"%@%@",
|
||||
@"ic_bm_",
|
||||
[@(kml::ToString(bookmarkImage).c_str()) lowercaseString]];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
Loading…
Add table
Add a link
Reference in a new issue