[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 <kirylkaveryn@gmail.com>
This commit is contained in:
Kiryl Kaveryn
2025-01-09 16:35:31 +04:00
committed by Yannik Bloscheck
parent 5d0b8f1c04
commit b79724f248
35 changed files with 554 additions and 216 deletions

View File

@@ -4,7 +4,7 @@
@implementation TrackInfo @implementation TrackInfo
+ (TrackInfo *)emptyInfo { + (TrackInfo *)emptyInfo {
return [[TrackInfo alloc] init]; return [[TrackInfo alloc] initWithTrackStatistics:TrackStatistics()];
} }
@end @end

View File

@@ -21,12 +21,12 @@ NS_ASSUME_NONNULL_BEGIN
typedef void (^SearchInDownloaderCompletions)(NSArray<MWMMapSearchResult *> *results, BOOL finished); typedef void (^SearchInDownloaderCompletions)(NSArray<MWMMapSearchResult *> *results, BOOL finished);
typedef void (^TrackRecordingUpdatedHandler)(TrackInfo * _Nonnull trackInfo); typedef void (^TrackRecordingUpdatedHandler)(TrackInfo * _Nonnull trackInfo);
@protocol TrackRecorder <NSObject> @protocol TrackRecorder
+ (void)startTrackRecording; + (void)startTrackRecording;
+ (void)setTrackRecordingUpdateHandler:(TrackRecordingUpdatedHandler _Nullable)trackRecordingDidUpdate; + (void)setTrackRecordingUpdateHandler:(TrackRecordingUpdatedHandler _Nullable)trackRecordingDidUpdate;
+ (void)stopTrackRecording; + (void)stopTrackRecording;
+ (void)saveTrackRecordingWithName:(nullable NSString *)name; + (void)saveTrackRecordingWithName:(nonnull NSString *)name;
+ (BOOL)isTrackRecordingEnabled; + (BOOL)isTrackRecordingEnabled;
+ (BOOL)isTrackRecordingEmpty; + (BOOL)isTrackRecordingEmpty;
/// Returns current track recording elevation info. /// Returns current track recording elevation info.

View File

@@ -239,8 +239,8 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore(
GetFramework().StopTrackRecording(); GetFramework().StopTrackRecording();
} }
+ (void)saveTrackRecordingWithName:(nullable NSString *)name { + (void)saveTrackRecordingWithName:(nonnull NSString *)name {
GetFramework().SaveTrackRecordingWithName(name == nil ? "" : name.UTF8String); GetFramework().SaveTrackRecordingWithName(name.UTF8String);
} }
+ (BOOL)isTrackRecordingEnabled { + (BOOL)isTrackRecordingEnabled {
@@ -252,7 +252,7 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore(
} }
+ (ElevationProfileData * _Nonnull)trackRecordingElevationInfo { + (ElevationProfileData * _Nonnull)trackRecordingElevationInfo {
return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingCurrentElevationInfo()]; return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingElevationInfo()];
} }
// MARK: - ProductsManager // MARK: - ProductsManager

View File

@@ -1,6 +1,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@class PlacePageScheduleData; @class PlacePageScheduleData;
@class TrackInfo;
typedef NS_ENUM(NSInteger, PlacePageDataHotelType) { typedef NS_ENUM(NSInteger, PlacePageDataHotelType) {
PlacePageDataHotelTypeHotel, PlacePageDataHotelTypeHotel,
@@ -39,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) PlacePageDataSchedule schedule; @property(nonatomic, readonly) PlacePageDataSchedule schedule;
@property(nonatomic, readonly) BOOL isMyPosition; @property(nonatomic, readonly) BOOL isMyPosition;
- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@@ -1,4 +1,8 @@
#import "PlacePagePreviewData+Core.h" #import "PlacePagePreviewData+Core.h"
#import "DistanceFormatter.h"
#import "AltitudeFormatter.h"
#import "DurationFormatter.h"
#import "TrackInfo.h"
#include "3party/opening_hours/opening_hours.hpp" #include "3party/opening_hours/opening_hours.hpp"
@@ -46,6 +50,16 @@ static PlacePageDataSchedule convertOpeningHours(std::string_view rawOH)
@implementation PlacePagePreviewData @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 @end
@implementation PlacePagePreviewData (Core) @implementation PlacePagePreviewData (Core)

View File

@@ -10,8 +10,10 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) MWMTrackID trackId; @property(nonatomic, readonly) MWMTrackID trackId;
@property(nonatomic, readonly) MWMMarkGroupID groupId; @property(nonatomic, readonly) MWMMarkGroupID groupId;
@property(nonatomic, readonly, nonnull) TrackInfo * trackInfo; @property(nonatomic, readwrite, nonnull) TrackInfo * trackInfo;
@property(nonatomic, readonly, nullable) ElevationProfileData * elevationProfileData; @property(nonatomic, readwrite, nullable) ElevationProfileData * elevationProfileData;
- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo;
@end @end

View File

@@ -4,6 +4,15 @@
@implementation PlacePageTrackData @implementation PlacePageTrackData
- (nonnull instancetype)initWithTrackInfo:(TrackInfo *)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo {
self = [super init];
if (self) {
_trackInfo = trackInfo;
_elevationProfileData = elevationInfo;
}
return self;
}
@end @end
@implementation PlacePageTrackData (Core) @implementation PlacePageTrackData (Core)

View File

@@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
elevationInfo:(ElevationInfo const &)elevationInfo elevationInfo:(ElevationInfo const &)elevationInfo
activePoint:(double)activePoint activePoint:(double)activePoint
myPosition:(double)myPosition; myPosition:(double)myPosition;
- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo;
@end @end

View File

@@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, ElevationDifficulty) {
@interface ElevationProfileData : NSObject @interface ElevationProfileData : NSObject
@property(nonatomic, readonly) uint64_t trackId; @property(nonatomic, readonly) uint64_t trackId;
@property(nonatomic, readonly) BOOL isTrackRecording;
@property(nonatomic, readonly) ElevationDifficulty difficulty; @property(nonatomic, readonly) ElevationDifficulty difficulty;
@property(nonatomic, readonly) NSArray<ElevationHeightPoint *> * points; @property(nonatomic, readonly) NSArray<ElevationHeightPoint *> * points;
@property(nonatomic, readonly) double activePoint; @property(nonatomic, readonly) double activePoint;

View File

@@ -30,23 +30,36 @@ static ElevationDifficulty convertDifficulty(uint8_t difficulty) {
if (self) { if (self) {
_trackId = trackId; _trackId = trackId;
_difficulty = convertDifficulty(elevationInfo.GetDifficulty()); _difficulty = convertDifficulty(elevationInfo.GetDifficulty());
_points = [ElevationProfileData pointsFromElevationInfo: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];
}
_points = [pointsArray copy];
_activePoint = activePoint; _activePoint = activePoint;
_myPosition = myPosition; _myPosition = myPosition;
_isTrackRecording = false;
} }
return self; 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<ElevationHeightPoint *> *)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 @end

View File

