diff --git a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.h b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.h index 585870871f..1913abd978 100644 --- a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.h +++ b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.h @@ -3,22 +3,16 @@ #include "storage/index.hpp" -@protocol MWMMapDownloaderDataSourceProtocol +@interface MWMMapDownloaderDataSource : NSObject -- (BOOL)isParentRoot; +@property (nonatomic, readonly) BOOL isParentRoot; + +- (instancetype)initWithDelegate:(id)delegate; - (storage::TCountryId)parentCountryId; - (storage::TCountryId)countryIdForIndexPath:(NSIndexPath *)indexPath; - (NSString *)cellIdentifierForIndexPath:(NSIndexPath *)indexPath; - (void)fillCell:(MWMMapDownloaderTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; -@optional - - (NSString *)searchMatchedResultForCountryId:(storage::TCountryId)countryId; @end - -@interface MWMMapDownloaderDataSource : NSObject - -- (instancetype)initWithDelegate:(id)delegate; - -@end diff --git a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.mm b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.mm index 4ecefb7c73..b1556eac26 100644 --- a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.mm +++ b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDataSource.mm @@ -34,14 +34,8 @@ using namespace storage; static_cast(cell).needDisplayArea = self.isParentRoot; if ([cell isKindOfClass:[MWMMapDownloaderSubplaceTableViewCell class]]) - { - BOOL const correctDataSource = [self respondsToSelector:@selector(searchMatchedResultForCountryId:)]; - NSAssert(correctDataSource, @"Invalid data source"); - if (!correctDataSource) - return; [static_cast(cell) setSubplaceText:[self searchMatchedResultForCountryId:countryId]]; - } [cell setCountryId:countryId]; } @@ -63,7 +57,7 @@ using namespace storage; return cell; } -#pragma mark - MWMMapDownloaderDataSourceProtocol +#pragma mark - MWMMapDownloaderDataSource - (BOOL)isParentRoot { @@ -85,4 +79,9 @@ using namespace storage; return nil; } +- (NSString *)searchMatchedResultForCountryId:(storage::TCountryId)countryId +{ + return nil; +} + @end diff --git a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.h b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.h index 4c9a3a00af..297b91b5fb 100644 --- a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.h +++ b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.h @@ -2,6 +2,10 @@ @interface MWMMapDownloaderDefaultDataSource : MWMMapDownloaderDataSource +@property (nonatomic, readonly) BOOL needFullReload; + - (instancetype)initForRootCountryId:(storage::TCountryId)countryId delegate:(id)delegate; +- (void)reload; +- (std::vector)getReloadSections; @end diff --git a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.mm b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.mm index f4b470f382..8a5f7a5972 100644 --- a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.mm +++ b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderDefaultDataSource.mm @@ -7,6 +7,21 @@ extern NSString * const kSubplaceCellIdentifier; extern NSString * const kPlaceCellIdentifier; extern NSString * const kLargeCountryCellIdentifier; +namespace +{ +auto compareStrings = ^NSComparisonResult(NSString * s1, NSString * s2) +{ + return [s1 compare:s2 options:NSCaseInsensitiveSearch range:{0, s1.length} locale:[NSLocale currentLocale]]; +}; +auto compareLocalNames = ^NSComparisonResult(NSString * s1, NSString * s2) +{ + auto const & s = GetFramework().Storage(); + string l1 = s.GetNodeLocalName(s1.UTF8String); + string l2 = s.GetNodeLocalName(s2.UTF8String); + return compareStrings(@(l1.c_str()), @(l2.c_str())); +}; +} // namespace + using namespace storage; @interface MWMMapDownloaderDefaultDataSource () @@ -14,34 +29,89 @@ using namespace storage; @property (copy, nonatomic) NSArray * indexes; @property (copy, nonatomic) NSDictionary *> * countryIds; +@property (copy, nonatomic) NSArray * downloadedCoutryIds; +@property (nonatomic, readwrite) NSInteger downloadedCountrySection; +@property (nonatomic, readonly) NSInteger countrySectionsShift; + +@property (nonatomic, readwrite) BOOL needFullReload; + @end @implementation MWMMapDownloaderDefaultDataSource { TCountryId m_parentId; + std::vector m_reloadSections; } +@synthesize isParentRoot = _isParentRoot; + - (instancetype)initForRootCountryId:(storage::TCountryId)countryId delegate:(id)delegate { self = [super initWithDelegate:delegate]; if (self) - [self configData:countryId]; + { + m_parentId = countryId; + _isParentRoot = (m_parentId == GetFramework().Storage().GetRootId()); + [self loadData]; + } return self; } -- (void)configData:(TCountryId)countryId +- (void)loadData { - m_parentId = countryId; auto const & s = GetFramework().Storage(); - TCountriesVec children; - s.GetChildren(m_parentId, children); - NSMutableSet * indexSet = [NSMutableSet setWithCapacity:children.size()]; + TCountriesVec downloadedChildren; + TCountriesVec availableChildren; + s.GetChildrenInGroups(m_parentId, downloadedChildren, availableChildren); + [self configAvailableSections:availableChildren]; + [self configDownloadedSection:downloadedChildren]; +} + +- (void)reload +{ + // Get old data for comparison. + NSArray * downloadedCoutryIds = [self.downloadedCoutryIds copy]; + NSDictionary *> * countryIds = [self.countryIds copy]; + BOOL const hadDownloadedCountries = self.haveDownloadedCountries; + + // Load updated data. + [self loadData]; + + // Compare new data vs old data to understand what kind of reload is required and what sections need reload. + self.needFullReload = (hadDownloadedCountries != self.haveDownloadedCountries || countryIds.count == 0); + if (self.needFullReload) + return; + if (hadDownloadedCountries && ![downloadedCoutryIds isEqualToArray:self.downloadedCoutryIds]) + m_reloadSections.push_back(self.downloadedCountrySection); + [countryIds enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSArray * obj, BOOL * stop) + { + NSArray * sectionCountries = self.countryIds[key]; + if (!sectionCountries) + { + self.needFullReload = YES; + *stop = YES; + } + if (![obj isEqualToArray:sectionCountries]) + self->m_reloadSections.push_back([self.indexes indexOfObject:key] + self.countrySectionsShift); + }]; +} + +- (std::vector)getReloadSections +{ + return m_reloadSections; +} + +- (void)configAvailableSections:(TCountriesVec const &)availableChildren +{ + NSMutableSet * indexSet = [NSMutableSet setWithCapacity:availableChildren.size()]; NSMutableDictionary *> * countryIds = [@{} mutableCopy]; BOOL const isParentRoot = self.isParentRoot; - for (auto const & countryId : children) + auto const & s = GetFramework().Storage(); + for (auto const & countryId : availableChildren) { NSString * nsCountryId = @(countryId.c_str()); - NSString * index = isParentRoot ? [L(nsCountryId) substringToIndex:1].capitalizedString : @"all_values"; + string localName = s.GetNodeLocalName(countryId); + NSString * index = isParentRoot ? [@(localName.c_str()) substringToIndex:1].capitalizedString : @"all_values"; [indexSet addObject:index]; NSMutableArray * letterIds = [countryIds[index] mutableCopy]; @@ -49,30 +119,41 @@ using namespace storage; [letterIds addObject:nsCountryId]; countryIds[index] = [letterIds copy]; } - NSLocale * currentLocale = [NSLocale currentLocale]; - auto sort = ^NSComparisonResult(NSString * s1, NSString * s2) - { - NSString * l1 = L(s1); - return [l1 compare:L(s2) options:NSCaseInsensitiveSearch range:{0, l1.length} locale:currentLocale]; - }; - self.indexes = [[indexSet allObjects] sortedArrayUsingComparator:sort]; + self.indexes = [[indexSet allObjects] sortedArrayUsingComparator:compareStrings]; [countryIds enumerateKeysAndObjectsUsingBlock:^(NSString * key, NSArray * obj, BOOL * stop) { - countryIds[key] = [obj sortedArrayUsingComparator:sort]; + countryIds[key] = [obj sortedArrayUsingComparator:compareLocalNames]; }]; self.countryIds = countryIds; } +- (void)configDownloadedSection:(TCountriesVec const &)downloadedChildren +{ + self.downloadedCoutryIds = nil; + self.downloadedCountrySection = NSNotFound; + NSMutableArray * nsDownloadedCoutryIds = [@[] mutableCopy]; + for (auto const & countryId : downloadedChildren) + [nsDownloadedCoutryIds addObject:@(countryId.c_str())]; + [nsDownloadedCoutryIds sortUsingComparator:compareLocalNames]; + if (nsDownloadedCoutryIds.count != 0) + { + self.downloadedCoutryIds = nsDownloadedCoutryIds; + self.downloadedCountrySection = 0; + } +} + #pragma mark - UITableViewDataSource - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return self.indexes.count; + return self.indexes.count + self.countrySectionsShift; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - NSString * index = self.indexes[section]; + if (section == self.downloadedCountrySection) + return self.downloadedCoutryIds.count; + NSString * index = self.indexes[section - self.countrySectionsShift]; return self.countryIds[index].count; } @@ -83,20 +164,19 @@ using namespace storage; - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { - return index; + return index + self.countrySectionsShift; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - return self.isParentRoot ? self.indexes[section] : nil; + if (!self.isParentRoot) + return nil; + if (section == self.downloadedCountrySection) + return L(@"downloader_downloaded_maps"); + return self.indexes[section - self.countrySectionsShift]; } -#pragma mark - MWMMapDownloaderDataSourceProtocol - -- (BOOL)isParentRoot -{ - return (m_parentId == GetFramework().Storage().GetRootId()); -} +#pragma mark - MWMMapDownloaderDataSource - (TCountryId)parentCountryId { @@ -105,7 +185,11 @@ using namespace storage; - (TCountryId)countryIdForIndexPath:(NSIndexPath *)indexPath { - NSString * index = self.indexes[indexPath.section]; + NSInteger const section = indexPath.section; + NSInteger const row = indexPath.row; + if (section == self.downloadedCountrySection) + return self.downloadedCoutryIds[row].UTF8String; + NSString * index = self.indexes[section - self.countrySectionsShift]; NSArray * countryIds = self.countryIds[index]; NSString * nsCountryId = countryIds[indexPath.row]; return nsCountryId.UTF8String; @@ -122,4 +206,23 @@ using namespace storage; return self.isParentRoot ? kCountryCellIdentifier : kPlaceCellIdentifier; } +#pragma mark - Properties + +- (NSInteger)countrySectionsShift +{ + return (self.haveDownloadedCountries ? self.downloadedCountrySection + 1 : 0); +} + +- (BOOL)haveDownloadedCountries +{ + return (self.downloadedCountrySection != NSNotFound); +} + +- (void)setNeedFullReload:(BOOL)needFullReload +{ + _needFullReload = needFullReload; + if (needFullReload) + m_reloadSections.clear(); +} + @end diff --git a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderExtendedDataSource.mm b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderExtendedDataSource.mm index a82458af77..8ad9858c06 100644 --- a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderExtendedDataSource.mm +++ b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderExtendedDataSource.mm @@ -6,13 +6,20 @@ using namespace storage; +@interface MWMMapDownloaderDefaultDataSource () + +@property (nonatomic, readonly) NSInteger downloadedCountrySection; + +- (void)reload; + +@end + @interface MWMMapDownloaderExtendedDataSource () @property (copy, nonatomic) NSArray * closestCoutryIds; -@property (copy, nonatomic) NSArray * downloadedCoutryIds; -@property (nonatomic) NSInteger baseSectionShift; + @property (nonatomic) NSInteger closestCountriesSection; -@property (nonatomic) NSInteger downloadedCountriesSection; +@property (nonatomic, readonly) NSInteger closestCountriesSectionShift; @end @@ -22,14 +29,21 @@ using namespace storage; { self = [super initForRootCountryId:countryId delegate:delegate]; if (self) - { - self.baseSectionShift = 0; [self configNearMeSection]; - [self configDownloadedSection]; - } return self; } +- (std::vector)getReloadSections +{ + std::vector sections = [super getReloadSections]; + if (self.haveClosestCountries) + { + for (auto & section : sections) + section += self.closestCountriesSectionShift; + } + return sections; +} + - (void)configNearMeSection { self.closestCoutryIds = nil; @@ -46,24 +60,7 @@ using namespace storage; if (nsClosestCoutryIds.count != 0) { self.closestCoutryIds = nsClosestCoutryIds; - self.closestCountriesSection = self.baseSectionShift++; - } -} - -- (void)configDownloadedSection -{ - self.downloadedCoutryIds = nil; - self.downloadedCountriesSection = NSNotFound; - auto const & s = GetFramework().Storage(); - TCountriesVec downloadedCoutryIds, availableCountryIds; - s.GetChildrenInGroups(self.parentCountryId, downloadedCoutryIds, availableCountryIds); - NSMutableArray * nsDownloadedCoutryIds = [@[] mutableCopy]; - for (auto const & countryId : downloadedCoutryIds) - [nsDownloadedCoutryIds addObject:@(countryId.c_str())]; - if (nsDownloadedCoutryIds.count != 0) - { - self.downloadedCoutryIds = nsDownloadedCoutryIds; - self.downloadedCountriesSection = self.baseSectionShift++; + self.closestCountriesSection = 0; } } @@ -71,53 +68,49 @@ using namespace storage; - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - NSInteger const numberOfSections = [super numberOfSectionsInTableView:tableView]; - return numberOfSections + self.baseSectionShift; + return [super numberOfSectionsInTableView:tableView] + self.closestCountriesSectionShift; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - if (section >= self.baseSectionShift) - return [super tableView:tableView numberOfRowsInSection:section - self.baseSectionShift]; if (section == self.closestCountriesSection) return self.closestCoutryIds.count; - if (section == self.downloadedCountriesSection) - return self.downloadedCoutryIds.count; - NSAssert(NO, @"Invalid section"); - return 0; + return [super tableView:tableView numberOfRowsInSection:section - self.closestCountriesSectionShift]; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { - return [super tableView:tableView sectionForSectionIndexTitle:title atIndex:index] + self.baseSectionShift; + return [super tableView:tableView sectionForSectionIndexTitle:title atIndex:index] + self.closestCountriesSectionShift; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - if (section >= self.baseSectionShift) - return [super tableView:tableView titleForHeaderInSection:section - self.baseSectionShift]; if (section == self.closestCountriesSection) return L(@"search_mode_nearme"); - if (section == self.downloadedCountriesSection) - return L(@"downloader_downloaded_maps"); - NSAssert(NO, @"Invalid section"); - return nil; + return [super tableView:tableView titleForHeaderInSection:section - self.closestCountriesSectionShift]; } -#pragma mark - MWMMapDownloaderDataSourceProtocol +#pragma mark - MWMMapDownloaderDataSource - (TCountryId)countryIdForIndexPath:(NSIndexPath *)indexPath { NSInteger const row = indexPath.row; NSInteger const section = indexPath.section; - if (section >= self.baseSectionShift) - return [super countryIdForIndexPath:[NSIndexPath indexPathForRow:row inSection:section - self.baseSectionShift]]; if (section == self.closestCountriesSection) return self.closestCoutryIds[row].UTF8String; - if (section == self.downloadedCountriesSection) - return self.downloadedCoutryIds[row].UTF8String; - NSAssert(NO, @"Invalid section"); - return kInvalidCountryId; + return [super countryIdForIndexPath:[NSIndexPath indexPathForRow:row inSection:section - self.closestCountriesSectionShift]]; +} + +#pragma mark - Properties + +- (NSInteger)closestCountriesSectionShift +{ + return (self.haveClosestCountries ? self.closestCountriesSection + 1 : 0); +} + +- (BOOL)haveClosestCountries +{ + return (self.closestCountriesSection != NSNotFound); } @end diff --git a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderSearchDataSource.mm b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderSearchDataSource.mm index 5b823455eb..d9b81eebea 100644 --- a/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderSearchDataSource.mm +++ b/iphone/Maps/Classes/MapDownloader/DataSources/MWMMapDownloaderSearchDataSource.mm @@ -54,7 +54,7 @@ extern NSString * const kPlaceCellIdentifier; return self.searchCoutryIds.count; } -#pragma mark - MWMMapDownloaderDataSourceProtocol +#pragma mark - MWMMapDownloaderDataSource - (TCountryId)countryIdForIndexPath:(NSIndexPath *)indexPath { diff --git a/iphone/Maps/Classes/MapDownloader/MWMBaseMapDownloaderViewController.mm b/iphone/Maps/Classes/MapDownloader/MWMBaseMapDownloaderViewController.mm index 8266a9e573..583a243b45 100644 --- a/iphone/Maps/Classes/MapDownloader/MWMBaseMapDownloaderViewController.mm +++ b/iphone/Maps/Classes/MapDownloader/MWMBaseMapDownloaderViewController.mm @@ -1,6 +1,7 @@ #import "Common.h" #import "MapsAppDelegate.h" #import "MWMAlertViewController.h" +#import "MWMFrameworkListener.h" #import "MWMMapDownloaderDefaultDataSource.h" #import "MWMMapDownloaderLargeCountryTableViewCell.h" #import "MWMMapDownloaderPlaceTableViewCell.h" @@ -29,7 +30,7 @@ NSString * const kShowActionTitle = L(@"zoom_to_country"); NSString * const kCancelActionTitle = L(@"cancel"); } // namespace -@interface MWMBaseMapDownloaderViewController () +@interface MWMBaseMapDownloaderViewController () @property (weak, nonatomic) IBOutlet UITableView * tableView; @@ -42,7 +43,7 @@ NSString * const kCancelActionTitle = L(@"cancel"); @property (nonatomic) CGFloat lastScrollOffset; @property (nonatomic) MWMMapDownloaderDataSource * dataSource; -@property (nonatomic) MWMMapDownloaderDataSource * defaultDataSource; +@property (nonatomic) MWMMapDownloaderDefaultDataSource * defaultDataSource; @end @@ -59,6 +60,7 @@ using namespace storage; [self configNavBar]; [self configTable]; [self configAllMapsView]; + [MWMFrameworkListener addObserver:self]; } - (void)viewWillAppear:(BOOL)animated @@ -86,6 +88,33 @@ using namespace storage; self.title = self.dataSource.isParentRoot ? L(@"download_maps") : L(@(self.parentCountryId.c_str())); } +#pragma mark - MWMFrameworkStorageObserver + +- (void)processCountryEvent:(TCountryId const &)countryId +{ + storage::NodeAttrs nodeAttrs; + GetFramework().Storage().GetNodeAttrs(countryId, nodeAttrs); + MWMMapDownloaderDefaultDataSource * dataSource = self.defaultDataSource; + [dataSource reload]; + if (![self.dataSource isEqual:dataSource]) + return; + + if (dataSource.needFullReload) + { + [self reloadData]; + return; + } + + UITableView * tv = self.tableView; + std::vector sections = [dataSource getReloadSections]; + if (sections.empty()) + return; + NSMutableIndexSet * indexSet = [NSMutableIndexSet indexSet]; + for (auto & section : sections) + [indexSet addIndex:section]; + [tv reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic]; +} + #pragma mark - Table - (void)registerCellWithIdentifier:(NSString *)identifier @@ -378,6 +407,7 @@ using namespace storage; - (void)setTableView:(UITableView *)tableView { _tableView = tableView; + _tableView.tableFooterView = [[UIView alloc] initWithFrame:{}]; self.dataSource = self.defaultDataSource; } @@ -407,4 +437,20 @@ using namespace storage; return static_cast(self.tableView.dataSource); } +#pragma mark - Helpers + +- (void)reloadData +{ + UITableView * tv = self.tableView; + // If these methods are not called, tableView will not call tableView:cellForRowAtIndexPath: + [tv setNeedsLayout]; + [tv layoutIfNeeded]; + + [tv reloadData]; + + // If these methods are not called, tableView will not display new cells + [tv setNeedsLayout]; + [tv layoutIfNeeded]; +} + @end diff --git a/iphone/Maps/Classes/MapDownloader/MWMMapDownloaderViewController.mm b/iphone/Maps/Classes/MapDownloader/MWMMapDownloaderViewController.mm index 883f051f39..0c8c85779b 100644 --- a/iphone/Maps/Classes/MapDownloader/MWMMapDownloaderViewController.mm +++ b/iphone/Maps/Classes/MapDownloader/MWMMapDownloaderViewController.mm @@ -18,6 +18,8 @@ using namespace storage; @property (nonatomic) MWMMapDownloaderDataSource * dataSource; @property (nonatomic) MWMMapDownloaderDataSource * defaultDataSource; +- (void)reloadData; + @end @interface MWMMapDownloaderViewController () @@ -38,7 +40,6 @@ using namespace storage; { [super viewDidLoad]; self.searchBar.placeholder = L(@"search_downloaded_maps"); - self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero]; [self setupSearchParams]; } @@ -159,19 +160,4 @@ using namespace storage; self.defaultDataSource = [[MWMMapDownloaderExtendedDataSource alloc] initForRootCountryId:parentId delegate:self]; } -#pragma mark - Helpers - -- (void)reloadData -{ - // If these methods are not called, tableView will not call tableView:cellForRowAtIndexPath: - [self.tableView setNeedsLayout]; - [self.tableView layoutIfNeeded]; - - [self.tableView reloadData]; - - // If these methods are not called, tableView will not display new cells - [self.tableView setNeedsLayout]; - [self.tableView layoutIfNeeded]; -} - @end