From b79724f2484064f544282a4aea5d35d51f951d8e Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 16:35:31 +0400 Subject: [PATCH] [ios] implement TrackRecording place page 1. add an new screen (layout) 2. add TR icon for the bottom tabbar 3. share current location from the TR PP 4. refactor TR manager to properly handle state updates and pass them to the LiveActivityManager and PlacePage 5. add init/update with TrackInfo/EleInfo methods to the PlacePageData and PlacePagePreviewData to update the PP state Signed-off-by: Kiryl Kaveryn --- iphone/CoreApi/CoreApi/Bookmarks/TrackInfo.mm | 2 +- .../CoreApi/Framework/MWMFrameworkHelper.h | 4 +- .../CoreApi/Framework/MWMFrameworkHelper.mm | 6 +- .../Common/PlacePagePreviewData.h | 3 + .../Common/PlacePagePreviewData.mm | 14 ++ .../PlacePageData/Common/PlacePageTrackData.h | 6 +- .../Common/PlacePageTrackData.mm | 9 + .../ElevationProfileData+Core.h | 1 + .../ElevationProfile/ElevationProfileData.h | 1 + .../ElevationProfile/ElevationProfileData.mm | 37 ++-- .../CoreApi/PlacePageData/PlacePageData.h | 4 + .../CoreApi/PlacePageData/PlacePageData.mm | 20 ++ ...ertController+UnknownCurrentPosition.swift | 7 + .../MWMMapViewControlsManager.h | 12 +- .../MWMMapViewControlsManager.mm | 37 ++-- ... TrackRecordingButtonViewController.swift} | 79 ++++--- iphone/Maps/Classes/MapViewController.h | 1 + iphone/Maps/Classes/MapViewController.mm | 61 +++++- .../Maps/Core/Location/MWMLocationManager.h | 11 +- iphone/Maps/Core/Theme/GlobalStyleSheet.swift | 1 + .../TrackRecorder/TrackRecordingManager.swift | 193 ++++++++++-------- .../Contents.json | 15 ++ .../ic_track_save.png | Bin 0 -> 4317 bytes iphone/Maps/Maps.xcodeproj/project.pbxproj | 24 ++- .../TrackRecordingButtonArea.swift | 2 +- .../Menu/BottomMenuInteractor.swift | 18 +- .../Components/ActionBarViewController.swift | 50 +++-- .../ElevationProfilePresenter.swift | 5 + .../PlacePageHeaderPresenter.swift | 2 - .../PlacePageHeaderViewController.swift | 10 - .../Maps/UI/PlacePage/PlacePageBuilder.swift | 11 +- .../UI/PlacePage/PlacePageInteractor.swift | 23 ++- .../ActionBar/MWMActionBarButton.h | 1 + .../ActionBar/MWMActionBarButton.m | 8 +- .../PlacePageTrackRecordingLayout.swift | 92 +++++++++ 35 files changed, 554 insertions(+), 216 deletions(-) create mode 100644 iphone/Maps/Classes/CustomAlert/UnknownCurrentPositionAlert/UIAlertController+UnknownCurrentPosition.swift rename iphone/Maps/Classes/CustomViews/MapViewControls/{TrackRecordingViewController.swift => TrackRecordingButtonViewController.swift} (73%) create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png create mode 100644 iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift 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 0000000000000000000000000000000000000000..d0bee6031e9efa62c6d8111eb1f6bedd96fdb328 GIT binary patch literal 4317 zcma)fXH*kR(>6s^5Tr`qFIoO2Q7#J8hOzs-k&_~)o zIe(Vk->)6@p%1KqcO8Qn7$h|Q38P{Db$2?*7;Iy#$AFj=*`_OJeD0XvVPI&?yg>6{ zVPN12GBLPg8_xI(n;t4W%r``+6%pWqG}QD2ovUTrv*l`KTIAzGT(M#ClFM>te8cw0 zz9EpsMvl@{?r6Z3hTIqpg`=~?e@lJk|xtGDX>uw(^$0k8eK2yr=VvSMfRb7~lC-Gs%(!zn6IW`uud^^&5@0O- zD0>&fKh*xdOsQ#YoaHrIiAhJFh}}me;o`fJpU9&89ZZN_=P92@ERlmxGJe}WGDnKW zI28XsM<8I~R(d(*?W3P>4|2X@#saFye4>n{*&jpQ9G!vo(_`JJcA7UmtB7qy9f3W0 z`Go}`1iS>%>5COt;AYZM0y+pSJ5oqdr>6P{u!td&sk_TYQocjc;kbP zcE02Vw)c`N%rl$iwfGl14Z$*f$5d;WDA=5DALZ}LxHgL}ukHVM``6Fq+cFvCWJ_f1 zJH?;B(K(jd*D+{Kn>T6HY}UwqpI z%cH6uEz!uxpvxY$BXzI zm>OSc^iys{H-~R0x=1F%!v3BVQ&pcq{JR#Zt~wT&x$t96SI$ifPxE?J2WR~A@HfGg zU#`Z1*R-@5f%DSA_tfxMS zU^b|X6?8$;D!!Eow?wylgo$2}GaT7^zngHP33H$3Xxe44mJKozpdtu?z1yVVbJaKGJfCcg_6UPJCIo9<2DdxnnZ-O2ttMZ#SyWQvwXJch z;GPerN3v1y<%wIeZb7R~&*Tvu3+o}4AG+9F<7s(wJ54J~2d74i(2;8V%{d?EI&%xp53YRQb@HdJ|q zC#^KF>DxyOpFf^yJBBHD&e&zl(8zU0vBQth52bV_!vD$^qhT3-*#CLA9S zaRZa1!~`W6;8@_2O_(?z56yS^ZPB&9%?)_EPBYFZ`9?O<#ew*xj#3{PLDJx z$gbr~P^&=M8DWZX3x8(<3+`%Yrqd{dw;t1F#q+L3N*Z@zkj2bZgWrBUGk=J@nM-k9eRzVoh}U<)BrY3$5RB-3G-qoia>pAVuh& zX>C<6qWc=z8M!wY4tqtc(uHDs5~!-6$C zaqt4RzHjE=SFga*5uY@gcDP45hd~?QKRkyOz&zx-Sr4rDVPOW!^`zsWyHIo66-W;J z>O?Ou>d*q##gGQxpjKgL;@f`>L&39XZbc}gv+k=ng4b%o+BNJBL8k-0CFid>Tl5*f@?HK1UU|}If-q1+A>ppi=-tn6(D``(3`f&Ej){D>BX* zM;-sw$M{VHha;`{lpfl?O0!H()8z#$wBRj%j28=E283rk@?qx%(oLYVBbkn#6x_h= zmQ-o+gHW6xX6>w{*2ge5@p;y|(*BJajRnvHi`BU!_ZemI^B8yq98FVR@sSS*cz&;tuz5Pm zW^_T^D1hMCQf{P*+Bj7?Wo%Z9kHX~ez3TkqL34j>Fb@1cA8I9=YMy z+SQ$Fs&9fm(6tISN|xuy+Hg6m-F>~ZSk9lo{2@2~$^LLZ_D!e=n0&7G45PSF%{;M_ zWle4WUg6zn6{gc%cOXfmVGa=ccxchjHnr2_s#Bi{^^&`PDkAaAv}~tX%!Vb1)rrDT z^$XFc{!n|MNFxY4|MAM8Z}}c!zN*R{a-I^nMrQ4 zv+1~#K1^%Jn^g&LwbXU@PUsGMfj)^@(y3{*po+X5w8>GUkJ8+9OtIAMLTycy7D8f{ z5TP+eh!2Fnb8^*o*OvKSy}`>T#k}lJsZsm!lzZQPdh@{xfP`T{bpJ%NU04TRZ37lk zqZx7?HfCOJ4Hc3<^S-CD+^LK>M9t08L(f{nvp8w$V=+Zj^K0|6LEF{yGRp^0)4M1O zRx@Li5!DXnBKW;hIQP|)Y?@JtNC-&NdppyLxw1Uprspdg6<*hgwV1hD(U3*ki@fJ6 zpDS4h+B$^O#-^`P1#t_`4ZQYW4Y}>*LpoB?3lIC6dI<~tk?Be%+tpd?(jwUG8DC&^ zJE0vzNF4{;g+-_!&9*J#`96*6vx4N;Q`lOnHJ63ItLiXJnU=pmRPZ1`GEkkk;J4e8 zw?5B`uaq#E^*?-dJg-tUP|Ifu;|kfu5Kbn%nmyQKz*p~mAJnF9ES(C#X}mPq;BC7I zIs9cO3iA+XBjGCB5P@;(5TcC2H+G;?oTz}_DA5++3Q9GR|D=J(%y@7b40VHp?iGfV zD0FQ!5qb7g@*K3}lEoD=eqV$thB^6@-!0ifmLyg<HJS*MC}R^#Ov*;0{4O*nAL~YwoDk~zYR$5O=XNz5T0TfrgsI8Jtf&Ek=Sy7Z3!Lr^Z z#K6Jh_3Nx)F_JlFsV@QBM#YvR0X^raAr^>R&QHq%Wc}rVkBri5af1FaH&^)AnMb2! z&~q^TB#+1jd$xt+A8pIWA(_}K(Is7(31nMzBW=ukx(N+2FMe)(?WPjrdRbD$9-qu= zMIYoBCXB^pd2FuO*omkg=i9W+-u@nCzmk|U=8n?Cv=Hmr?;7eXoO217MicPGeXWbn zv4!=ZN73?@tg;p5xZW<+t@u=3Lko?M!vEk`oq3EOWu986LwxY6>X!kbGOO$6Ao-gy zxUw5pSOz~<&x*I%SC6 zzfpEh@>AcB#4N@K`GS2gC%$BUH-B8?q-b!I3N$V@Q{J0rWN8-bi-F{)UI`%akzay?73j$7Tko(X46iSWS03jZkYw3dR4i4Gd+*P#Y!;6qBfirsr{et_-;aqEGmVK*G;gEoH_W94l{ zn0cQ`nImy?P`?crT(7`m!8GwYAmW^&x{TjUG91O&S~R#syM|8YzV=V`$Ove sN)9vU4sN=}B%`JO*AM^y3kw`LWe})oNuvB#=KZ(8#L&V3q30g|e{aZt7XSbN literal 0 HcmV?d00001 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() + } +}