diff options
Diffstat (limited to 'iphone/Maps/Classes/MWMPlacePageLayout.mm')
-rw-r--r-- | iphone/Maps/Classes/MWMPlacePageLayout.mm | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/iphone/Maps/Classes/MWMPlacePageLayout.mm b/iphone/Maps/Classes/MWMPlacePageLayout.mm new file mode 100644 index 0000000000..b8e4a55a3c --- /dev/null +++ b/iphone/Maps/Classes/MWMPlacePageLayout.mm @@ -0,0 +1,530 @@ +#import "MWMCircularProgress.h" +#import "MWMPlacePageActionBar.h" +#import "MWMPlacePageCellUpdateProtocol.h" +#import "MWMPlacePageData.h" +#import "MWMPlacePageLayout.h" +#import "MWMPPView.h" + +#import "MWMBookmarkCell.h" +#import "MWMPlacePageBookmarkCell.h" +#import "MWMPlacePageButtonCell.h" +#import "MWMPlacePageInfoCell.h" +#import "MWMOpeningHoursCell.h" +#import "MWMPlacePagePreviewCell.h" +#import "UIColor+MapsMeColor.h" + +#include "storage/storage.hpp" + +#include "std/array.hpp" + +namespace +{ +enum class ScrollDirection { Up, Down }; + +enum class State { Bottom, Top }; + +CGFloat const kMinOffset = 64; +CGFloat const kOpenPlacePageStopValue = 0.7; + +array<NSString *, 1> kPreviewCells = {{@"MWMPlacePagePreviewCell"}}; + +array<NSString *, 1> kBookmarkCells = {{@"MWMBookmarkCell"}}; + +array<NSString *, 9> kMetaInfoCells = {{@"MWMOpeningHoursCell", @"PlacePageLinkCell", @"PlacePageInfoCell", @"PlacePageLinkCell", @"PlacePageLinkCell", @"PlacePageInfoCell", @"PlacePageInfoCell", @"PlacePageInfoCell", @"PlacePageInfoCell"}}; + +array<NSString *, 1> kButtonsCells = {{@"MWMPlacePageButtonCell"}}; + +NSTimeInterval const kAnimationDuration = 0.15; + +void animate(TMWMVoidBlock animate, TMWMVoidBlock completion = nil) +{ + [UIView animateWithDuration:kAnimationDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ + animate(); + } completion:^(BOOL finished) { + if (completion) + completion(); + }]; +} + +} // namespace + +@interface MWMPlacePageLayout () <UITableViewDelegate, UITableViewDataSource, + MWMPlacePageCellUpdateProtocol, MWMPlacePageViewUpdateProtocol> + +@property(weak, nonatomic) MWMPlacePageData * data; + +@property(weak, nonatomic) UIView * ownerView; +@property(weak, nonatomic) id<MWMPlacePageLayoutDelegate, MWMPlacePageButtonsProtocol, MWMActionBarProtocol> delegate; +@property(weak, nonatomic) id<MWMPlacePageLayoutDataSource> dataSource; + +@property(nonatomic) MWMPPScrollView * scrollView; +@property(nonatomic) IBOutlet MWMPPView * placePageView; + +@property(nonatomic) ScrollDirection direction; +@property(nonatomic) State state; + +@property(nonatomic) CGFloat portraitOpenContentOffset; +@property(nonatomic) CGFloat landscapeOpenContentOffset; +@property(nonatomic) CGFloat lastContentOffset; +@property(nonatomic) CGFloat expandedContentOffset; + +@property(nonatomic) MWMPlacePagePreviewCell * ppPreviewCell; +@property(nonatomic) MWMBookmarkCell * bookmarkCell; + +@property(nonatomic) MWMPlacePageActionBar * actionBar; + +@property(nonatomic) BOOL isPlacePageButtonsEnabled; +@property(nonatomic) BOOL isDownloaderViewShown; + +@end + +@implementation MWMPlacePageLayout + +- (instancetype)initWithOwnerView:(UIView *)view + delegate:(id<MWMPlacePageLayoutDelegate, MWMPlacePageButtonsProtocol, MWMActionBarProtocol>)delegate + dataSource:(id<MWMPlacePageLayoutDataSource>)dataSource +{ + self = [super init]; + if (self) + { + _ownerView = view; + _delegate = delegate; + _dataSource = dataSource; + + [[NSBundle mainBundle] loadNibNamed:[MWMPPView className] owner:self options:nil]; + auto view = self.ownerView; + auto const & size = view.size; + _placePageView.frame = {{0, size.height}, size}; + _placePageView.delegate = self; + _scrollView = [[MWMPPScrollView alloc] initWithFrame:view.frame inactiveView:_placePageView]; + _portraitOpenContentOffset = MAX(size.width, size.height) * kOpenPlacePageStopValue; + _landscapeOpenContentOffset = MIN(size.width, size.height) * kOpenPlacePageStopValue; + [_ownerView addSubview:_scrollView]; + [_scrollView addSubview:_placePageView]; + + auto tv = _placePageView.tableView; + [tv registerNib:[UINib nibWithNibName:kPreviewCells[0] bundle:nil] forCellReuseIdentifier:kPreviewCells[0]]; + [tv registerNib:[UINib nibWithNibName:kButtonsCells[0] bundle:nil] forCellReuseIdentifier:kButtonsCells[0]]; + [tv registerNib:[UINib nibWithNibName:kBookmarkCells[0] bundle:nil] forCellReuseIdentifier:kBookmarkCells[0]]; + } + return self; +} + +- (void)registerCells +{ + auto tv = self.placePageView.tableView; + // Register all meta info cells. + for (auto const i : self.data.metainfoRows) + { + auto name = kMetaInfoCells[static_cast<size_t>(i)]; + [tv registerNib:[UINib nibWithNibName:name bundle:nil] forCellReuseIdentifier:name]; + } +} + +- (void)layoutWithSize:(CGSize const &)size +{ + self.scrollView.frame = {{}, size}; + self.placePageView.origin = {0., size.height}; + self.actionBar.frame = {{0., size.height - 48}, {size.width, 48}}; + [self.delegate onTopBoundChanged:self.scrollView.contentOffset.y]; +} + +- (UIView *)shareAnchor +{ + return self.actionBar.shareAnchor; +} + +- (void)showWithData:(MWMPlacePageData *)data +{ + self.isPlacePageButtonsEnabled = YES; + self.data = nil; + self.ppPreviewCell = nil; + self.bookmarkCell = nil; + + self.scrollView.delegate = self; + self.state = State::Bottom; + + [self collapse]; + + self.data = data; + [self.actionBar configureWithData:static_cast<id<MWMActionBarSharedData>>(self.data)]; + [self registerCells]; + [self.placePageView.tableView reloadData]; +} + +- (void)rotateDirectionArrowToAngle:(CGFloat)angle +{ + [self.ppPreviewCell rotateDirectionArrowToAngle:angle]; +} + +- (void)setDistanceToObject:(NSString *)distance +{ + [self.ppPreviewCell setDistanceToObject:distance]; +} + +- (MWMPlacePageActionBar *)actionBar +{ + if (!_actionBar) + { + _actionBar = [MWMPlacePageActionBar actionBarWithDelegate:self.delegate]; + UIView * superview = self.ownerView; + _actionBar.origin = {0., superview.height}; + [superview addSubview:_actionBar]; + } + return _actionBar; +} + +- (void)close +{ + animate(^ { + self.actionBar.origin = {0., self.ownerView.height}; + [self.scrollView setContentOffset:{} animated:YES]; + }, ^{ + [self.actionBar removeFromSuperview]; + self.actionBar = nil; + [self.delegate shouldDestroyLayout]; + }); +} + +- (void)mwm_refreshUI +{ + [self.placePageView mwm_refreshUI]; + [self.actionBar mwm_refreshUI]; +} + +- (void)reloadBookmarkSection:(BOOL)isBookmark +{ + auto tv = self.placePageView.tableView; + NSIndexSet * set = [NSIndexSet indexSetWithIndex:static_cast<NSInteger>(place_page::Sections::Bookmark)]; + + if (isBookmark) + { + if (self.bookmarkCell) + [tv reloadSections:set withRowAnimation:UITableViewRowAnimationAutomatic]; + else + [tv insertSections:set withRowAnimation:UITableViewRowAnimationAutomatic]; + } + else + { + [tv deleteSections:set withRowAnimation:UITableViewRowAnimationAutomatic]; + self.bookmarkCell = nil; + } +} + +- (void)collapse +{ + self.actionBar.hidden = YES; + + [self.placePageView hideTableView:YES]; + self.scrollView.scrollEnabled = NO; + + animate(^{ + self.actionBar.origin = {0., self.ownerView.height}; + [self.scrollView setContentOffset:{0., kMinOffset} animated:YES]; + }); +} + +- (void)expand +{ + self.actionBar.hidden = NO; + self.scrollView.scrollEnabled = YES; + + animate(^{ + [self.placePageView hideTableView:NO]; + self.actionBar.minY = self.actionBar.superview.height - self.actionBar.height; + self.expandedContentOffset = self.ppPreviewCell.height + self.placePageView.top.height + self.actionBar.height; + auto const targetOffset = self.state == State::Bottom ? self.expandedContentOffset : self.topContentOffset; + [self.scrollView setContentOffset:{0, targetOffset} animated:YES]; + }); +} + +- (BOOL)isPortrait +{ + auto const & s = self.ownerView.size; + return s.height > s.width; +} + +- (CGFloat)topContentOffset +{ + auto const target = self.isPortrait ? self.portraitOpenContentOffset : self.landscapeOpenContentOffset; + if (target > self.placePageView.height) + return self.placePageView.height; + + return target; +} + +#pragma mark - Downloader event + +- (void)processDownloaderEventWithStatus:(storage::NodeStatus)status progress:(CGFloat)progress +{ + using namespace storage; + + switch(status) + { + case NodeStatus::Undefined: + { + self.isPlacePageButtonsEnabled = YES; + auto const & sections = self.data.sections; + auto const it = find(sections.begin(), sections.end(), place_page::Sections::Buttons); + if (it != sections.end()) + { + [self.placePageView.tableView reloadSections:[NSIndexSet indexSetWithIndex:distance(sections.begin(), it)] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + + if (self.ppPreviewCell) + [self.ppPreviewCell setDownloaderViewHidden:YES animated:NO]; + else + self.isDownloaderViewShown = NO; + + break; + } + + case NodeStatus::Downloading: + self.ppPreviewCell.mapDownloadProgress.progress = progress; + break; + + case NodeStatus::InQueue: + self.ppPreviewCell.mapDownloadProgress.state = MWMCircularProgressStateSpinner; + break; + + case NodeStatus::Error: + self.ppPreviewCell.mapDownloadProgress.state = MWMCircularProgressStateFailed; + break; + + case NodeStatus::Partly: + break; + + case NodeStatus::OnDiskOutOfDate: + case NodeStatus::OnDisk: + { + self.isPlacePageButtonsEnabled = YES; + auto const & sections = self.data.sections; + auto const it = find(sections.begin(), sections.end(), place_page::Sections::Buttons); + if (it != sections.end()) + { + [self.placePageView.tableView reloadSections:[NSIndexSet indexSetWithIndex:distance(sections.begin(), it)] + withRowAnimation:UITableViewRowAnimationAutomatic]; + } + [self.ppPreviewCell setDownloaderViewHidden:YES animated:NO]; + break; + } + + case NodeStatus::NotDownloaded: + self.isPlacePageButtonsEnabled = NO; + if (self.ppPreviewCell) + [self.ppPreviewCell setDownloaderViewHidden:NO animated:NO]; + else + self.isDownloaderViewShown = YES; + + break; + } +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(MWMPPScrollView *)scrollView +{ + if ([scrollView isEqual:self.placePageView.tableView]) + return; + + auto const offset = scrollView.contentOffset; + if (offset.y <= 0) + { + [self.scrollView removeFromSuperview]; + [self.actionBar removeFromSuperview]; + [self.delegate shouldDestroyLayout]; + return; + } + + if (offset.y > self.placePageView.height + 30) + { + auto const bounded = self.placePageView.height + 30; + [scrollView setContentOffset:{0, bounded}]; + [self.delegate onTopBoundChanged:bounded]; + } + else + { + [self.delegate onTopBoundChanged:offset.y]; + } + + self.direction = self.lastContentOffset < offset.y ? ScrollDirection::Up : ScrollDirection::Down; + self.lastContentOffset = offset.y; +} + +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView + withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset +{ + auto const actualOffset = scrollView.contentOffset.y; + auto const openOffset = self.isPortrait ? self.portraitOpenContentOffset : self.landscapeOpenContentOffset; + auto const targetOffset = (*targetContentOffset).y; + + if (actualOffset > self.expandedContentOffset && actualOffset < openOffset) + { + self.state = self.direction == ScrollDirection::Up ? State::Top : State::Bottom; + (*targetContentOffset).y = self.direction == ScrollDirection::Up ? openOffset : self.expandedContentOffset; + } + else if (actualOffset > openOffset && targetOffset < openOffset) + { + self.state = State::Top; + (*targetContentOffset).y = openOffset; + } + else if (actualOffset < self.expandedContentOffset) + { + (*targetContentOffset).y = 0; + animate(^{ + self.actionBar.origin = {0., self.ownerView.height}; + }); + } + else + { + self.state = State::Top; + } +} + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + if (decelerate) + return; + + auto const actualOffset = scrollView.contentOffset.y; + auto const openOffset = self.isPortrait ? self.portraitOpenContentOffset : self.landscapeOpenContentOffset; + if (actualOffset < self.expandedContentOffset + 30) + { + self.state = State::Bottom; + animate(^{ + [scrollView setContentOffset:{0, self.expandedContentOffset} animated:YES]; + }); + } + else if (actualOffset < openOffset) + { + self.state = self.direction == ScrollDirection::Up ? State::Top : State::Bottom; + animate(^{ + [scrollView setContentOffset:{0, self.direction == ScrollDirection::Up ? openOffset : self.expandedContentOffset} + animated:YES]; + }); + } + else + { + self.state = State::Top; + } +} + +#pragma mark - UITableViewDelegate & UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + if (!self.data) + return 0; + return self.data.sections.size(); +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + using namespace place_page; + + switch (self.data.sections[section]) + { + case Sections::Preview: + case Sections::Bookmark: return 1; + case Sections::Metainfo: return self.data.metainfoRows.size(); + case Sections::Buttons: return self.data.buttonsRows.size(); + } +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + using namespace place_page; + + switch (self.data.sections[indexPath.section]) + { + case Sections::Preview: + { + if (!self.ppPreviewCell) + self.ppPreviewCell = [tableView dequeueReusableCellWithIdentifier:[MWMPlacePagePreviewCell className]]; + + [self.ppPreviewCell configure:self.data updateLayoutDelegate:self dataSource:self.dataSource]; + [self.ppPreviewCell setDownloaderViewHidden:!self.isDownloaderViewShown animated:NO]; + + return self.ppPreviewCell; + } + case Sections::Bookmark: + { + MWMBookmarkCell * c = [tableView dequeueReusableCellWithIdentifier:kBookmarkCells[0]]; + [c configureWithText:self.data.bookmarkDescription + updateCellDelegate:self + editBookmarkDelegate:self.delegate + isHTML:self.data.isHTMLDescription]; + return c; + } + case Sections::Metainfo: + { + auto const row = self.data.metainfoRows[indexPath.row]; + auto cellName = kMetaInfoCells[static_cast<size_t>(row)]; + UITableViewCell * c = [tableView dequeueReusableCellWithIdentifier:cellName]; + + switch (row) + { + case MetainfoRows::OpeningHours: + [static_cast<MWMOpeningHoursCell *>(c) configureWithOpeningHours:[self.data stringForRow:row] + updateLayoutDelegate:self + isClosedNow:self.data.schedule == OpeningHours::Closed]; + break; + case MetainfoRows::Phone: + case MetainfoRows::Address: + case MetainfoRows::Website: + case MetainfoRows::Email: + case MetainfoRows::Cuisine: + case MetainfoRows::Operator: + case MetainfoRows::Internet: + case MetainfoRows::Coordinate: + [static_cast<MWMPlacePageInfoCell *>(c) configWithRow:row data:self.data]; + break; + } + return c; + } + case Sections::Buttons: + { + MWMPlacePageButtonCell * c = [tableView dequeueReusableCellWithIdentifier:kButtonsCells[0]]; + auto const row = self.data.buttonsRows[indexPath.row]; + [c configForRow:row withDelegate:self.delegate]; + if (row != ButtonsRows::HotelDescription) + [c setEnabled:self.isPlacePageButtonsEnabled]; + + return c; + } + } +} + +#pragma mark - MWMPlacePageCellUpdateProtocol + +- (void)updateCellWithForceReposition:(BOOL)isForceReposition +{ + auto const update = isForceReposition ? @selector(updateWithExpand) : @selector(update); + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:update object:nil]; + [self performSelector:update withObject:nil afterDelay:0.5]; +} + +- (void)update +{ + auto tableView = self.placePageView.tableView; + [tableView beginUpdates]; + [tableView endUpdates]; +} + +- (void)updateWithExpand +{ + [self update]; + [self expand]; +} + +#pragma mark - MWMPlacePageViewUpdateProtocol + +- (void)updateWithHeight:(CGFloat)height +{ + auto const & size = self.ownerView.size; + self.scrollView.contentSize = {size.width, size.height + height}; +} + +@end |