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