@@ -10,6 +10,7 @@
@class PlacePageBookmarkData; @class PlacePageBookmarkData;
@class MWMMapNodeAttributes; @class MWMMapNodeAttributes;
@class TrackInfo; @class TrackInfo;
@class ElevationProfileData;
typedef NS_ENUM(NSInteger, PlacePageRoadType) { typedef NS_ENUM(NSInteger, PlacePageRoadType) {
PlacePageRoadTypeToll, PlacePageRoadTypeToll,
@@ -49,12 +50,15 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, readonly) CLLocationCoordinate2D locationCoordinate; @property(nonatomic, readonly) CLLocationCoordinate2D locationCoordinate;
@property(nonatomic, copy, nullable) MWMVoidBlock onBookmarkStatusUpdate; @property(nonatomic, copy, nullable) MWMVoidBlock onBookmarkStatusUpdate;
@property(nonatomic, copy, nullable) MWMVoidBlock onMapNodeStatusUpdate; @property(nonatomic, copy, nullable) MWMVoidBlock onMapNodeStatusUpdate;
@property(nonatomic, copy, nullable) MWMVoidBlock onTrackRecordingProgressUpdate;
@property(nonatomic, copy, nullable) void (^onMapNodeProgressUpdate)(uint64_t downloadedBytes, uint64_t totalBytes); @property(nonatomic, copy, nullable) void (^onMapNodeProgressUpdate)(uint64_t downloadedBytes, uint64_t totalBytes);
- (instancetype)initWithLocalizationProvider:(id<IOpeningHoursLocalization>)localization; - (instancetype)initWithLocalizationProvider:(id<IOpeningHoursLocalization>)localization;
- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo;
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
- (void)updateBookmarkStatus; - (void)updateBookmarkStatus;
- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo;
@end @end

View File

@@ -5,6 +5,7 @@
#import "PlacePageInfoData+Core.h" #import "PlacePageInfoData+Core.h"
#import "PlacePageBookmarkData+Core.h" #import "PlacePageBookmarkData+Core.h"
#import "PlacePageTrackData+Core.h" #import "PlacePageTrackData+Core.h"
#import "ElevationProfileData+Core.h"
#import "MWMMapNodeAttributes.h" #import "MWMMapNodeAttributes.h"
#include <CoreApi/CoreApi.h> #include <CoreApi/CoreApi.h>
@@ -84,6 +85,25 @@ static PlacePageRoadType convertRoadType(RoadWarningMarkType roadType) {
return self; 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 { - (void)dealloc {
if (self.mapNodeAttributes != nil) { if (self.mapNodeAttributes != nil) {
[[MWMStorage sharedStorage] removeObserver:self]; [[MWMStorage sharedStorage] removeObserver:self];

View File

@@ -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
}
}

View File

@@ -4,9 +4,15 @@
@class MapViewController; @class MapViewController;
@class BottomTabBarViewController; @class BottomTabBarViewController;
@class TrackRecordingViewController; @class TrackRecordingButtonViewController;
@class SearchQuery; @class SearchQuery;
typedef NS_ENUM(NSUInteger, TrackRecordingButtonState) {
TrackRecordingButtonStateHidden,
TrackRecordingButtonStateVisible,
TrackRecordingButtonStateClosed,
};
@protocol MWMFeatureHolder; @protocol MWMFeatureHolder;
@interface MWMMapViewControlsManager : NSObject @interface MWMMapViewControlsManager : NSObject
@@ -21,7 +27,7 @@
@property(nonatomic) MWMBottomMenuState menuRestoreState; @property(nonatomic) MWMBottomMenuState menuRestoreState;
@property(nonatomic) BOOL isDirectionViewHidden; @property(nonatomic) BOOL isDirectionViewHidden;
@property(nonatomic) BottomTabBarViewController * tabBarController; @property(nonatomic) BottomTabBarViewController * tabBarController;
@property(nonatomic) TrackRecordingViewController * trackRecordingButton; @property(nonatomic) TrackRecordingButtonViewController * trackRecordingButton;
- (instancetype)init __attribute__((unavailable("init is not available"))); - (instancetype)init __attribute__((unavailable("init is not available")));
- (instancetype)initWithParentController:(MapViewController *)controller; - (instancetype)initWithParentController:(MapViewController *)controller;
@@ -35,6 +41,8 @@
- (void)viewWillTransitionToSize:(CGSize)size - (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator; withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
- (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state;
#pragma mark - MWMNavigationDashboardManager #pragma mark - MWMNavigationDashboardManager
- (void)onRoutePrepare; - (void)onRoutePrepare;

View File

@@ -28,15 +28,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
@interface MWMMapViewControlsManager () <BottomMenuDelegate> @interface MWMMapViewControlsManager () <BottomMenuDelegate>
@property(nonatomic) MWMSideButtons *sideButtons; @property(nonatomic) MWMSideButtons * sideButtons;
@property(nonatomic) MWMTrafficButtonViewController *trafficButton; @property(nonatomic) MWMTrafficButtonViewController * trafficButton;
@property(nonatomic) UIButton *promoButton; @property(nonatomic) UIButton * promoButton;
@property(nonatomic) UIViewController *menuController; @property(nonatomic) UIViewController * menuController;
@property(nonatomic) id<MWMPlacePageProtocol> placePageManager; @property(nonatomic) id<MWMPlacePageProtocol> placePageManager;
@property(nonatomic) MWMNavigationDashboardManager *navigationManager; @property(nonatomic) MWMNavigationDashboardManager * navigationManager;
@property(nonatomic) SearchOnMapManager *searchManager; @property(nonatomic) SearchOnMapManager * searchManager;
@property(weak, nonatomic) MapViewController *ownerController; @property(weak, nonatomic) MapViewController * ownerController;
@property(nonatomic) BOOL disableStandbyOnRouteFollowing; @property(nonatomic) BOOL disableStandbyOnRouteFollowing;
@property(nonatomic) BOOL isAddingPlace; @property(nonatomic) BOOL isAddingPlace;
@@ -63,17 +63,10 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
self.menuState = MWMBottomMenuStateInactive; self.menuState = MWMBottomMenuStateInactive;
self.menuRestoreState = MWMBottomMenuStateInactive; self.menuRestoreState = MWMBottomMenuStateInactive;
self.isAddingPlace = NO; self.isAddingPlace = NO;
[TrackRecordingManager.shared addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * trackInfo) {
[self setTrackRecordingButtonHidden:state == TrackRecordingStateInactive];
}];
self.searchManager = controller.searchManager; self.searchManager = controller.searchManager;
return self; return self;
} }
- (void)dealloc {
[TrackRecordingManager.shared removeObserver:self];
}
- (UIStatusBarStyle)preferredStatusBarStyle { - (UIStatusBarStyle)preferredStatusBarStyle {
BOOL const isNavigationUnderStatusBar = self.navigationManager.state != MWMNavigationDashboardStateHidden && BOOL const isNavigationUnderStatusBar = self.navigationManager.state != MWMNavigationDashboardStateHidden &&
self.navigationManager.state != MWMNavigationDashboardStateNavigation; self.navigationManager.state != MWMNavigationDashboardStateNavigation;
@@ -280,17 +273,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
self.trafficButton.hidden = self.hidden || _trafficButtonHidden; self.trafficButton.hidden = self.hidden || _trafficButtonHidden;
} }
- (void)setTrackRecordingButtonHidden:(BOOL)trackRecordingButtonHidden { - (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state {
if (trackRecordingButtonHidden && _trackRecordingButton) { if (!_trackRecordingButton) {
[self.trackRecordingButton closeWithCompletion:^{ _trackRecordingButton = [[TrackRecordingButtonViewController alloc] init];
[MWMMapWidgetsHelper updateLayoutForAvailableArea];
}];
_trackRecordingButton = nil;
} }
else if (!trackRecordingButtonHidden && !_trackRecordingButton) { [self.trackRecordingButton setState:state completion:^{
_trackRecordingButton = [[TrackRecordingViewController alloc] init];
[MWMMapWidgetsHelper updateLayoutForAvailableArea]; [MWMMapWidgetsHelper updateLayoutForAvailableArea];
} }];
if (state == TrackRecordingButtonStateClosed)
_trackRecordingButton = nil;
} }
- (void)setMenuState:(MWMBottomMenuState)menuState { - (void)setMenuState:(MWMBottomMenuState)menuState {

View File

@@ -1,4 +1,4 @@
final class TrackRecordingViewController: MWMViewController { final class TrackRecordingButtonViewController: MWMViewController {
private enum Constants { private enum Constants {
static let buttonDiameter = CGFloat(48) static let buttonDiameter = CGFloat(48)
@@ -13,6 +13,7 @@ final class TrackRecordingViewController: MWMViewController {
private var blinkingTimer: Timer? private var blinkingTimer: Timer?
private var topConstraint = NSLayoutConstraint() private var topConstraint = NSLayoutConstraint()
private var trailingConstraint = NSLayoutConstraint() private var trailingConstraint = NSLayoutConstraint()
private var state: TrackRecordingButtonState = .hidden
private static var availableArea: CGRect = .zero private static var availableArea: CGRect = .zero
private static var topConstraintValue: CGFloat { private static var topConstraintValue: CGFloat {
@@ -38,31 +39,29 @@ final class TrackRecordingViewController: MWMViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func viewDidAppear(_ animated: Bool) { override func viewDidLoad() {
super.viewDidAppear(animated) super.viewDidLoad()
UIView.transition(with: self.view, // async is for smoother appearance
duration: kDefaultAnimationDuration, DispatchQueue.main.asyncAfter(deadline: .now() + kDefaultAnimationDuration) {
options: .transitionCrossDissolve, self.setState(self.state, completion: nil)
animations: { }
self.button.isHidden = false
})
} }
// MARK: - Public methods // MARK: - Public methods
@objc @objc
func close(completion: @escaping (() -> Void)) { func setState(_ state: TrackRecordingButtonState, completion: (() -> Void)?) {
stopTimer() self.state = state
UIView.transition(with: self.view, switch state {
duration: kDefaultAnimationDuration, case .visible:
options: .transitionCrossDissolve, setHidden(false, completion: nil)
animations: { case .hidden:
self.button.isHidden = true setHidden(true, completion: completion)
}, completion: { _ in case .closed:
self.removeFromParent() close(completion: completion)
self.view.removeFromSuperview() @unknown default:
completion() fatalError()
}) }
} }
// MARK: - Private methods // MARK: - Private methods
@@ -75,7 +74,7 @@ final class TrackRecordingViewController: MWMViewController {
button.tintColor = Constants.color.darker button.tintColor = Constants.color.darker
button.translatesAutoresizingMaskIntoConstraints = false button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(resource: .icMenuBookmarkTrackRecording), for: .normal) 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 button.isHidden = true
} }
@@ -97,7 +96,7 @@ final class TrackRecordingViewController: MWMViewController {
} }
private func updateLayout() { private func updateLayout() {
guard let superview = self.view.superview else { return } guard let superview = view.superview else { return }
superview.animateConstraints { superview.animateConstraints {
self.topConstraint.constant = Self.topConstraintValue self.topConstraint.constant = Self.topConstraintValue
self.trailingConstraint.constant = Self.trailingConstraintValue self.trailingConstraint.constant = Self.trailingConstraintValue
@@ -123,23 +122,39 @@ final class TrackRecordingViewController: MWMViewController {
blinkingTimer = nil 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) { static func updateAvailableArea(_ frame: CGRect) {
availableArea = frame availableArea = frame
guard let controller = MapViewController.shared()?.controlsManager.trackRecordingButton else { return } guard let button = MapViewController.shared()?.controlsManager.trackRecordingButton else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
controller.updateLayout() button.updateLayout()
} }
} }
// MARK: - Actions // MARK: - Actions
@objc @objc
private func onTrackRecordingButtonPressed(_ sender: Any) { private func didTap(_ sender: Any) {
switch trackRecordingManager.recordingState { MapViewController.shared()?.showTrackRecordingPlacePage()
case .inactive:
trackRecordingManager.processAction(.start)
case .active:
trackRecordingManager.processAction(.stop)
}
} }
} }

View File

@@ -37,6 +37,7 @@
- (void)openBookmarkEditor; - (void)openBookmarkEditor;
- (void)openFullPlaceDescriptionWithHtml:(NSString *_Nonnull)htmlString; - (void)openFullPlaceDescriptionWithHtml:(NSString *_Nonnull)htmlString;
- (void)openDrivingOptions; - (void)openDrivingOptions;
- (void)showTrackRecordingPlacePage;
- (void)setPlacePageTopBound:(CGFloat)bound duration:(double)duration; - (void)setPlacePageTopBound:(CGFloat)bound duration:(double)duration;

View File

@@ -76,6 +76,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
@property(nonatomic, readwrite) MWMMapViewControlsManager *controlsManager; @property(nonatomic, readwrite) MWMMapViewControlsManager *controlsManager;
@property(nonatomic, readwrite) SearchOnMapManager *searchManager; @property(nonatomic, readwrite) SearchOnMapManager *searchManager;
@property(nonatomic, readwrite) TrackRecordingManager *trackRecordingManager;
@property(nonatomic) BOOL disableStandbyOnLocationStateMode; @property(nonatomic) BOOL disableStandbyOnLocationStateMode;
@@ -116,7 +117,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
return [MapsAppDelegate theApp].mapViewController; return [MapsAppDelegate theApp].mapViewController;
} }
#pragma mark - Map Navigation #pragma mark - PlacePage
- (void)showOrUpdatePlacePage:(PlacePageData *)data { - (void)showOrUpdatePlacePage:(PlacePageData *)data {
if (self.searchManager.isSearching) if (self.searchManager.isSearching)
@@ -124,9 +125,10 @@ NSString *const kSettingsSegue = @"Map2Settings";
self.controlsManager.trafficButtonHidden = YES; self.controlsManager.trafficButtonHidden = YES;
if (self.placePageVC != nil) { if (self.placePageVC != nil) {
[PlacePageBuilder update:(PlacePageViewController *)self.placePageVC with:data]; [PlacePageBuilder update:self.placePageVC with:data];
return; return;
} }
[self showPlacePageFor:data]; [self showPlacePageFor:data];
} }
@@ -204,6 +206,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
} }
- (void)hideRegularPlacePage { - (void)hideRegularPlacePage {
[self stopObservingTrackRecordingUpdates];
[self.placePageVC closeAnimatedWithCompletion:^{ [self.placePageVC closeAnimatedWithCompletion:^{
[self.placePageVC.view removeFromSuperview]; [self.placePageVC.view removeFromSuperview];
[self.placePageVC willMoveToParentViewController:nil]; [self.placePageVC willMoveToParentViewController:nil];
@@ -247,6 +250,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
return; return;
} }
PlacePageData * data = [[PlacePageData alloc] initWithLocalizationProvider:[[OpeinigHoursLocalization alloc] init]]; PlacePageData * data = [[PlacePageData alloc] initWithLocalizationProvider:[[OpeinigHoursLocalization alloc] init]];
[self stopObservingTrackRecordingUpdates];
[self showOrUpdatePlacePage:data]; [self showOrUpdatePlacePage:data];
} }
@@ -425,6 +429,9 @@ NSString *const kSettingsSegue = @"Map2Settings";
// After all users migrate to OAuth2 we can remove next code // After all users migrate to OAuth2 we can remove next code
[self migrateOAuthCredentials]; [self migrateOAuthCredentials];
if (self.trackRecordingManager.isActive)
[self showTrackRecordingPlacePage];
/// @todo: Uncomment update dialog when will be ready to handle big traffic bursts. /// @todo: Uncomment update dialog when will be ready to handle big traffic bursts.
/* /*
if (!DeepLinkHandler.shared.isLaunchedByDeeplink) if (!DeepLinkHandler.shared.isLaunchedByDeeplink)
@@ -742,6 +749,12 @@ NSString *const kSettingsSegue = @"Map2Settings";
return _searchManager; return _searchManager;
} }
- (TrackRecordingManager *)trackRecordingManager {
if (!_trackRecordingManager)
_trackRecordingManager = TrackRecordingManager.shared;
return _trackRecordingManager;
}
- (UIView * _Nullable)searchViewAvailableArea { - (UIView * _Nullable)searchViewAvailableArea {
return self.searchManager.viewController.availableAreaView; 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 // MARK: - Handle macOS trackpad gestures
- (void)handlePan:(UIPanGestureRecognizer *)recognizer API_AVAILABLE(ios(14.0)) { - (void)handlePan:(UIPanGestureRecognizer *)recognizer API_AVAILABLE(ios(14.0)) {

View File

@@ -3,8 +3,15 @@
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
@protocol LocationService
+ (BOOL)isLocationProhibited;
+ (void)checkLocationStatus;
@end
NS_SWIFT_NAME(LocationManager) NS_SWIFT_NAME(LocationManager)
@interface MWMLocationManager : NSObject @interface MWMLocationManager : NSObject<LocationService>
+ (void)start; + (void)start;
+ (void)stop; + (void)stop;
@@ -14,10 +21,8 @@ NS_SWIFT_NAME(LocationManager)
+ (void)removeObserver:(id<MWMLocationObserver>)observer NS_SWIFT_NAME(remove(observer:)); + (void)removeObserver:(id<MWMLocationObserver>)observer NS_SWIFT_NAME(remove(observer:));
+ (void)setMyPositionMode:(MWMMyPositionMode)mode; + (void)setMyPositionMode:(MWMMyPositionMode)mode;
+ (void)checkLocationStatus;
+ (nullable CLLocation *)lastLocation; + (nullable CLLocation *)lastLocation;
+ (BOOL)isLocationProhibited;
+ (nullable CLHeading *)lastHeading; + (nullable CLHeading *)lastHeading;
+ (void)applicationDidBecomeActive; + (void)applicationDidBecomeActive;

View File

@@ -198,6 +198,7 @@ extension GlobalStyleSheet: IStyleSheet {
case .trackRecordingWidgetButton: case .trackRecordingWidgetButton:
return .addFrom(Self.bottomTabBarButton) { s in return .addFrom(Self.bottomTabBarButton) { s in
s.cornerRadius = .custom(23) s.cornerRadius = .custom(23)
s.coloring = .red
} }
case .blackOpaqueBackground: case .blackOpaqueBackground:
return .add { s in return .add { s in

View File

@@ -4,31 +4,47 @@ enum TrackRecordingState: Int, Equatable {
case active case active
} }
enum TrackRecordingAction: String, CaseIterable { enum TrackRecordingAction {
case start case start
case stop case stopAndSave(name: String)
} }
enum TrackRecordingError: Error { enum TrackRecordingError: Error {
case locationIsProhibited 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 addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler)
func removeObserver(_ observer: AnyObject) 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 @objcMembers
final class TrackRecordingManager: NSObject { final class TrackRecordingManager: NSObject {
typealias CompletionHandler = () -> Void typealias CompletionHandler = (TrackRecordingActionResult) -> Void
private enum SavingOption {
case withoutSaving
case saveWithName(String? = nil)
}
fileprivate struct Observation { fileprivate struct Observation {
weak var observer: AnyObject? weak var observer: AnyObject?
@@ -37,28 +53,40 @@ final class TrackRecordingManager: NSObject {
static let shared: TrackRecordingManager = { static let shared: TrackRecordingManager = {
let trackRecorder = FrameworkHelper.self let trackRecorder = FrameworkHelper.self
let locationManager = LocationManager.self
var activityManager: TrackRecordingActivityManager? = nil var activityManager: TrackRecordingActivityManager? = nil
#if canImport(ActivityKit) #if canImport(ActivityKit)
if #available(iOS 16.2, *) { if #available(iOS 16.2, *) {
activityManager = TrackRecordingLiveActivityManager.shared activityManager = TrackRecordingLiveActivityManager.shared
} }
#endif #endif
return TrackRecordingManager(trackRecorder: trackRecorder, activityManager: activityManager) return TrackRecordingManager(trackRecorder: trackRecorder,
locationService: locationManager,
activityManager: activityManager)
}() }()
private let trackRecorder: TrackRecorder.Type private let trackRecorder: TrackRecorder.Type
private var locationService: LocationService.Type
private var activityManager: TrackRecordingActivityManager? private var activityManager: TrackRecordingActivityManager?
private var observations: [Observation] = [] private var observations: [Observation] = []
private var trackRecordingInfo: TrackInfo? private(set) var trackRecordingInfo: TrackInfo = .empty()
var trackRecordingElevationProfileData: ElevationProfileData {
FrameworkHelper.trackRecordingElevationInfo()
}
var recordingState: TrackRecordingState { var recordingState: TrackRecordingState {
trackRecorder.isTrackRecordingEnabled() ? .active : .inactive trackRecorder.isTrackRecordingEnabled() ? .active : .inactive
} }
private init(trackRecorder: TrackRecorder.Type, activityManager: TrackRecordingActivityManager?) { init(trackRecorder: TrackRecorder.Type,
locationService: LocationService.Type,
activityManager: TrackRecordingActivityManager?) {
self.trackRecorder = trackRecorder self.trackRecorder = trackRecorder
self.locationService = locationService
self.activityManager = activityManager self.activityManager = activityManager
super.init() super.init()
self.subscribeOnTheAppLifecycleEvents()
} }
// MARK: - Public methods // MARK: - Public methods
@@ -84,22 +112,42 @@ final class TrackRecordingManager: NSObject {
} }
func processAction(_ action: TrackRecordingAction, completion: (CompletionHandler)? = nil) { func processAction(_ action: TrackRecordingAction, completion: (CompletionHandler)? = nil) {
switch action { do {
case .start: switch action {
start(completion: completion) case .start:
case .stop: try startRecording()
stop(completion: completion) case .stopAndSave(let name):
stopRecording()
try checkIsTrackNotEmpty()
saveTrackRecording(name: name)
}
completion?(.success)
} catch {
handleError(error, completion: completion)
} }
} }
// MARK: - Private methods // MARK: - Private methods
private func checkIsLocationEnabled() throws { private func subscribeOnTheAppLifecycleEvents() {
if LocationManager.isLocationProhibited() { NotificationCenter.default.addObserver(self,
selector: #selector(notifyObservers),
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
private func checkIsLocationEnabled() throws(TrackRecordingError) {
if locationService.isLocationProhibited() {
throw TrackRecordingError.locationIsProhibited throw TrackRecordingError.locationIsProhibited
} }
} }
private func checkIsTrackNotEmpty() throws(TrackRecordingError) {
if trackRecorder.isTrackRecordingEmpty() {
throw TrackRecordingError.trackIsEmpty
}
}
// MARK: - Handle track recording process // MARK: - Handle track recording process
private func subscribeOnTrackRecordingProgressUpdates() { private func subscribeOnTrackRecordingProgressUpdates() {
@@ -113,95 +161,68 @@ final class TrackRecordingManager: NSObject {
private func unsubscribeFromTrackRecordingProgressUpdates() { private func unsubscribeFromTrackRecordingProgressUpdates() {
trackRecorder.setTrackRecordingUpdateHandler(nil) trackRecorder.setTrackRecordingUpdateHandler(nil)
trackRecordingInfo = nil
} }
// MARK: - Handle Start/Stop event and Errors // MARK: - Handle Start/Stop event and Errors
private func start(completion: (CompletionHandler)? = nil) { private func startRecording() throws(TrackRecordingError) {
do { switch recordingState {
case .inactive:
try checkIsLocationEnabled() try checkIsLocationEnabled()
switch recordingState { subscribeOnTrackRecordingProgressUpdates()
case .inactive: trackRecorder.startTrackRecording()
subscribeOnTrackRecordingProgressUpdates() notifyObservers()
trackRecorder.startTrackRecording() do {
notifyObservers() try activityManager?.start(with: trackRecordingInfo)
try? activityManager?.start(with: trackRecordingInfo ?? .empty()) } catch {
case .active: LOG(.warning, "Failed to start activity manager")
break handleError(.systemError(error))
} }
completion?() case .active:
} catch { break
handleError(error, completion: completion)
} }
} }
private func stop(completion: (CompletionHandler)? = nil) { private func stopRecording() {
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) {
unsubscribeFromTrackRecordingProgressUpdates() unsubscribeFromTrackRecordingProgressUpdates()
trackRecorder.stopTrackRecording() trackRecorder.stopTrackRecording()
trackRecordingInfo = .empty()
activityManager?.stop() activityManager?.stop()
notifyObservers() notifyObservers()
switch savingOption {
case .withoutSaving:
break
case .saveWithName(let name):
trackRecorder.saveTrackRecording(withName: name)
}
completion?()
} }
private func handleError(_ error: Error, completion: (CompletionHandler)? = nil) { private func saveTrackRecording(name: String) {
LOG(.error, error.localizedDescription) trackRecorder.saveTrackRecording(withName: name)
}
private func handleError(_ error: TrackRecordingError, completion: (CompletionHandler)? = nil) {
switch error { switch error {
case TrackRecordingError.locationIsProhibited: case TrackRecordingError.locationIsProhibited:
// Show alert to enable location // Show alert to enable location
LocationManager.checkLocationStatus() locationService.checkLocationStatus()
default: case TrackRecordingError.trackIsEmpty:
Toast.show(withText: L("track_recording_toast_nothing_to_save"))
case TrackRecordingError.systemError(let error):
LOG(.error, error.localizedDescription)
break break
} }
stopRecording(.withoutSaving, completion: completion) DispatchQueue.main.async {
} completion?(.error(error))
}
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)
} }
} }
// MARK: - TrackRecordingObserver // MARK: - TrackRecordingObserver
extension TrackRecordingManager: TrackRecordingObserver { extension TrackRecordingManager: TrackRecordingObservable {
@objc @objc
func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) { func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) {
guard !observations.contains(where: { $0.observer === observer }) else { return }
let observation = Observation(observer: observer, recordingStateDidChangeHandler: recordingIsActiveDidChangeHandler) let observation = Observation(observer: observer, recordingStateDidChangeHandler: recordingIsActiveDidChangeHandler)
observations.append(observation) observations.append(observation)
recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo) recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo) {
self.trackRecordingElevationProfileData
}
} }
@objc @objc
@@ -209,8 +230,16 @@ extension TrackRecordingManager: TrackRecordingObserver {
observations.removeAll { $0.observer === observer } observations.removeAll { $0.observer === observer }
} }
@objc
func contains(_ observer: AnyObject) -> Bool {
observations.contains { $0.observer === observer }
}
@objc
private func notifyObservers() { private func notifyObservers() {
observations = observations.filter { $0.observer != nil } observations.removeAll { $0.observer == nil }
observations.forEach { $0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo) } observations.forEach {
$0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo, { self.trackRecordingElevationProfileData })
}
} }
} }

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "ic_track_save.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -470,7 +470,7 @@
ED2D74652D14357F00660FBF /* TrackRecordingLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D74302D14337500660FBF /* TrackRecordingLiveActivityAttributes.swift */; }; ED2D74652D14357F00660FBF /* TrackRecordingLiveActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D74302D14337500660FBF /* TrackRecordingLiveActivityAttributes.swift */; };
ED2D74662D1435A600660FBF /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D742D2D14337500660FBF /* LiveActivityManager.swift */; }; ED2D74662D1435A600660FBF /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2D742D2D14337500660FBF /* LiveActivityManager.swift */; };
ED2E328E2D10500900807A08 /* TrackRecordingButtonArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED46DD922D06F804007CACD6 /* TrackRecordingButtonArea.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 */; }; ED3EAC202B03C88100220A4A /* BottomTabBarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3EAC1F2B03C88100220A4A /* BottomTabBarButton.swift */; };
ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED43B8BC2C12063500D07BAA /* DocumentPicker.swift */; }; ED43B8BD2C12063500D07BAA /* DocumentPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED43B8BC2C12063500D07BAA /* DocumentPicker.swift */; };
ED46DDCE2D098A0B007CACD6 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED46DDCD2D098A0B007CACD6 /* WidgetKit.framework */; }; 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 */; }; ED914ABE2D351FF800973C45 /* UILabel+SetFontStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */; };
ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; }; ED9857082C4ED02D00694F6C /* MailComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9857072C4ED02D00694F6C /* MailComposer.swift */; };
ED9966802B94FBC20083CE55 /* ColorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED99667D2B94FBC20083CE55 /* ColorPicker.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 */; }; EDA1EAA42CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */; };
EDB71D8C2D8474A0004A6A7F /* CornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */; }; EDB71D8C2D8474A0004A6A7F /* CornerRadius.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */; };
EDB71E002D8B0338004A6A7F /* ModalPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.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 */; }; EDFDFB4C2B722C9C0013A44C /* InfoTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB4B2B722C9C0013A44C /* InfoTableViewCell.swift */; };
EDFDFB522B726F1A0013A44C /* ButtonsStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB512B726F1A0013A44C /* ButtonsStackView.swift */; }; EDFDFB522B726F1A0013A44C /* ButtonsStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB512B726F1A0013A44C /* ButtonsStackView.swift */; };
EDFDFB612B74E2500013A44C /* DonationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFDFB602B74E2500013A44C /* DonationView.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 */; }; 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 */; }; 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 */; }; 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; }; 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 = "<group>"; }; ED48BBB817C2B1E2003E7E92 /* CircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleView.h; sourceTree = "<group>"; };
ED48BBB917C2B1E2003E7E92 /* CircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleView.m; sourceTree = "<group>"; }; ED48BBB917C2B1E2003E7E92 /* CircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleView.m; sourceTree = "<group>"; };
ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingViewController.swift; sourceTree = "<group>"; }; ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingButtonViewController.swift; sourceTree = "<group>"; };
ED4DC7732CAEDECC0029B338 /* ProductButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductButton.swift; sourceTree = "<group>"; }; ED4DC7732CAEDECC0029B338 /* ProductButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductButton.swift; sourceTree = "<group>"; };
ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = "<group>"; }; ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = "<group>"; };
ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewModel.swift; sourceTree = "<group>"; }; ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewModel.swift; sourceTree = "<group>"; };
@@ -1525,6 +1527,7 @@
ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SetFontStyle.swift"; sourceTree = "<group>"; }; ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SetFontStyle.swift"; sourceTree = "<group>"; };
ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = "<group>"; }; ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = "<group>"; };
ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = "<group>"; }; ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = "<group>"; };
ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacePageTrackRecordingLayout.swift; sourceTree = "<group>"; };
EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = "<group>"; }; EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = "<group>"; };
EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadius.swift; sourceTree = "<group>"; }; EDB71D8B2D8474A0004A6A7F /* CornerRadius.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerRadius.swift; sourceTree = "<group>"; };
EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationAnimator.swift; sourceTree = "<group>"; }; EDB71DFF2D8B0338004A6A7F /* ModalPresentationAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModalPresentationAnimator.swift; sourceTree = "<group>"; };
@@ -1556,6 +1559,7 @@
EDFDFB4B2B722C9C0013A44C /* InfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoTableViewCell.swift; sourceTree = "<group>"; }; EDFDFB4B2B722C9C0013A44C /* InfoTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoTableViewCell.swift; sourceTree = "<group>"; };
EDFDFB512B726F1A0013A44C /* ButtonsStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsStackView.swift; sourceTree = "<group>"; }; EDFDFB512B726F1A0013A44C /* ButtonsStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsStackView.swift; sourceTree = "<group>"; };
EDFDFB602B74E2500013A44C /* DonationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationView.swift; sourceTree = "<group>"; }; EDFDFB602B74E2500013A44C /* DonationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationView.swift; sourceTree = "<group>"; };
EDFE1A492DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+UnknownCurrentPosition.swift"; sourceTree = "<group>"; };
EE026F0511D6AC0D00645242 /* classificator.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = classificator.txt; path = ../../data/classificator.txt; sourceTree = SOURCE_ROOT; }; 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 = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; EED10A4411F78D120095FAD4 /* MapViewController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = MapViewController.mm; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
F607C1831C032A8800B53A87 /* resources-hdpi_light */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-hdpi_light"; path = "../../data/resources-hdpi_light"; sourceTree = "<group>"; }; F607C1831C032A8800B53A87 /* resources-hdpi_light */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "resources-hdpi_light"; path = "../../data/resources-hdpi_light"; sourceTree = "<group>"; };
@@ -2596,7 +2600,7 @@
34BC72091B0DECAE0012A34B /* MapViewControls */ = { 34BC72091B0DECAE0012A34B /* MapViewControls */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */, ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */,
340537621BBED98600D452C6 /* MWMMapViewControlsCommon.h */, 340537621BBED98600D452C6 /* MWMMapViewControlsCommon.h */,
34BC72101B0DECAE0012A34B /* MWMMapViewControlsManager.h */, 34BC72101B0DECAE0012A34B /* MWMMapViewControlsManager.h */,
34BC72111B0DECAE0012A34B /* MWMMapViewControlsManager.mm */, 34BC72111B0DECAE0012A34B /* MWMMapViewControlsManager.mm */,
@@ -3060,6 +3064,7 @@
99C6532123F2F506004322F3 /* IPlacePageLayout.swift */, 99C6532123F2F506004322F3 /* IPlacePageLayout.swift */,
99F3EB0223F4178200C713F8 /* PlacePageCommonLayout.swift */, 99F3EB0223F4178200C713F8 /* PlacePageCommonLayout.swift */,
993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */, 993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */,
ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */,
); );
path = Layouts; path = Layouts;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -3446,6 +3451,14 @@
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
EDFE1A462DF1986900FDEA38 /* UnknownCurrentPositionAlert */ = {
isa = PBXGroup;
children = (
EDFE1A492DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift */,
);
path = UnknownCurrentPositionAlert;
sourceTree = "<group>";
};
F607C18B1C047FCA00B53A87 /* Segue */ = { F607C18B1C047FCA00B53A87 /* Segue */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -3496,6 +3509,7 @@
F64F195F1AB8125C006EAF7E /* CustomAlert */ = { F64F195F1AB8125C006EAF7E /* CustomAlert */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
EDFE1A462DF1986900FDEA38 /* UnknownCurrentPositionAlert */,
447DB4B72BA7826D000DF4C2 /* ReauthAlert */, 447DB4B72BA7826D000DF4C2 /* ReauthAlert */,
F62607FB207B78E300176C5A /* SpinnerAlert */, F62607FB207B78E300176C5A /* SpinnerAlert */,
F6D67CDA2062B9810032FD38 /* CreateBookmarkCategory */, F6D67CDA2062B9810032FD38 /* CreateBookmarkCategory */,
@@ -4489,8 +4503,9 @@
99F3EB1223F418C900C713F8 /* PlacePageInteractor.swift in Sources */, 99F3EB1223F418C900C713F8 /* PlacePageInteractor.swift in Sources */,
340708651F2905A500029ECC /* NavigationInfoArea.swift in Sources */, 340708651F2905A500029ECC /* NavigationInfoArea.swift in Sources */,
993DF0CC23F6BD0600AC231A /* ElevationDetailsPresenter.swift in Sources */, 993DF0CC23F6BD0600AC231A /* ElevationDetailsPresenter.swift in Sources */,
EDFE1A4A2DF1989700FDEA38 /* UIAlertController+UnknownCurrentPosition.swift in Sources */,
34AB666B1FC5AA330078E451 /* TransportTransitCell.swift in Sources */, 34AB666B1FC5AA330078E451 /* TransportTransitCell.swift in Sources */,
ED2E32912D10501700807A08 /* TrackRecordingViewController.swift in Sources */, ED2E32912D10501700807A08 /* TrackRecordingButtonViewController.swift in Sources */,
47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */, 47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */,
993DF11123F6BDB100AC231A /* UILabelRenderer.swift in Sources */, 993DF11123F6BDB100AC231A /* UILabelRenderer.swift in Sources */,
34AB66471FC5AA330078E451 /* RouteManagerTableView.swift in Sources */, 34AB66471FC5AA330078E451 /* RouteManagerTableView.swift in Sources */,
@@ -4633,6 +4648,7 @@
1DFA2F6A20D3B57400FB2C66 /* UIColor+PartnerColor.m in Sources */, 1DFA2F6A20D3B57400FB2C66 /* UIColor+PartnerColor.m in Sources */,
9989273B2449E60200260CE2 /* BottomMenuBuilder.swift in Sources */, 9989273B2449E60200260CE2 /* BottomMenuBuilder.swift in Sources */,
993DF10F23F6BDB100AC231A /* UIActivityIndicatorRenderer.swift in Sources */, 993DF10F23F6BDB100AC231A /* UIActivityIndicatorRenderer.swift in Sources */,
ED9DDF882D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift in Sources */,
ED0B1FEF2CAA9A25006E31A4 /* UIView+Highlight.swift in Sources */, ED0B1FEF2CAA9A25006E31A4 /* UIView+Highlight.swift in Sources */,
99A614E423CDD1D900D8D8D0 /* UIButton+RuntimeAttributes.m in Sources */, 99A614E423CDD1D900D8D8D0 /* UIButton+RuntimeAttributes.m in Sources */,
343E75981E5B1EE20041226A /* MWMCollectionViewController.m in Sources */, 343E75981E5B1EE20041226A /* MWMCollectionViewController.m in Sources */,

View File

@@ -10,7 +10,7 @@ final class TrackRecordingButtonArea: AvailableArea {
} }
override func notifyObserver() { override func notifyObserver() {
TrackRecordingViewController.updateAvailableArea(areaFrame) TrackRecordingButtonViewController.updateAvailableArea(areaFrame)
} }
} }

