[ios] implement PP for the track recording

Signed-off-by: Kiryl Kaveryn <kirylkaveryn@gmail.com>
This commit is contained in:
Kiryl Kaveryn 2025-01-09 16:46:22 +04:00
parent bfb2dc0154
commit 9ada5eeb6b
20 changed files with 327 additions and 128 deletions

View file

@ -4,7 +4,13 @@
@class MapViewController;
@class BottomTabBarViewController;
@class TrackRecordingViewController;
@class TrackRecordingButtonViewController;
typedef NS_ENUM(NSUInteger, TrackRecordingButtonState) {
TrackRecordingButtonStateHidden,
TrackRecordingButtonStateVisible,
TrackRecordingButtonStateClosed,
};
@protocol MWMFeatureHolder;
@ -20,7 +26,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;
@ -34,6 +40,8 @@
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator;
- (void)setTrackRecordingButtonState:(TrackRecordingButtonState)state;
#pragma mark - MWMNavigationDashboardManager
- (void)onRoutePrepare;

View file

@ -28,15 +28,15 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue";
@interface MWMMapViewControlsManager () <BottomMenuDelegate>
@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<MWMPlacePageProtocol> 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 {

View file

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

View file

@ -33,6 +33,7 @@
- (void)openFullPlaceDescriptionWithHtml:(NSString *_Nonnull)htmlString;
- (void)searchText:(NSString *_Nonnull)text;
- (void)openDrivingOptions;
- (void)showTrackRecordingPlacePage;
- (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) SearchOnMapManager *searchManager;
@property(nonatomic, readwrite) TrackRecordingManager *trackRecordingManager;
@property(nonatomic) BOOL disableStandbyOnLocationStateMode;
@ -116,29 +117,7 @@ NSString *const kSettingsSegue = @"Map2Settings";
return [MapsAppDelegate theApp].mapViewController;
}
#pragma mark - Map Navigation
- (void)showTrackRecordingPlacePage {
__block PlacePageData * placePageData = [[PlacePageData alloc] initWithTrackInfo:TrackRecordingManager.shared.trackRecordingInfo
elevationInfo:[MWMFrameworkHelper trackRecordingElevationInfo]];
[TrackRecordingManager.shared addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * _Nonnull trackInfo) {
switch (state) {
case TrackRecordingStateInactive:
[self stopObservingTrackRecordingUpdates];
break;
case TrackRecordingStateActive:
if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive)
return;
[placePageData updateWithTrackInfo:trackInfo elevationInfo:[MWMFrameworkHelper trackRecordingElevationInfo]];
break;
}
}];
[self showOrUpdatePlacePage:placePageData];
}
- (void)stopObservingTrackRecordingUpdates {
[TrackRecordingManager.shared removeObserver:self];
}
#pragma mark - PlacePage
- (void)showOrUpdatePlacePage:(PlacePageData *)data {
if (self.searchManager.isSearching)
@ -146,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];
}
@ -433,6 +413,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)
@ -754,6 +737,12 @@ NSString *const kSettingsSegue = @"Map2Settings";
return _searchManager;
}
- (TrackRecordingManager *)trackRecordingManager {
if (!_trackRecordingManager)
_trackRecordingManager = TrackRecordingManager.shared;
return _trackRecordingManager;
}
- (UIView * _Nullable)searchViewContainer {
return self.searchManager.viewController.view;
}
@ -873,6 +862,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)) {

View file

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

View file

@ -24,13 +24,22 @@ enum TrackRecordingActionResult {
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 {
@ -62,6 +71,10 @@ final class TrackRecordingManager: NSObject {
private var observations: [Observation] = []
private(set) var trackRecordingInfo: TrackInfo = .empty()
var trackRecordingElevationProfileData: ElevationProfileData {
FrameworkHelper.trackRecordingElevationInfo()
}
var recordingState: TrackRecordingState {
trackRecorder.isTrackRecordingEnabled() ? .active : .inactive
}
@ -73,6 +86,7 @@ final class TrackRecordingManager: NSObject {
self.locationService = locationService
self.activityManager = activityManager
super.init()
self.subscribeOnTheAppLifecycleEvents()
}
// MARK: - Public methods
@ -115,6 +129,13 @@ final class TrackRecordingManager: NSObject {
// MARK: - Private methods
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
@ -199,7 +220,9 @@ extension TrackRecordingManager: TrackRecordingObservable {
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
@ -212,8 +235,11 @@ extension TrackRecordingManager: TrackRecordingObservable {
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 })
}
}
}

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

@ -481,7 +481,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 */; };
@ -524,6 +524,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 */; };
EDBD68072B625724005DD151 /* LocationServicesDisabledAlert.xib in Resources */ = {isa = PBXBuildFile; fileRef = EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */; };
EDBD680B2B62572E005DD151 /* LocationServicesDisabledAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */; };
@ -1449,7 +1450,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 = "<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>"; };
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>"; };
@ -1490,6 +1491,7 @@
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>"; };
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>"; };
EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LocationServicesDisabledAlert.xib; sourceTree = "<group>"; };
EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServicesDisabledAlert.swift; sourceTree = "<group>"; };
@ -2569,7 +2571,7 @@
34BC72091B0DECAE0012A34B /* MapViewControls */ = {
isa = PBXGroup;
children = (
ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */,
ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */,
340537621BBED98600D452C6 /* MWMMapViewControlsCommon.h */,
34BC72101B0DECAE0012A34B /* MWMMapViewControlsManager.h */,
34BC72111B0DECAE0012A34B /* MWMMapViewControlsManager.mm */,
@ -3031,6 +3033,7 @@
99C6532123F2F506004322F3 /* IPlacePageLayout.swift */,
99F3EB0223F4178200C713F8 /* PlacePageCommonLayout.swift */,
993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */,
ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */,
);
path = Layouts;
sourceTree = "<group>";
@ -4489,7 +4492,7 @@
340708651F2905A500029ECC /* NavigationInfoArea.swift in Sources */,
993DF0CC23F6BD0600AC231A /* ElevationDetailsPresenter.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 */,
@ -4630,6 +4633,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 */,

View file

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

View file

@ -79,8 +79,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()
}
}

