diff --git a/iphone/CoreApi/CoreApi/Bookmarks/TrackInfo.mm b/iphone/CoreApi/CoreApi/Bookmarks/TrackInfo.mm index 64803f191..18f4d7480 100644 --- a/iphone/CoreApi/CoreApi/Bookmarks/TrackInfo.mm +++ b/iphone/CoreApi/CoreApi/Bookmarks/TrackInfo.mm @@ -4,7 +4,7 @@ @implementation TrackInfo + (TrackInfo *)emptyInfo { - return [[TrackInfo alloc] init]; + return [[TrackInfo alloc] initWithTrackStatistics:TrackStatistics()]; } @end diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h index d0be6b03f..2a958f107 100644 --- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h +++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h @@ -21,12 +21,12 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^SearchInDownloaderCompletions)(NSArray *results, BOOL finished); typedef void (^TrackRecordingUpdatedHandler)(TrackInfo * _Nonnull trackInfo); -@protocol TrackRecorder +@protocol TrackRecorder + (void)startTrackRecording; + (void)setTrackRecordingUpdateHandler:(TrackRecordingUpdatedHandler _Nullable)trackRecordingDidUpdate; + (void)stopTrackRecording; -+ (void)saveTrackRecordingWithName:(nullable NSString *)name; ++ (void)saveTrackRecordingWithName:(nonnull NSString *)name; + (BOOL)isTrackRecordingEnabled; + (BOOL)isTrackRecordingEmpty; /// Returns current track recording elevation info. diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm index 442d9931a..c5bcd6ba6 100644 --- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm +++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm @@ -239,8 +239,8 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore( GetFramework().StopTrackRecording(); } -+ (void)saveTrackRecordingWithName:(nullable NSString *)name { - GetFramework().SaveTrackRecordingWithName(name == nil ? "" : name.UTF8String); ++ (void)saveTrackRecordingWithName:(nonnull NSString *)name { + GetFramework().SaveTrackRecordingWithName(name.UTF8String); } + (BOOL)isTrackRecordingEnabled { @@ -252,7 +252,7 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore( } + (ElevationProfileData * _Nonnull)trackRecordingElevationInfo { - return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingCurrentElevationInfo()]; + return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingElevationInfo()]; } // MARK: - ProductsManager diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h index ab25289cb..8b5a10fa6 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h @@ -1,6 +1,7 @@ #import @class PlacePageScheduleData; +@class TrackInfo; typedef NS_ENUM(NSInteger, PlacePageDataHotelType) { PlacePageDataHotelTypeHotel, @@ -39,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) PlacePageDataSchedule schedule; @property(nonatomic, readonly) BOOL isMyPosition; +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo; + @end NS_ASSUME_NONNULL_END diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm index 9aed885cb..199c1b5e0 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm @@ -1,4 +1,8 @@ #import "PlacePagePreviewData+Core.h" +#import "DistanceFormatter.h" +#import "AltitudeFormatter.h" +#import "DurationFormatter.h" +#import "TrackInfo.h" #include "3party/opening_hours/opening_hours.hpp" @@ -46,6 +50,16 @@ static PlacePageDataSchedule convertOpeningHours(std::string_view rawOH) @implementation PlacePagePreviewData +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo { + self = [super init]; + if (self) { + // TODO: (KK) Replace separator with a shared static constant. + NSString * kSeparator = @" • "; + _title = [@[trackInfo.duration, trackInfo.distance] componentsJoinedByString:kSeparator]; + } + return self; +} + @end @implementation PlacePagePreviewData (Core) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h index 0a6ad57bf..fa83eb897 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h @@ -10,8 +10,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) MWMTrackID trackId; @property(nonatomic, readonly) MWMMarkGroupID groupId; -@property(nonatomic, readonly, nonnull) TrackInfo * trackInfo; -@property(nonatomic, readonly, nullable) ElevationProfileData * elevationProfileData; +@property(nonatomic, readwrite, nonnull) TrackInfo * trackInfo; +@property(nonatomic, readwrite, nullable) ElevationProfileData * elevationProfileData; + +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm index ad421d8f1..d8b4ff89f 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm @@ -4,6 +4,15 @@ @implementation PlacePageTrackData +- (nonnull instancetype)initWithTrackInfo:(TrackInfo *)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo { + self = [super init]; + if (self) { + _trackInfo = trackInfo; + _elevationProfileData = elevationInfo; + } + return self; +} + @end @implementation PlacePageTrackData (Core) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h index 48c63cde2..0a00cabca 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN elevationInfo:(ElevationInfo const &)elevationInfo activePoint:(double)activePoint myPosition:(double)myPosition; +- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h index f8b5a070e..2087a30a2 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h @@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, ElevationDifficulty) { @interface ElevationProfileData : NSObject @property(nonatomic, readonly) uint64_t trackId; +@property(nonatomic, readonly) BOOL isTrackRecording; @property(nonatomic, readonly) ElevationDifficulty difficulty; @property(nonatomic, readonly) NSArray * points; @property(nonatomic, readonly) double activePoint; diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm index 69e89c938..ffdd63c2d 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm @@ -30,23 +30,36 @@ static ElevationDifficulty convertDifficulty(uint8_t difficulty) { if (self) { _trackId = trackId; _difficulty = convertDifficulty(elevationInfo.GetDifficulty()); - - auto const & points = elevationInfo.GetPoints(); - NSMutableArray * pointsArray = [[NSMutableArray alloc] initWithCapacity:points.size()]; - for (auto const & point : points) { - auto pointLatLon = mercator::ToLatLon(point.m_point.GetPoint()); - CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(pointLatLon.m_lat, pointLatLon.m_lon); - ElevationHeightPoint * elevationPoint = [[ElevationHeightPoint alloc] initWithCoordinates:coordinates - distance:point.m_distance - andAltitude:point.m_point.GetAltitude()]; - [pointsArray addObject:elevationPoint]; - } - _points = [pointsArray copy]; + _points = [ElevationProfileData pointsFromElevationInfo:elevationInfo]; _activePoint = activePoint; _myPosition = myPosition; + _isTrackRecording = false; } return self; } +- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo { + self = [super init]; + if (self) { + _difficulty = convertDifficulty(elevationInfo.GetDifficulty()); + _points = [ElevationProfileData pointsFromElevationInfo:elevationInfo]; + _isTrackRecording = true; + } + return self; +} + ++ (NSArray *)pointsFromElevationInfo:(ElevationInfo const &)elevationInfo { + auto const & points = elevationInfo.GetPoints(); + NSMutableArray * pointsArray = [[NSMutableArray alloc] initWithCapacity:points.size()]; + for (auto const & point : points) { + auto pointLatLon = mercator::ToLatLon(point.m_point.GetPoint()); + CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(pointLatLon.m_lat, pointLatLon.m_lon); + ElevationHeightPoint * elevationPoint = [[ElevationHeightPoint alloc] initWithCoordinates:coordinates + distance:point.m_distance + andAltitude:point.m_point.GetAltitude()]; + [pointsArray addObject:elevationPoint]; + } + return [pointsArray copy]; +} @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h index c3c198816..99736a970 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h @@ -10,6 +10,7 @@ @class PlacePageBookmarkData; @class MWMMapNodeAttributes; @class TrackInfo; +@class ElevationProfileData; typedef NS_ENUM(NSInteger, PlacePageRoadType) { PlacePageRoadTypeToll, @@ -49,12 +50,15 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) CLLocationCoordinate2D locationCoordinate; @property(nonatomic, copy, nullable) MWMVoidBlock onBookmarkStatusUpdate; @property(nonatomic, copy, nullable) MWMVoidBlock onMapNodeStatusUpdate; +@property(nonatomic, copy, nullable) MWMVoidBlock onTrackRecordingProgressUpdate; @property(nonatomic, copy, nullable) void (^onMapNodeProgressUpdate)(uint64_t downloadedBytes, uint64_t totalBytes); - (instancetype)initWithLocalizationProvider:(id)localization; +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo; - (instancetype)init NS_UNAVAILABLE; - (void)updateBookmarkStatus; +- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm index cda7f2791..491452b0a 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm @@ -5,6 +5,7 @@ #import "PlacePageInfoData+Core.h" #import "PlacePageBookmarkData+Core.h" #import "PlacePageTrackData+Core.h" +#import "ElevationProfileData+Core.h" #import "MWMMapNodeAttributes.h" #include @@ -84,6 +85,25 @@ static PlacePageRoadType convertRoadType(RoadWarningMarkType roadType) { return self; } +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo { + self = [super init]; + if (self) { + _objectType = PlacePageObjectTypeTrackRecording; + _roadType = PlacePageRoadTypeNone; + _previewData = [[PlacePagePreviewData alloc] initWithTrackInfo:trackInfo]; + _trackData = [[PlacePageTrackData alloc] initWithTrackInfo:trackInfo elevationInfo:elevationInfo]; + } + return self; +} + +- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo { + _previewData = [[PlacePagePreviewData alloc] initWithTrackInfo:trackInfo]; + _trackData.trackInfo = trackInfo; + _trackData.elevationProfileData = elevationInfo; + if (self.onTrackRecordingProgressUpdate != nil) + self.onTrackRecordingProgressUpdate(); +} + - (void)dealloc { if (self.mapNodeAttributes != nil) { [[MWMStorage sharedStorage] removeObserver:self]; diff --git a/iphone/Maps/Classes/CustomAlert/UnknownCurrentPositionAlert/UIAlertController+UnknownCurrentPosition.swift b/iphone/Maps/Classes/CustomAlert/UnknownCurrentPositionAlert/UIAlertController+UnknownCurrentPosition.swift new file mode 100644 index 000000000..257f0f316 --- /dev/null +++ b/iphone/Maps/Classes/CustomAlert/UnknownCurrentPositionAlert/UIAlertController+UnknownCurrentPosition.swift @@ -0,0 +1,7 @@ +extension UIAlertController { + static func unknownCurrentPosition() -> UIAlertController { + let alert = UIAlertController(title: L("unknown_current_position"), message: nil, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: L("ok"), style: .default, handler: nil)) + return alert + } +} diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h index 7ddbbd89f..677a73eed 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h @@ -4,9 +4,15 @@ @class MapViewController; @class BottomTabBarViewController; -@class TrackRecordingViewController; +@class TrackRecordingButtonViewController; @class SearchQuery; +typedef NS_ENUM(NSUInteger, TrackRecordingButtonState) { + TrackRecordingButtonStateHidden, + TrackRecordingButtonStateVisible, + TrackRecordingButtonStateClosed, +}; + @protocol MWMFeatureHolder; @interface MWMMapViewControlsManager : NSObject @@ -21,7 +27,7 @@ @property(nonatomic) MWMBottomMenuState menuRestoreState; @property(nonatomic) BOOL isDirectionViewHidden; @property(nonatomic) BottomTabBarViewController * tabBarController; -@property(nonatomic) TrackRecordingViewController * trackRecordingButton; +@property(nonatomic) TrackRecordingButtonViewController * trackRecordingButton; - (instancetype)init __attribute__((unavailable("init is not available"))); - (instancetype)initWithParentController:(MapViewController *)controller; @@ -35,6 +41,8 @@ - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator; +- (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state; + #pragma mark - MWMNavigationDashboardManager - (void)onRoutePrepare; diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm index dc2360f9e..728b3e1c9 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm @@ -28,15 +28,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; @interface MWMMapViewControlsManager () -@property(nonatomic) MWMSideButtons *sideButtons; -@property(nonatomic) MWMTrafficButtonViewController *trafficButton; -@property(nonatomic) UIButton *promoButton; -@property(nonatomic) UIViewController *menuController; +@property(nonatomic) MWMSideButtons * sideButtons; +@property(nonatomic) MWMTrafficButtonViewController * trafficButton; +@property(nonatomic) UIButton * promoButton; +@property(nonatomic) UIViewController * menuController; @property(nonatomic) id placePageManager; -@property(nonatomic) MWMNavigationDashboardManager *navigationManager; -@property(nonatomic) SearchOnMapManager *searchManager; +@property(nonatomic) MWMNavigationDashboardManager * navigationManager; +@property(nonatomic) SearchOnMapManager * searchManager; -@property(weak, nonatomic) MapViewController *ownerController; +@property(weak, nonatomic) MapViewController * ownerController; @property(nonatomic) BOOL disableStandbyOnRouteFollowing; @property(nonatomic) BOOL isAddingPlace; @@ -63,17 +63,10 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; self.menuState = MWMBottomMenuStateInactive; self.menuRestoreState = MWMBottomMenuStateInactive; self.isAddingPlace = NO; - [TrackRecordingManager.shared addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * trackInfo) { - [self setTrackRecordingButtonHidden:state == TrackRecordingStateInactive]; - }]; self.searchManager = controller.searchManager; return self; } -- (void)dealloc { - [TrackRecordingManager.shared removeObserver:self]; -} - - (UIStatusBarStyle)preferredStatusBarStyle { BOOL const isNavigationUnderStatusBar = self.navigationManager.state != MWMNavigationDashboardStateHidden && self.navigationManager.state != MWMNavigationDashboardStateNavigation; @@ -280,17 +273,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; self.trafficButton.hidden = self.hidden || _trafficButtonHidden; } -- (void)setTrackRecordingButtonHidden:(BOOL)trackRecordingButtonHidden { - if (trackRecordingButtonHidden && _trackRecordingButton) { - [self.trackRecordingButton closeWithCompletion:^{ - [MWMMapWidgetsHelper updateLayoutForAvailableArea]; - }]; - _trackRecordingButton = nil; +- (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state { + if (!_trackRecordingButton) { + _trackRecordingButton = [[TrackRecordingButtonViewController alloc] init]; } - else if (!trackRecordingButtonHidden && !_trackRecordingButton) { - _trackRecordingButton = [[TrackRecordingViewController alloc] init]; + [self.trackRecordingButton setState:state completion:^{ [MWMMapWidgetsHelper updateLayoutForAvailableArea]; - } + }]; + if (state == TrackRecordingButtonStateClosed) + _trackRecordingButton = nil; } - (void)setMenuState:(MWMBottomMenuState)menuState { diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift similarity index 73% rename from iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift rename to iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift index a4385108a..fd864d2b0 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift @@ -1,4 +1,4 @@ -final class TrackRecordingViewController: MWMViewController { +final class TrackRecordingButtonViewController: MWMViewController { private enum Constants { static let buttonDiameter = CGFloat(48) @@ -13,6 +13,7 @@ final class TrackRecordingViewController: MWMViewController { private var blinkingTimer: Timer? private var topConstraint = NSLayoutConstraint() private var trailingConstraint = NSLayoutConstraint() + private var state: TrackRecordingButtonState = .hidden private static var availableArea: CGRect = .zero private static var topConstraintValue: CGFloat { @@ -38,31 +39,29 @@ final class TrackRecordingViewController: MWMViewController { fatalError("init(coder:) has not been implemented") } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - UIView.transition(with: self.view, - duration: kDefaultAnimationDuration, - options: .transitionCrossDissolve, - animations: { - self.button.isHidden = false - }) + override func viewDidLoad() { + super.viewDidLoad() + // async is for smoother appearance + DispatchQueue.main.asyncAfter(deadline: .now() + kDefaultAnimationDuration) { + self.setState(self.state, completion: nil) + } } // MARK: - Public methods @objc - func close(completion: @escaping (() -> Void)) { - stopTimer() - UIView.transition(with: self.view, - duration: kDefaultAnimationDuration, - options: .transitionCrossDissolve, - animations: { - self.button.isHidden = true - }, completion: { _ in - self.removeFromParent() - self.view.removeFromSuperview() - completion() - }) + func setState(_ state: TrackRecordingButtonState, completion: (() -> Void)?) { + self.state = state + switch state { + case .visible: + setHidden(false, completion: nil) + case .hidden: + setHidden(true, completion: completion) + case .closed: + close(completion: completion) + @unknown default: + fatalError() + } } // MARK: - Private methods @@ -75,7 +74,7 @@ final class TrackRecordingViewController: MWMViewController { button.tintColor = Constants.color.darker button.translatesAutoresizingMaskIntoConstraints = false button.setImage(UIImage(resource: .icMenuBookmarkTrackRecording), for: .normal) - button.addTarget(self, action: #selector(onTrackRecordingButtonPressed), for: .touchUpInside) + button.addTarget(self, action: #selector(didTap), for: .touchUpInside) button.isHidden = true } @@ -97,7 +96,7 @@ final class TrackRecordingViewController: MWMViewController { } private func updateLayout() { - guard let superview = self.view.superview else { return } + guard let superview = view.superview else { return } superview.animateConstraints { self.topConstraint.constant = Self.topConstraintValue self.trailingConstraint.constant = Self.trailingConstraintValue @@ -123,23 +122,39 @@ final class TrackRecordingViewController: MWMViewController { blinkingTimer = nil } + private func setHidden(_ hidden: Bool, completion: (() -> Void)?) { + UIView.transition(with: self.view, + duration: kDefaultAnimationDuration, + options: .transitionCrossDissolve, + animations: { + self.button.isHidden = hidden + }) { _ in + completion?() + } + } + + private func close(completion: (() -> Void)?) { + stopTimer() + setHidden(true) { [weak self] in + guard let self else { return } + self.removeFromParent() + self.view.removeFromSuperview() + completion?() + } + } + static func updateAvailableArea(_ frame: CGRect) { availableArea = frame - guard let controller = MapViewController.shared()?.controlsManager.trackRecordingButton else { return } + guard let button = MapViewController.shared()?.controlsManager.trackRecordingButton else { return } DispatchQueue.main.async { - controller.updateLayout() + button.updateLayout() } } // MARK: - Actions @objc - private func onTrackRecordingButtonPressed(_ sender: Any) { - switch trackRecordingManager.recordingState { - case .inactive: - trackRecordingManager.processAction(.start) - case .active: - trackRecordingManager.processAction(.stop) - } + private func didTap(_ sender: Any) { + MapViewController.shared()?.showTrackRecordingPlacePage() } } diff --git a/iphone/Maps/Classes/MapViewController.h b/iphone/Maps/Classes/MapViewController.h index 465137125..a6f4d220a 100644 --- a/iphone/Maps/Classes/MapViewController.h +++ b/iphone/Maps/Classes/MapViewController.h @@ -37,6 +37,7 @@ - (void)openBookmarkEditor; - (void)openFullPlaceDescriptionWithHtml:(NSString *_Nonnull)htmlString; - (void)openDrivingOptions; +- (void)showTrackRecordingPlacePage; - (void)setPlacePageTopBound:(CGFloat)bound duration:(double)duration; diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 7620c2bce..606c22f0d 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -76,6 +76,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; @property(nonatomic, readwrite) MWMMapViewControlsManager *controlsManager; @property(nonatomic, readwrite) SearchOnMapManager *searchManager; +@property(nonatomic, readwrite) TrackRecordingManager *trackRecordingManager; @property(nonatomic) BOOL disableStandbyOnLocationStateMode; @@ -116,7 +117,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; return [MapsAppDelegate theApp].mapViewController; } -#pragma mark - Map Navigation +#pragma mark - PlacePage - (void)showOrUpdatePlacePage:(PlacePageData *)data { if (self.searchManager.isSearching) @@ -124,9 +125,10 @@ NSString *const kSettingsSegue = @"Map2Settings"; self.controlsManager.trafficButtonHidden = YES; if (self.placePageVC != nil) { - [PlacePageBuilder update:(PlacePageViewController *)self.placePageVC with:data]; + [PlacePageBuilder update:self.placePageVC with:data]; return; } + [self showPlacePageFor:data]; } @@ -204,6 +206,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; } - (void)hideRegularPlacePage { + [self stopObservingTrackRecordingUpdates]; [self.placePageVC closeAnimatedWithCompletion:^{ [self.placePageVC.view removeFromSuperview]; [self.placePageVC willMoveToParentViewController:nil]; @@ -247,6 +250,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; return; } PlacePageData * data = [[PlacePageData alloc] initWithLocalizationProvider:[[OpeinigHoursLocalization alloc] init]]; + [self stopObservingTrackRecordingUpdates]; [self showOrUpdatePlacePage:data]; } @@ -425,6 +429,9 @@ NSString *const kSettingsSegue = @"Map2Settings"; // After all users migrate to OAuth2 we can remove next code [self migrateOAuthCredentials]; + if (self.trackRecordingManager.isActive) + [self showTrackRecordingPlacePage]; + /// @todo: Uncomment update dialog when will be ready to handle big traffic bursts. /* if (!DeepLinkHandler.shared.isLaunchedByDeeplink) @@ -742,6 +749,12 @@ NSString *const kSettingsSegue = @"Map2Settings"; return _searchManager; } +- (TrackRecordingManager *)trackRecordingManager { + if (!_trackRecordingManager) + _trackRecordingManager = TrackRecordingManager.shared; + return _trackRecordingManager; +} + - (UIView * _Nullable)searchViewAvailableArea { return self.searchManager.viewController.availableAreaView; } @@ -858,6 +871,50 @@ NSString *const kSettingsSegue = @"Map2Settings"; } } +// MARK: - Track Recording Place Page + +- (void)showTrackRecordingPlacePage { + if ([self.trackRecordingManager contains:self]) { + [self dismissPlacePage]; + return; + } + PlacePageData * placePageData = [[PlacePageData alloc] initWithTrackInfo:self.trackRecordingManager.trackRecordingInfo + elevationInfo:self.trackRecordingManager.trackRecordingElevationProfileData]; + [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateHidden]; + [self showOrUpdatePlacePage:placePageData]; + [self startObservingTrackRecordingUpdatesForPlacePageData:placePageData]; +} + +- (void)startObservingTrackRecordingUpdatesForPlacePageData:(PlacePageData *)placePageData { + __weak __typeof(self) weakSelf = self; + [self.trackRecordingManager addObserver:self + recordingIsActiveDidChangeHandler:^(TrackRecordingState state, + TrackInfo * _Nonnull trackInfo, + ElevationProfileData * _Nonnull (^ _Nullable elevationData) ()) { + __strong __typeof(weakSelf) self = weakSelf; + + switch (state) { + case TrackRecordingStateInactive: + [self stopObservingTrackRecordingUpdates]; + [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateClosed]; + break; + case TrackRecordingStateActive: + if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) + return; + [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateHidden]; + [placePageData updateWithTrackInfo:trackInfo + elevationInfo:elevationData()]; + break; + } + }]; +} + +- (void)stopObservingTrackRecordingUpdates { + [self.trackRecordingManager removeObserver:self]; + if (self.trackRecordingManager.isActive) + [self.controlsManager setTrackRecordingButtonState:TrackRecordingButtonStateVisible]; +} + // MARK: - Handle macOS trackpad gestures - (void)handlePan:(UIPanGestureRecognizer *)recognizer API_AVAILABLE(ios(14.0)) { diff --git a/iphone/Maps/Core/Location/MWMLocationManager.h b/iphone/Maps/Core/Location/MWMLocationManager.h index e662c4792..2bb3b033f 100644 --- a/iphone/Maps/Core/Location/MWMLocationManager.h +++ b/iphone/Maps/Core/Location/MWMLocationManager.h @@ -3,8 +3,15 @@ NS_ASSUME_NONNULL_BEGIN +@protocol LocationService + ++ (BOOL)isLocationProhibited; ++ (void)checkLocationStatus; + +@end + NS_SWIFT_NAME(LocationManager) -@interface MWMLocationManager : NSObject +@interface MWMLocationManager : NSObject + (void)start; + (void)stop; @@ -14,10 +21,8 @@ NS_SWIFT_NAME(LocationManager) + (void)removeObserver:(id)observer NS_SWIFT_NAME(remove(observer:)); + (void)setMyPositionMode:(MWMMyPositionMode)mode; -+ (void)checkLocationStatus; + (nullable CLLocation *)lastLocation; -+ (BOOL)isLocationProhibited; + (nullable CLHeading *)lastHeading; + (void)applicationDidBecomeActive; diff --git a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift index 3afe39ae7..321c0f7a5 100644 --- a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift +++ b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift @@ -198,6 +198,7 @@ extension GlobalStyleSheet: IStyleSheet { case .trackRecordingWidgetButton: return .addFrom(Self.bottomTabBarButton) { s in s.cornerRadius = .custom(23) + s.coloring = .red } case .blackOpaqueBackground: return .add { s in diff --git a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift index 30ab1121c..797723004 100644 --- a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift +++ b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift @@ -4,31 +4,47 @@ enum TrackRecordingState: Int, Equatable { case active } -enum TrackRecordingAction: String, CaseIterable { +enum TrackRecordingAction { case start - case stop + case stopAndSave(name: String) } enum TrackRecordingError: Error { case locationIsProhibited + case trackIsEmpty + case systemError(Error) } -protocol TrackRecordingObserver: AnyObject { +enum TrackRecordingActionResult { + case success + case error(TrackRecordingError) +} + +@objc +protocol TrackRecordingObservable: AnyObject { + var recordingState: TrackRecordingState { get } + var trackRecordingInfo: TrackInfo { get } + var trackRecordingElevationProfileData: ElevationProfileData { get } + func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) func removeObserver(_ observer: AnyObject) + func contains(_ observer: AnyObject) -> Bool } -typealias TrackRecordingStateHandler = (TrackRecordingState, TrackInfo?) -> Void +/// A handler type for extracting elevation profile data on demand. +typealias ElevationProfileDataExtractionHandler = () -> ElevationProfileData + +/// A callback type that notifies observers about track recording state changes. +/// - Parameters: +/// - state: The current recording state. +/// - info: The current track recording info. +/// - elevationProfileExtractor: A closure to fetch elevation profile data lazily. +typealias TrackRecordingStateHandler = (TrackRecordingState, TrackInfo, ElevationProfileDataExtractionHandler?) -> Void @objcMembers final class TrackRecordingManager: NSObject { - typealias CompletionHandler = () -> Void - - private enum SavingOption { - case withoutSaving - case saveWithName(String? = nil) - } + typealias CompletionHandler = (TrackRecordingActionResult) -> Void fileprivate struct Observation { weak var observer: AnyObject? @@ -37,28 +53,40 @@ final class TrackRecordingManager: NSObject { static let shared: TrackRecordingManager = { let trackRecorder = FrameworkHelper.self + let locationManager = LocationManager.self var activityManager: TrackRecordingActivityManager? = nil #if canImport(ActivityKit) if #available(iOS 16.2, *) { activityManager = TrackRecordingLiveActivityManager.shared } #endif - return TrackRecordingManager(trackRecorder: trackRecorder, activityManager: activityManager) + return TrackRecordingManager(trackRecorder: trackRecorder, + locationService: locationManager, + activityManager: activityManager) }() private let trackRecorder: TrackRecorder.Type + private var locationService: LocationService.Type private var activityManager: TrackRecordingActivityManager? private var observations: [Observation] = [] - private var trackRecordingInfo: TrackInfo? + private(set) var trackRecordingInfo: TrackInfo = .empty() + + var trackRecordingElevationProfileData: ElevationProfileData { + FrameworkHelper.trackRecordingElevationInfo() + } var recordingState: TrackRecordingState { trackRecorder.isTrackRecordingEnabled() ? .active : .inactive } - private init(trackRecorder: TrackRecorder.Type, activityManager: TrackRecordingActivityManager?) { + init(trackRecorder: TrackRecorder.Type, + locationService: LocationService.Type, + activityManager: TrackRecordingActivityManager?) { self.trackRecorder = trackRecorder + self.locationService = locationService self.activityManager = activityManager super.init() + self.subscribeOnTheAppLifecycleEvents() } // MARK: - Public methods @@ -84,22 +112,42 @@ final class TrackRecordingManager: NSObject { } func processAction(_ action: TrackRecordingAction, completion: (CompletionHandler)? = nil) { - switch action { - case .start: - start(completion: completion) - case .stop: - stop(completion: completion) + do { + switch action { + case .start: + try startRecording() + case .stopAndSave(let name): + stopRecording() + try checkIsTrackNotEmpty() + saveTrackRecording(name: name) + } + completion?(.success) + } catch { + handleError(error, completion: completion) } } // MARK: - Private methods - private func checkIsLocationEnabled() throws { - if LocationManager.isLocationProhibited() { + private func subscribeOnTheAppLifecycleEvents() { + NotificationCenter.default.addObserver(self, + selector: #selector(notifyObservers), + name: UIApplication.didBecomeActiveNotification, + object: nil) + } + + private func checkIsLocationEnabled() throws(TrackRecordingError) { + if locationService.isLocationProhibited() { throw TrackRecordingError.locationIsProhibited } } + private func checkIsTrackNotEmpty() throws(TrackRecordingError) { + if trackRecorder.isTrackRecordingEmpty() { + throw TrackRecordingError.trackIsEmpty + } + } + // MARK: - Handle track recording process private func subscribeOnTrackRecordingProgressUpdates() { @@ -113,95 +161,68 @@ final class TrackRecordingManager: NSObject { private func unsubscribeFromTrackRecordingProgressUpdates() { trackRecorder.setTrackRecordingUpdateHandler(nil) - trackRecordingInfo = nil } // MARK: - Handle Start/Stop event and Errors - private func start(completion: (CompletionHandler)? = nil) { - do { + private func startRecording() throws(TrackRecordingError) { + switch recordingState { + case .inactive: try checkIsLocationEnabled() - switch recordingState { - case .inactive: - subscribeOnTrackRecordingProgressUpdates() - trackRecorder.startTrackRecording() - notifyObservers() - try? activityManager?.start(with: trackRecordingInfo ?? .empty()) - case .active: - break + subscribeOnTrackRecordingProgressUpdates() + trackRecorder.startTrackRecording() + notifyObservers() + do { + try activityManager?.start(with: trackRecordingInfo) + } catch { + LOG(.warning, "Failed to start activity manager") + handleError(.systemError(error)) } - completion?() - } catch { - handleError(error, completion: completion) + case .active: + break } } - private func stop(completion: (CompletionHandler)? = nil) { - guard !trackRecorder.isTrackRecordingEmpty() else { - Toast.show(withText: L("track_recording_toast_nothing_to_save")) - stopRecording(.withoutSaving, completion: completion) - return - } - Self.showOnFinishRecordingAlert(onSave: { [weak self] in - guard let self else { return } - // TODO: (KK) pass the user provided name from the track saving screen (when it will be implemented) - self.stopRecording(.saveWithName(), completion: completion) - }, - onStop: { [weak self] in - guard let self else { return } - self.stopRecording(.withoutSaving, completion: completion) - }, - onContinue: { - completion?() - }) - } - - private func stopRecording(_ savingOption: SavingOption, completion: (CompletionHandler)? = nil) { + private func stopRecording() { unsubscribeFromTrackRecordingProgressUpdates() trackRecorder.stopTrackRecording() + trackRecordingInfo = .empty() activityManager?.stop() notifyObservers() - - switch savingOption { - case .withoutSaving: - break - case .saveWithName(let name): - trackRecorder.saveTrackRecording(withName: name) - } - completion?() } - private func handleError(_ error: Error, completion: (CompletionHandler)? = nil) { - LOG(.error, error.localizedDescription) + private func saveTrackRecording(name: String) { + trackRecorder.saveTrackRecording(withName: name) + } + + private func handleError(_ error: TrackRecordingError, completion: (CompletionHandler)? = nil) { switch error { case TrackRecordingError.locationIsProhibited: // Show alert to enable location - LocationManager.checkLocationStatus() - default: + locationService.checkLocationStatus() + case TrackRecordingError.trackIsEmpty: + Toast.show(withText: L("track_recording_toast_nothing_to_save")) + case TrackRecordingError.systemError(let error): + LOG(.error, error.localizedDescription) break } - stopRecording(.withoutSaving, completion: completion) - } - - private static func showOnFinishRecordingAlert(onSave: @escaping CompletionHandler, - onStop: @escaping CompletionHandler, - onContinue: @escaping CompletionHandler) { - let alert = UIAlertController(title: L("track_recording_alert_title"), message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: L("continue_recording"), style: .default, handler: { _ in onContinue() })) - alert.addAction(UIAlertAction(title: L("stop_without_saving"), style: .default, handler: { _ in onStop() })) - alert.addAction(UIAlertAction(title: L("save"), style: .cancel, handler: { _ in onSave() })) - UIViewController.topViewController().present(alert, animated: true) + DispatchQueue.main.async { + completion?(.error(error)) + } } } // MARK: - TrackRecordingObserver -extension TrackRecordingManager: TrackRecordingObserver { +extension TrackRecordingManager: TrackRecordingObservable { @objc func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) { + guard !observations.contains(where: { $0.observer === observer }) else { return } let observation = Observation(observer: observer, recordingStateDidChangeHandler: recordingIsActiveDidChangeHandler) observations.append(observation) - recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo) + recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo) { + self.trackRecordingElevationProfileData + } } @objc @@ -209,8 +230,16 @@ extension TrackRecordingManager: TrackRecordingObserver { observations.removeAll { $0.observer === observer } } + @objc + func contains(_ observer: AnyObject) -> Bool { + observations.contains { $0.observer === observer } + } + + @objc private func notifyObservers() { - observations = observations.filter { $0.observer != nil } - observations.forEach { $0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo) } + observations.removeAll { $0.observer == nil } + observations.forEach { + $0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo, { self.trackRecordingElevationProfileData }) + } } } diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json new file mode 100644 index 000000000..4c2879a33 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_track_save.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png new file mode 100644 index 000000000..d0bee6031 Binary files /dev/null and b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png differ diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 6fd4564eb..b76de473f 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -470,7 +470,7 @@ ED2D74652D14357F00660FBF /* TrackRecordingLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D74302D14337500660FBF /* TrackRecordingLiveActivityAttributes.swift */; }; ED2D74662D1435A600660FBF /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D742D2D14337500660FBF /* LiveActivityManager.swift */; }; ED2E328E2D10500900807A08 /* TrackRecordingButtonArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED46DD922D06F804007CACD6 /* TrackRecordingButtonArea.swift */; }; - ED2E32912D10501700807A08 /* TrackRecordingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */; }; + ED2E32912D10501700807A08 /* TrackRecordingButtonViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */; }; ED3EAC202B03C88100220A4A /* BottomTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3EAC1F2B03C88100220A4A /* BottomTabBarButton.swift */; }; ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED43B8BC2C12063500D07BAA /* DocumentPicker.swift */; }; ED46DDCE2D098A0B007CACD6 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED46DDCD2D098A0B007CACD6 /* WidgetKit.framework */; }; @@ -510,6 +510,7 @@ ED914ABE2D351FF800973C45 /* UILabel+SetFontStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */; }; ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; }; ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED99667D2B94FBC20083CE55 /* ColorPicker.swift */; }; + ED9DDF882D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */; }; EDA1EAA42CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */; }; EDB71D8C2D8474A0004A6A7F /* CornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */; }; EDB71E002D8B0338004A6A7F /* ModalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */; }; @@ -541,6 +542,7 @@ EDFDFB4C2B722C9C0013A44C /* InfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB4B2B722C9C0013A44C /* InfoTableViewCell.swift */; }; EDFDFB522B726F1A0013A44C /* ButtonsStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB512B726F1A0013A44C /* ButtonsStackView.swift */; }; EDFDFB612B74E2500013A44C /* DonationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB602B74E2500013A44C /* DonationView.swift */; }; + EDFE1A4A2DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFE1A492DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift */; }; F607C1881C032A8800B53A87 /* resources-hdpi_light in Resources */ = {isa = PBXBuildFile; fileRef = F607C1831C032A8800B53A87 /* resources-hdpi_light */; }; F607C18A1C032A8800B53A87 /* resources-hdpi_dark in Resources */ = {isa = PBXBuildFile; fileRef = F607C1841C032A8800B53A87 /* resources-hdpi_dark */; }; F623DA6C1C9C2731006A3436 /* opening_hours_how_to_edit.html in Resources */ = {isa = PBXBuildFile; fileRef = F623DA6A1C9C2731006A3436 /* opening_hours_how_to_edit.html */; }; @@ -1444,7 +1446,7 @@ ED46DDCF2D098A0B007CACD6 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; ED48BBB817C2B1E2003E7E92 /* CircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleView.h; sourceTree = ""; }; ED48BBB917C2B1E2003E7E92 /* CircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleView.m; sourceTree = ""; }; - ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingViewController.swift; sourceTree = ""; }; + ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingButtonViewController.swift; sourceTree = ""; }; ED4DC7732CAEDECC0029B338 /* ProductButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductButton.swift; sourceTree = ""; }; ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = ""; }; ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewModel.swift; sourceTree = ""; }; @@ -1525,6 +1527,7 @@ ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SetFontStyle.swift"; sourceTree = ""; }; ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = ""; }; ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = ""; }; + ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacePageTrackRecordingLayout.swift; sourceTree = ""; }; EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = ""; }; EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadius.swift; sourceTree = ""; }; EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationAnimator.swift; sourceTree = ""; }; @@ -1556,6 +1559,7 @@ EDFDFB4B2B722C9C0013A44C /* InfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoTableViewCell.swift; sourceTree = ""; }; EDFDFB512B726F1A0013A44C /* ButtonsStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsStackView.swift; sourceTree = ""; }; EDFDFB602B74E2500013A44C /* DonationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationView.swift; sourceTree = ""; }; + EDFE1A492DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+UnknownCurrentPosition.swift"; sourceTree = ""; }; EE026F0511D6AC0D00645242 /* classificator.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = classificator.txt; path = ../../data/classificator.txt; sourceTree = SOURCE_ROOT; }; EED10A4411F78D120095FAD4 /* MapViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = MapViewController.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; F607C1831C032A8800B53A87 /* resources-hdpi_light */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-hdpi_light"; path = "../../data/resources-hdpi_light"; sourceTree = ""; }; @@ -2596,7 +2600,7 @@ 34BC72091B0DECAE0012A34B /* MapViewControls */ = { isa = PBXGroup; children = ( - ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */, + ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */, 340537621BBED98600D452C6 /* MWMMapViewControlsCommon.h */, 34BC72101B0DECAE0012A34B /* MWMMapViewControlsManager.h */, 34BC72111B0DECAE0012A34B /* MWMMapViewControlsManager.mm */, @@ -3060,6 +3064,7 @@ 99C6532123F2F506004322F3 /* IPlacePageLayout.swift */, 99F3EB0223F4178200C713F8 /* PlacePageCommonLayout.swift */, 993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */, + ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */, ); path = Layouts; sourceTree = ""; @@ -3446,6 +3451,14 @@ path = Views; sourceTree = ""; }; + EDFE1A462DF1986900FDEA38 /* UnknownCurrentPositionAlert */ = { + isa = PBXGroup; + children = ( + EDFE1A492DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift */, + ); + path = UnknownCurrentPositionAlert; + sourceTree = ""; + }; F607C18B1C047FCA00B53A87 /* Segue */ = { isa = PBXGroup; children = ( @@ -3496,6 +3509,7 @@ F64F195F1AB8125C006EAF7E /* CustomAlert */ = { isa = PBXGroup; children = ( + EDFE1A462DF1986900FDEA38 /* UnknownCurrentPositionAlert */, 447DB4B72BA7826D000DF4C2 /* ReauthAlert */, F62607FB207B78E300176C5A /* SpinnerAlert */, F6D67CDA2062B9810032FD38 /* CreateBookmarkCategory */, @@ -4489,8 +4503,9 @@ 99F3EB1223F418C900C713F8 /* PlacePageInteractor.swift in Sources */, 340708651F2905A500029ECC /* NavigationInfoArea.swift in Sources */, 993DF0CC23F6BD0600AC231A /* ElevationDetailsPresenter.swift in Sources */, + EDFE1A4A2DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift in Sources */, 34AB666B1FC5AA330078E451 /* TransportTransitCell.swift in Sources */, - ED2E32912D10501700807A08 /* TrackRecordingViewController.swift in Sources */, + ED2E32912D10501700807A08 /* TrackRecordingButtonViewController.swift in Sources */, 47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */, 993DF11123F6BDB100AC231A /* UILabelRenderer.swift in Sources */, 34AB66471FC5AA330078E451 /* RouteManagerTableView.swift in Sources */, @@ -4633,6 +4648,7 @@ 1DFA2F6A20D3B57400FB2C66 /* UIColor+PartnerColor.m in Sources */, 9989273B2449E60200260CE2 /* BottomMenuBuilder.swift in Sources */, 993DF10F23F6BDB100AC231A /* UIActivityIndicatorRenderer.swift in Sources */, + ED9DDF882D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift in Sources */, ED0B1FEF2CAA9A25006E31A4 /* UIView+Highlight.swift in Sources */, 99A614E423CDD1D900D8D8D0 /* UIButton+RuntimeAttributes.m in Sources */, 343E75981E5B1EE20041226A /* MWMCollectionViewController.m in Sources */, diff --git a/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift b/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift index 5272dd493..9f1ca5e4a 100644 --- a/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift +++ b/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift @@ -10,7 +10,7 @@ final class TrackRecordingButtonArea: AvailableArea { } override func notifyObserver() { - TrackRecordingViewController.updateAvailableArea(areaFrame) + TrackRecordingButtonViewController.updateAvailableArea(areaFrame) } } diff --git a/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift b/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift index 68f07db72..3ec98b553 100644 --- a/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift +++ b/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift @@ -66,12 +66,9 @@ extension BottomMenuInteractor: BottomMenuInteractorProtocol { } func shareLocation(cell: BottomMenuItemCell) { - let lastLocation = LocationManager.lastLocation() - guard let coordinates = lastLocation?.coordinate else { - let alert = UIAlertController(title: L("unknown_current_position"), message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: L("ok"), style: .default, handler: nil)) - viewController?.present(alert, animated: true, completion: nil) - return; + guard let coordinates = LocationManager.lastLocation()?.coordinate else { + viewController?.present(UIAlertController.unknownCurrentPosition(), animated: true, completion: nil) + return } guard let viewController = viewController else { return } let vc = ActivityViewController.share(forMyPosition: coordinates) @@ -79,8 +76,13 @@ extension BottomMenuInteractor: BottomMenuInteractorProtocol { } func toggleTrackRecording() { - trackRecorder.processAction(trackRecorder.recordingState == .active ? .stop : .start) { [weak self] in - self?.close() + switch trackRecorder.recordingState { + case .active: + break + case .inactive: + trackRecorder.processAction(.start) } + close() + MapViewController.shared()?.showTrackRecordingPlacePage() } } diff --git a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift index e70431130..2632ec188 100644 --- a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift @@ -62,19 +62,27 @@ final class ActionBarViewController: UIViewController { fatalError() } } + var buttons: [ActionBarButtonType] = [] - if isRoutePlanning { - buttons.append(.routeFrom) - } - let hasAnyPhones = !(placePageData.infoData?.phones ?? []).isEmpty - if hasAnyPhones, AppInfo.shared().canMakeCalls { - buttons.append(.call) - } - if !isRoutePlanning { - buttons.append(.routeFrom) + switch placePageData.objectType { + case .POI, .bookmark, .track: + if isRoutePlanning { + buttons.append(.routeFrom) + } + let hasAnyPhones = !(placePageData.infoData?.phones ?? []).isEmpty + if hasAnyPhones, AppInfo.shared().canMakeCalls { + buttons.append(.call) + } + if !isRoutePlanning { + buttons.append(.routeFrom) + } + case .trackRecording: + break + @unknown default: + fatalError() } - assert(buttons.count > 0) + guard !buttons.isEmpty else { return } visibleButtons.append(buttons[0]) if buttons.count > 1 { additionalButtons.append(contentsOf: buttons.suffix(from: 1)) @@ -83,21 +91,24 @@ final class ActionBarViewController: UIViewController { private func configButton2() { var buttons: [ActionBarButtonType] = [] - if canAddStop { - buttons.append(.routeAddStop) - } switch placePageData.objectType { case .POI, .bookmark: + if canAddStop { + buttons.append(.routeAddStop) + } buttons.append(.bookmark) case .track: + if canAddStop { + buttons.append(.routeAddStop) + } buttons.append(.track) case .trackRecording: - // TODO: implement for track recording - break + buttons.append(.saveTrackRecording) @unknown default: fatalError() } assert(buttons.count > 0) + visibleButtons.append(buttons[0]) if buttons.count > 1 { additionalButtons.append(contentsOf: buttons.suffix(from: 1)) @@ -105,7 +116,14 @@ final class ActionBarViewController: UIViewController { } private func configButton3() { - visibleButtons.append(.routeTo) + switch placePageData.objectType { + case .POI, .bookmark, .track: + visibleButtons.append(.routeTo) + case .trackRecording: + break + @unknown default: + fatalError() + } } private func configButton4() { diff --git a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift index 0211a6672..00abec743 100644 --- a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift +++ b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift @@ -85,6 +85,11 @@ extension ElevationProfilePresenter: ElevationProfilePresenterProtocol { view?.setChartData(ChartPresentationData(chartData, formatter: formatter)) view?.reloadDescription() + guard !profileData.isTrackRecording else { + view?.isChartViewInfoHidden = true + return + } + view?.setActivePoint(profileData.activePoint) view?.setMyPosition(profileData.myPosition) bookmarkManager.setElevationActivePointChanged(profileData.trackId) { [weak self] distance in diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift index 8278ae5e6..daf2b7f32 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift @@ -51,8 +51,6 @@ extension PlacePageHeaderPresenter: PlacePageHeaderPresenterProtocol { view?.isExpandViewHidden = true view?.isShadowViewHidden = false } - // TODO: (KK) Enable share button for the tracks to share the whole track gpx/kml - view?.isShareButtonHidden = false } func onClosePress() { diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift index 04c05e7a7..b77bfda01 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift @@ -2,7 +2,6 @@ protocol PlacePageHeaderViewProtocol: AnyObject { var presenter: PlacePageHeaderPresenterProtocol? { get set } var isExpandViewHidden: Bool { get set } var isShadowViewHidden: Bool { get set } - var isShareButtonHidden: Bool { get set } func setTitle(_ title: String?, secondaryTitle: String?) func showShareTrackMenu() @@ -78,15 +77,6 @@ extension PlacePageHeaderViewController: PlacePageHeaderViewProtocol { } } - var isShareButtonHidden: Bool { - get { - shareButton.isHidden - } - set { - shareButton.isHidden = newValue - } - } - func setTitle(_ title: String?, secondaryTitle: String?) { titleText = title secondaryText = secondaryTitle diff --git a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift index cac8307f6..8502a0d49 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift @@ -15,8 +15,7 @@ case .track: layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data) case .trackRecording: - // TODO: Implement PlacePageTrackRecordingLayout - fatalError("PlacePageTrackRecordingLayout is not implemented") + layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data) @unknown default: fatalError() } @@ -34,14 +33,14 @@ data: data, mapViewController: MapViewController.shared()!) let layout: IPlacePageLayout + let storyboard = viewController.storyboard! switch data.objectType { case .POI, .bookmark: - layout = PlacePageCommonLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data) + layout = PlacePageCommonLayout(interactor: interactor, storyboard: storyboard, data: data) case .track: - layout = PlacePageTrackLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data) + layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data) case .trackRecording: - // TODO: Implement PlacePageTrackRecordingLayout - fatalError("PlacePageTrackRecordingLayout is not implemented") + layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data) @unknown default: fatalError() } diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index 88e7fd0e4..c4accd9fa 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -246,9 +246,19 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate { fatalError("More button should've been handled in ActionBarViewContoller") case .track: guard placePageData.trackData != nil else { return } - // TODO: This is temporary solution. Remove the dialog and use the MWMPlacePageManagerHelper.removeTrack + // TODO: (KK) This is temporary solution. Remove the dialog and use the MWMPlacePageManagerHelper.removeTrack // directly here when the track recovery mechanism will be implemented. showTrackDeletionConfirmationDialog() + case .saveTrackRecording: + // TODO: (KK) pass name typed by user + TrackRecordingManager.shared.processAction(.stopAndSave(name: "")) { [weak self] result in + switch result { + case .success: + break + case .error: + self?.presenter?.closeAnimated() + } + } @unknown default: fatalError() } @@ -298,8 +308,8 @@ extension PlacePageInteractor: ElevationProfileViewControllerDelegate { } func updateMapPoint(_ point: CLLocationCoordinate2D, distance: Double) { - guard let trackId = placePageData.trackData?.trackId else { return } - BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackId) + guard let trackData = placePageData.trackData, trackData.elevationProfileData?.isTrackRecording == false else { return } + BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackData.trackId) } } @@ -323,7 +333,12 @@ extension PlacePageInteractor: PlacePageHeaderViewControllerDelegate { case .track: presenter?.showShareTrackMenu() default: - fatalError() + guard let coordinates = LocationManager.lastLocation()?.coordinate else { + viewController?.present(UIAlertController.unknownCurrentPosition(), animated: true, completion: nil) + return + } + let activity = ActivityViewController.share(forMyPosition: coordinates) + activity.present(inParentViewController: mapViewController, anchorView: sourceView) } } diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h index a82a84c69..d8c5f33ab 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h @@ -3,6 +3,7 @@ typedef NS_ENUM(NSInteger, MWMActionBarButtonType) { MWMActionBarButtonTypeBookingSearch, MWMActionBarButtonTypeBookmark, MWMActionBarButtonTypeTrack, + MWMActionBarButtonTypeSaveTrackRecording, MWMActionBarButtonTypeCall, MWMActionBarButtonTypeDownload, MWMActionBarButtonTypeMore, diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m index 0a7401baf..dd38a97b9 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m @@ -19,6 +19,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) { case MWMActionBarButtonTypeBookmark: case MWMActionBarButtonTypeTrack: return L(isSelected ? @"delete" : @"save"); + case MWMActionBarButtonTypeSaveTrackRecording: + return L(@"save"); case MWMActionBarButtonTypeRouteFrom: return L(@"p2p_from_here"); case MWMActionBarButtonTypeRouteTo: @@ -55,7 +57,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) { self.label.text = titleForButton(self.type, isSelected); self.extraBackground.hidden = YES; self.button.coloring = MWMButtonColoringBlack; - + [self.button.imageView setContentMode:UIViewContentModeScaleAspectFit]; + switch (self.type) { case MWMActionBarButtonTypeDownload: { if (self.mapDownloadProgress) @@ -108,6 +111,9 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) { [self.button setImage:[[UIImage imageNamed:@"ic_route_manager_trash"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal]; self.button.coloring = MWMButtonColoringRed; break; + case MWMActionBarButtonTypeSaveTrackRecording: + [self.button setImage:[UIImage imageNamed:@"ic_placepage_save_track_recording"] forState:UIControlStateNormal]; + break; case MWMActionBarButtonTypeRouteFrom: [self.button setImage:[UIImage imageNamed:@"ic_route_from"] forState:UIControlStateNormal]; break; diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift new file mode 100644 index 000000000..5dba5ce43 --- /dev/null +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift @@ -0,0 +1,92 @@ +final class PlacePageTrackRecordingLayout: IPlacePageLayout { + private var placePageData: PlacePageData + private var interactor: PlacePageInteractor + private let storyboard: UIStoryboard + weak var presenter: PlacePagePresenterProtocol? + + lazy var bodyViewControllers: [UIViewController] = { + return configureViewControllers() + }() + + var actionBar: ActionBarViewController? { + actionBarViewController + } + + var navigationBar: UIViewController? { + placePageNavigationViewController + } + + lazy var headerViewControllers: [UIViewController] = { + [headerViewController] + }() + + lazy var headerViewController: PlacePageHeaderViewController = { + return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .flexible) + }() + + lazy var placePageNavigationViewController: PlacePageHeaderViewController = { + return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .fixed) + }() + + lazy var editTrackViewController: PlacePageEditBookmarkOrTrackViewController = { + let vc = storyboard.instantiateViewController(ofType: PlacePageEditBookmarkOrTrackViewController.self) + vc.view.isHidden = true + vc.delegate = interactor + return vc + }() + + lazy var elevationProfileViewController: ElevationProfileViewController? = { + guard let trackData = placePageData.trackData else { + return nil + } + return ElevationProfileBuilder.build(trackInfo: trackData.trackInfo, + elevationProfileData: trackData.elevationProfileData, + delegate: interactor) + }() + + lazy var actionBarViewController: ActionBarViewController = { + let vc = storyboard.instantiateViewController(ofType: ActionBarViewController.self) + vc.placePageData = placePageData + vc.canAddStop = MWMRouter.canAddIntermediatePoint() + vc.isRoutePlanning = MWMNavigationDashboardManager.shared().state != .hidden + vc.delegate = interactor + return vc + }() + + init(interactor: PlacePageInteractor, storyboard: UIStoryboard, data: PlacePageData) { + self.interactor = interactor + self.storyboard = storyboard + self.placePageData = data + } + + private func configureViewControllers() -> [UIViewController] { + var viewControllers = [UIViewController]() + + if let elevationProfileViewController { + viewControllers.append(elevationProfileViewController) + } + + placePageData.onTrackRecordingProgressUpdate = { [weak self] in + self?.updateTrackRecordingRelatedSections() + } + + return viewControllers + } + + func calculateSteps(inScrollView scrollView: UIScrollView, compact: Bool) -> [PlacePageState] { + var steps: [PlacePageState] = [] + let scrollHeight = scrollView.height + steps.append(.closed(-scrollHeight)) + steps.append(.full(0)) + return steps + } +} + +private extension PlacePageTrackRecordingLayout { + func updateTrackRecordingRelatedSections() { + guard let elevationProfileViewController, let trackInfo = placePageData.trackData?.trackInfo else { return } + headerViewController.setTitle(placePageData.previewData.title, secondaryTitle: nil) + elevationProfileViewController.presenter?.update(trackInfo: trackInfo, profileData: placePageData.trackData?.elevationProfileData) + presenter?.layoutIfNeeded() + } +}