View File

@@ -66,12 +66,9 @@ extension BottomMenuInteractor: BottomMenuInteractorProtocol {
} }
func shareLocation(cell: BottomMenuItemCell) { func shareLocation(cell: BottomMenuItemCell) {
let lastLocation = LocationManager.lastLocation() guard let coordinates = LocationManager.lastLocation()?.coordinate else {
guard let coordinates = lastLocation?.coordinate else { viewController?.present(UIAlertController.unknownCurrentPosition(), animated: true, completion: nil)
let alert = UIAlertController(title: L("unknown_current_position"), message: nil, preferredStyle: .alert) return
alert.addAction(UIAlertAction(title: L("ok"), style: .default, handler: nil))
viewController?.present(alert, animated: true, completion: nil)
return;
} }
guard let viewController = viewController else { return } guard let viewController = viewController else { return }
let vc = ActivityViewController.share(forMyPosition: coordinates) let vc = ActivityViewController.share(forMyPosition: coordinates)
@@ -79,8 +76,13 @@ extension BottomMenuInteractor: BottomMenuInteractorProtocol {
} }
func toggleTrackRecording() { func toggleTrackRecording() {
trackRecorder.processAction(trackRecorder.recordingState == .active ? .stop : .start) { [weak self] in switch trackRecorder.recordingState {
self?.close() case .active:
break
case .inactive:
trackRecorder.processAction(.start)
} }
close()
MapViewController.shared()?.showTrackRecordingPlacePage()
} }
} }