View file

@ -62,18 +62,26 @@ final class ActionBarViewController: UIViewController {
fatalError()
}
}
var buttons: [ActionBarButtonType] = []
if isRoutePlanning {
buttons.append(.routeFrom)
}
if placePageData.infoData?.phone != nil, AppInfo.shared().canMakeCalls {
buttons.append(.call)
}
if !isRoutePlanning {
buttons.append(.routeFrom)
switch placePageData.objectType {
case .POI, .bookmark, .track:
if isRoutePlanning {
buttons.append(.routeFrom)
}
if placePageData.infoData?.phone != nil, 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))
@ -91,8 +99,7 @@ final class ActionBarViewController: UIViewController {
case .track:
buttons.append(.track)
case .trackRecording:
// TODO: implement for track recording
break
buttons.append(.saveTrackRecording)
@unknown default:
fatalError()
}
@ -104,7 +111,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() {

View file

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

View file

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

View file

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

View file

@ -25,7 +25,7 @@ class PlacePageInteractor: NSObject {
private func updatePlacePageIfNeeded() {
let isBookmark = placePageData.bookmarkData != nil && bookmarksManager.hasBookmark(placePageData.bookmarkData!.bookmarkId)
let isTrack = placePageData.trackData != nil && bookmarksManager.hasTrack(placePageData.trackData!.trackId)
let isTrack = placePageData.trackData != nil/* && bookmarksManager.hasTrack(placePageData.trackData!.trackId)*/
guard isBookmark || isTrack else {
presenter?.closeAnimated()
return
@ -239,11 +239,11 @@ 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
// TODO: (KK) pass name typed by user
TrackRecordingManager.shared.processAction(.stopAndSave(name: "")) { [weak self] result in
switch result {
case .success:
@ -287,8 +287,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)
}
}

View file

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

View file

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

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