View File

@@ -62,19 +62,27 @@ final class ActionBarViewController: UIViewController {
fatalError() fatalError()
} }
} }
var buttons: [ActionBarButtonType] = [] var buttons: [ActionBarButtonType] = []
if isRoutePlanning { switch placePageData.objectType {
buttons.append(.routeFrom) case .POI, .bookmark, .track:
} if isRoutePlanning {
let hasAnyPhones = !(placePageData.infoData?.phones ?? []).isEmpty buttons.append(.routeFrom)
if hasAnyPhones, AppInfo.shared().canMakeCalls { }
buttons.append(.call) let hasAnyPhones = !(placePageData.infoData?.phones ?? []).isEmpty
} if hasAnyPhones, AppInfo.shared().canMakeCalls {
if !isRoutePlanning { buttons.append(.call)
buttons.append(.routeFrom) }
if !isRoutePlanning {
buttons.append(.routeFrom)
}
case .trackRecording:
break
@unknown default:
fatalError()
} }
assert(buttons.count > 0) guard !buttons.isEmpty else { return }
visibleButtons.append(buttons[0]) visibleButtons.append(buttons[0])
if buttons.count > 1 { if buttons.count > 1 {
additionalButtons.append(contentsOf: buttons.suffix(from: 1)) additionalButtons.append(contentsOf: buttons.suffix(from: 1))
@@ -83,21 +91,24 @@ final class ActionBarViewController: UIViewController {
private func configButton2() { private func configButton2() {
var buttons: [ActionBarButtonType] = [] var buttons: [ActionBarButtonType] = []
if canAddStop {
buttons.append(.routeAddStop)
}
switch placePageData.objectType { switch placePageData.objectType {
case .POI, .bookmark: case .POI, .bookmark:
if canAddStop {
buttons.append(.routeAddStop)
}
buttons.append(.bookmark) buttons.append(.bookmark)
case .track: case .track:
if canAddStop {
buttons.append(.routeAddStop)
}
buttons.append(.track) buttons.append(.track)
case .trackRecording: case .trackRecording:
// TODO: implement for track recording buttons.append(.saveTrackRecording)
break
@unknown default: @unknown default:
fatalError() fatalError()
} }
assert(buttons.count > 0) assert(buttons.count > 0)
visibleButtons.append(buttons[0]) visibleButtons.append(buttons[0])
if buttons.count > 1 { if buttons.count > 1 {
additionalButtons.append(contentsOf: buttons.suffix(from: 1)) additionalButtons.append(contentsOf: buttons.suffix(from: 1))
@@ -105,7 +116,14 @@ final class ActionBarViewController: UIViewController {
} }
private func configButton3() { 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() { private func configButton4() {

View File

@@ -85,6 +85,11 @@ extension ElevationProfilePresenter: ElevationProfilePresenterProtocol {
view?.setChartData(ChartPresentationData(chartData, formatter: formatter)) view?.setChartData(ChartPresentationData(chartData, formatter: formatter))
view?.reloadDescription() view?.reloadDescription()
guard !profileData.isTrackRecording else {
view?.isChartViewInfoHidden = true
return
}
view?.setActivePoint(profileData.activePoint) view?.setActivePoint(profileData.activePoint)
view?.setMyPosition(profileData.myPosition) view?.setMyPosition(profileData.myPosition)
bookmarkManager.setElevationActivePointChanged(profileData.trackId) { [weak self] distance in bookmarkManager.setElevationActivePointChanged(profileData.trackId) { [weak self] distance in

View File

@@ -51,8 +51,6 @@ extension PlacePageHeaderPresenter: PlacePageHeaderPresenterProtocol {
view?.isExpandViewHidden = true view?.isExpandViewHidden = true
view?.isShadowViewHidden = false view?.isShadowViewHidden = false
} }
// TODO: (KK) Enable share button for the tracks to share the whole track gpx/kml
view?.isShareButtonHidden = false
} }
func onClosePress() { func onClosePress() {

View File

@@ -2,7 +2,6 @@ protocol PlacePageHeaderViewProtocol: AnyObject {
var presenter: PlacePageHeaderPresenterProtocol? { get set } var presenter: PlacePageHeaderPresenterProtocol? { get set }
var isExpandViewHidden: Bool { get set } var isExpandViewHidden: Bool { get set }
var isShadowViewHidden: Bool { get set } var isShadowViewHidden: Bool { get set }
var isShareButtonHidden: Bool { get set }
func setTitle(_ title: String?, secondaryTitle: String?) func setTitle(_ title: String?, secondaryTitle: String?)
func showShareTrackMenu() 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?) { func setTitle(_ title: String?, secondaryTitle: String?) {
titleText = title titleText = title
secondaryText = secondaryTitle secondaryText = secondaryTitle

View File

@@ -15,8 +15,7 @@
case .track: case .track:
layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data) layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data)
case .trackRecording: case .trackRecording:
// TODO: Implement PlacePageTrackRecordingLayout layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data)
fatalError("PlacePageTrackRecordingLayout is not implemented")
@unknown default: @unknown default:
fatalError() fatalError()
} }
@@ -34,14 +33,14 @@
data: data, data: data,
mapViewController: MapViewController.shared()!) mapViewController: MapViewController.shared()!)
let layout: IPlacePageLayout let layout: IPlacePageLayout
let storyboard = viewController.storyboard!
switch data.objectType { switch data.objectType {
case .POI, .bookmark: case .POI, .bookmark:
layout = PlacePageCommonLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data) layout = PlacePageCommonLayout(interactor: interactor, storyboard: storyboard, data: data)
case .track: case .track:
layout = PlacePageTrackLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data) layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data)
case .trackRecording: case .trackRecording:
// TODO: Implement PlacePageTrackRecordingLayout layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data)
fatalError("PlacePageTrackRecordingLayout is not implemented")
@unknown default: @unknown default:
fatalError() fatalError()
} }

View File

@@ -246,9 +246,19 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate {
fatalError("More button should've been handled in ActionBarViewContoller") fatalError("More button should've been handled in ActionBarViewContoller")
case .track: case .track:
guard placePageData.trackData != nil else { return } 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. // directly here when the track recovery mechanism will be implemented.
showTrackDeletionConfirmationDialog() 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: @unknown default:
fatalError() fatalError()
} }
@@ -298,8 +308,8 @@ extension PlacePageInteractor: ElevationProfileViewControllerDelegate {
} }
func updateMapPoint(_ point: CLLocationCoordinate2D, distance: Double) { func updateMapPoint(_ point: CLLocationCoordinate2D, distance: Double) {
guard let trackId = placePageData.trackData?.trackId else { return } guard let trackData = placePageData.trackData, trackData.elevationProfileData?.isTrackRecording == false else { return }
BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackId) BookmarksManager.shared().setElevationActivePoint(point, distance: distance, trackId: trackData.trackId)
} }
} }
@@ -323,7 +333,12 @@ extension PlacePageInteractor: PlacePageHeaderViewControllerDelegate {
case .track: case .track:
presenter?.showShareTrackMenu() presenter?.showShareTrackMenu()
default: 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)
} }
} }

View File

@@ -3,6 +3,7 @@ typedef NS_ENUM(NSInteger, MWMActionBarButtonType) {
MWMActionBarButtonTypeBookingSearch, MWMActionBarButtonTypeBookingSearch,
MWMActionBarButtonTypeBookmark, MWMActionBarButtonTypeBookmark,
MWMActionBarButtonTypeTrack, MWMActionBarButtonTypeTrack,
MWMActionBarButtonTypeSaveTrackRecording,
MWMActionBarButtonTypeCall, MWMActionBarButtonTypeCall,
MWMActionBarButtonTypeDownload, MWMActionBarButtonTypeDownload,
MWMActionBarButtonTypeMore, MWMActionBarButtonTypeMore,

View File

@@ -19,6 +19,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) {
case MWMActionBarButtonTypeBookmark: case MWMActionBarButtonTypeBookmark:
case MWMActionBarButtonTypeTrack: case MWMActionBarButtonTypeTrack:
return L(isSelected ? @"delete" : @"save"); return L(isSelected ? @"delete" : @"save");
case MWMActionBarButtonTypeSaveTrackRecording:
return L(@"save");
case MWMActionBarButtonTypeRouteFrom: case MWMActionBarButtonTypeRouteFrom:
return L(@"p2p_from_here"); return L(@"p2p_from_here");
case MWMActionBarButtonTypeRouteTo: case MWMActionBarButtonTypeRouteTo:
@@ -55,7 +57,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) {
self.label.text = titleForButton(self.type, isSelected); self.label.text = titleForButton(self.type, isSelected);
self.extraBackground.hidden = YES; self.extraBackground.hidden = YES;
self.button.coloring = MWMButtonColoringBlack; self.button.coloring = MWMButtonColoringBlack;
[self.button.imageView setContentMode:UIViewContentModeScaleAspectFit];
switch (self.type) { switch (self.type) {
case MWMActionBarButtonTypeDownload: { case MWMActionBarButtonTypeDownload: {
if (self.mapDownloadProgress) 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 setImage:[[UIImage imageNamed:@"ic_route_manager_trash"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal];
self.button.coloring = MWMButtonColoringRed; self.button.coloring = MWMButtonColoringRed;
break; break;
case MWMActionBarButtonTypeSaveTrackRecording:
[self.button setImage:[UIImage imageNamed:@"ic_placepage_save_track_recording"] forState:UIControlStateNormal];
break;
case MWMActionBarButtonTypeRouteFrom: case MWMActionBarButtonTypeRouteFrom:
[self.button setImage:[UIImage imageNamed:@"ic_route_from"] forState:UIControlStateNormal]; [self.button setImage:[UIImage imageNamed:@"ic_route_from"] forState:UIControlStateNormal];
break; break;

View File

@@ -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()
}
}