From 1c6bedf230ae671d5c856f4c0a375714aacfccab Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 14:19:42 +0400 Subject: [PATCH 01/10] [ios] return the ElevationInfo for the current track recording Signed-off-by: Kiryl Kaveryn --- iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h | 4 ++++ iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h index a81f261b43..cbadf8945c 100644 --- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h +++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h @@ -5,6 +5,7 @@ @class MWMMapSearchResult; @class TrackInfo; +@class ElevationProfileData; typedef NS_ENUM(NSUInteger, MWMZoomMode) { MWMZoomModeIn = 0, MWMZoomModeOut }; @@ -28,6 +29,9 @@ typedef void (^TrackRecordingUpdatedHandler)(TrackInfo * _Nonnull trackInfo); + (void)saveTrackRecordingWithName:(nullable NSString *)name; + (BOOL)isTrackRecordingEnabled; + (BOOL)isTrackRecordingEmpty; +/// Returns current track recording elevation info. +/// If the track recording is not in progress, returns empty ElevationProfileData. ++ (ElevationProfileData * _Nonnull)trackRecordingElevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm index d00c60df64..6b8ea0aa9a 100644 --- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm +++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm @@ -3,6 +3,7 @@ #import "ProductsConfiguration+Core.h" #import "Product+Core.h" #import "TrackInfo+Core.h" +#import "ElevationProfileData+Core.h" #include "Framework.h" @@ -246,6 +247,10 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore( return GetFramework().IsTrackRecordingEmpty(); } ++ (ElevationProfileData * _Nonnull)trackRecordingElevationInfo { + return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingCurrentElevationInfo()]; +} + // MARK: - ProductsManager + (nullable ProductsConfiguration *)getProductsConfiguration { -- 2.45.3 From 867778c368d986b108a7d1ff21210f92f10b4680 Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 16:35:31 +0400 Subject: [PATCH 02/10] [ios] subscribe on the ElevationInfo updates when the TR PP is opened Signed-off-by: Kiryl Kaveryn --- .../Common/PlacePagePreviewData.h | 3 ++ .../Common/PlacePagePreviewData.mm | 16 ++++++++ .../PlacePageData/Common/PlacePageTrackData.h | 6 ++- .../Common/PlacePageTrackData.mm | 9 +++++ .../ElevationProfileData+Core.h | 1 + .../ElevationProfile/ElevationProfileData.h | 1 + .../ElevationProfile/ElevationProfileData.mm | 37 +++++++++++++------ .../CoreApi/PlacePageData/PlacePageData.h | 4 ++ .../CoreApi/PlacePageData/PlacePageData.mm | 20 ++++++++++ iphone/Maps/Classes/MapViewController.mm | 24 ++++++++++++ 10 files changed, 107 insertions(+), 14 deletions(-) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h index ab25289cb4..8b5a10fa6d 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.h @@ -1,6 +1,7 @@ #import @class PlacePageScheduleData; +@class TrackInfo; typedef NS_ENUM(NSInteger, PlacePageDataHotelType) { PlacePageDataHotelTypeHotel, @@ -39,6 +40,8 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) PlacePageDataSchedule schedule; @property(nonatomic, readonly) BOOL isMyPosition; +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo; + @end NS_ASSUME_NONNULL_END diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm index 9aed885cbb..785d906302 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePagePreviewData.mm @@ -1,4 +1,8 @@ #import "PlacePagePreviewData+Core.h" +#import "DistanceFormatter.h" +#import "AltitudeFormatter.h" +#import "DurationFormatter.h" +#import "TrackInfo.h" #include "3party/opening_hours/opening_hours.hpp" @@ -46,6 +50,18 @@ static PlacePageDataSchedule convertOpeningHours(std::string_view rawOH) @implementation PlacePagePreviewData +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo { + self = [super init]; + if (self) { + // TODO: (KK) Replace separator with a shared static constant. + NSString * kSeparator = @" • "; + NSString * duration = [DurationFormatter durationStringFromTimeInterval:trackInfo.duration]; + NSString * distance = [DistanceFormatter distanceStringFromMeters:trackInfo.distance]; + _title = [@[duration, distance] componentsJoinedByString:kSeparator]; + } + return self; +} + @end @implementation PlacePagePreviewData (Core) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h index 0a6ad57bf4..fa83eb897a 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.h @@ -10,8 +10,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) MWMTrackID trackId; @property(nonatomic, readonly) MWMMarkGroupID groupId; -@property(nonatomic, readonly, nonnull) TrackInfo * trackInfo; -@property(nonatomic, readonly, nullable) ElevationProfileData * elevationProfileData; +@property(nonatomic, readwrite, nonnull) TrackInfo * trackInfo; +@property(nonatomic, readwrite, nullable) ElevationProfileData * elevationProfileData; + +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm index ad421d8f16..d8b4ff89fe 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/Common/PlacePageTrackData.mm @@ -4,6 +4,15 @@ @implementation PlacePageTrackData +- (nonnull instancetype)initWithTrackInfo:(TrackInfo *)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo { + self = [super init]; + if (self) { + _trackInfo = trackInfo; + _elevationProfileData = elevationInfo; + } + return self; +} + @end @implementation PlacePageTrackData (Core) diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h index 48c63cde20..0a00cabca2 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData+Core.h @@ -11,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN elevationInfo:(ElevationInfo const &)elevationInfo activePoint:(double)activePoint myPosition:(double)myPosition; +- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h index f8b5a070e3..2087a30a2f 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.h @@ -13,6 +13,7 @@ typedef NS_ENUM(NSInteger, ElevationDifficulty) { @interface ElevationProfileData : NSObject @property(nonatomic, readonly) uint64_t trackId; +@property(nonatomic, readonly) BOOL isTrackRecording; @property(nonatomic, readonly) ElevationDifficulty difficulty; @property(nonatomic, readonly) NSArray * points; @property(nonatomic, readonly) double activePoint; diff --git a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm index 69e89c9388..ffdd63c2d4 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/ElevationProfile/ElevationProfileData.mm @@ -30,23 +30,36 @@ static ElevationDifficulty convertDifficulty(uint8_t difficulty) { if (self) { _trackId = trackId; _difficulty = convertDifficulty(elevationInfo.GetDifficulty()); - - auto const & points = elevationInfo.GetPoints(); - NSMutableArray * pointsArray = [[NSMutableArray alloc] initWithCapacity:points.size()]; - for (auto const & point : points) { - auto pointLatLon = mercator::ToLatLon(point.m_point.GetPoint()); - CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(pointLatLon.m_lat, pointLatLon.m_lon); - ElevationHeightPoint * elevationPoint = [[ElevationHeightPoint alloc] initWithCoordinates:coordinates - distance:point.m_distance - andAltitude:point.m_point.GetAltitude()]; - [pointsArray addObject:elevationPoint]; - } - _points = [pointsArray copy]; + _points = [ElevationProfileData pointsFromElevationInfo:elevationInfo]; _activePoint = activePoint; _myPosition = myPosition; + _isTrackRecording = false; } return self; } +- (instancetype)initWithElevationInfo:(ElevationInfo const &)elevationInfo { + self = [super init]; + if (self) { + _difficulty = convertDifficulty(elevationInfo.GetDifficulty()); + _points = [ElevationProfileData pointsFromElevationInfo:elevationInfo]; + _isTrackRecording = true; + } + return self; +} + ++ (NSArray *)pointsFromElevationInfo:(ElevationInfo const &)elevationInfo { + auto const & points = elevationInfo.GetPoints(); + NSMutableArray * pointsArray = [[NSMutableArray alloc] initWithCapacity:points.size()]; + for (auto const & point : points) { + auto pointLatLon = mercator::ToLatLon(point.m_point.GetPoint()); + CLLocationCoordinate2D coordinates = CLLocationCoordinate2DMake(pointLatLon.m_lat, pointLatLon.m_lon); + ElevationHeightPoint * elevationPoint = [[ElevationHeightPoint alloc] initWithCoordinates:coordinates + distance:point.m_distance + andAltitude:point.m_point.GetAltitude()]; + [pointsArray addObject:elevationPoint]; + } + return [pointsArray copy]; +} @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h index c3c198816d..99736a9707 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h +++ b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.h @@ -10,6 +10,7 @@ @class PlacePageBookmarkData; @class MWMMapNodeAttributes; @class TrackInfo; +@class ElevationProfileData; typedef NS_ENUM(NSInteger, PlacePageRoadType) { PlacePageRoadTypeToll, @@ -49,12 +50,15 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, readonly) CLLocationCoordinate2D locationCoordinate; @property(nonatomic, copy, nullable) MWMVoidBlock onBookmarkStatusUpdate; @property(nonatomic, copy, nullable) MWMVoidBlock onMapNodeStatusUpdate; +@property(nonatomic, copy, nullable) MWMVoidBlock onTrackRecordingProgressUpdate; @property(nonatomic, copy, nullable) void (^onMapNodeProgressUpdate)(uint64_t downloadedBytes, uint64_t totalBytes); - (instancetype)initWithLocalizationProvider:(id)localization; +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo; - (instancetype)init NS_UNAVAILABLE; - (void)updateBookmarkStatus; +- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo; @end diff --git a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm index cda7f27918..491452b0a2 100644 --- a/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm +++ b/iphone/CoreApi/CoreApi/PlacePageData/PlacePageData.mm @@ -5,6 +5,7 @@ #import "PlacePageInfoData+Core.h" #import "PlacePageBookmarkData+Core.h" #import "PlacePageTrackData+Core.h" +#import "ElevationProfileData+Core.h" #import "MWMMapNodeAttributes.h" #include @@ -84,6 +85,25 @@ static PlacePageRoadType convertRoadType(RoadWarningMarkType roadType) { return self; } +- (instancetype)initWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo { + self = [super init]; + if (self) { + _objectType = PlacePageObjectTypeTrackRecording; + _roadType = PlacePageRoadTypeNone; + _previewData = [[PlacePagePreviewData alloc] initWithTrackInfo:trackInfo]; + _trackData = [[PlacePageTrackData alloc] initWithTrackInfo:trackInfo elevationInfo:elevationInfo]; + } + return self; +} + +- (void)updateWithTrackInfo:(TrackInfo * _Nonnull)trackInfo elevationInfo:(ElevationProfileData * _Nullable)elevationInfo { + _previewData = [[PlacePagePreviewData alloc] initWithTrackInfo:trackInfo]; + _trackData.trackInfo = trackInfo; + _trackData.elevationProfileData = elevationInfo; + if (self.onTrackRecordingProgressUpdate != nil) + self.onTrackRecordingProgressUpdate(); +} + - (void)dealloc { if (self.mapNodeAttributes != nil) { [[MWMStorage sharedStorage] removeObserver:self]; diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 93fc70cb37..37944d4297 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -118,6 +118,28 @@ NSString *const kSettingsSegue = @"Map2Settings"; #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]; +} + - (void)showOrUpdatePlacePage:(PlacePageData *)data { if (self.searchManager.isSearching) [self.searchManager setPlaceOnMapSelected:YES]; @@ -190,6 +212,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; } - (void)hideRegularPlacePage { + [self stopObservingTrackRecordingUpdates]; [self.placePageVC closeAnimatedWithCompletion:^{ [self.placePageVC.view removeFromSuperview]; [self.placePageVC willMoveToParentViewController:nil]; @@ -233,6 +256,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; return; } PlacePageData * data = [[PlacePageData alloc] initWithLocalizationProvider:[[OpeinigHoursLocalization alloc] init]]; + [self stopObservingTrackRecordingUpdates]; [self showOrUpdatePlacePage:data]; } -- 2.45.3 From 106507fef59475693cdde157c3bfdd946a811ff1 Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 16:38:13 +0400 Subject: [PATCH 03/10] [ios] refactor TrackRecordingManager to return the proper state Signed-off-by: Kiryl Kaveryn --- .../CoreApi/Framework/MWMFrameworkHelper.h | 4 +- .../CoreApi/Framework/MWMFrameworkHelper.mm | 6 +- .../Maps/Core/Location/MWMLocationManager.h | 11 +- .../TrackRecorder/TrackRecordingManager.swift | 161 +++++++++--------- .../UI/PlacePage/PlacePageInteractor.swift | 10 ++ 5 files changed, 105 insertions(+), 87 deletions(-) diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h index cbadf8945c..0272047ba5 100644 --- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h +++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.h @@ -21,12 +21,12 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^SearchInDownloaderCompletions)(NSArray *results, BOOL finished); typedef void (^TrackRecordingUpdatedHandler)(TrackInfo * _Nonnull trackInfo); -@protocol TrackRecorder +@protocol TrackRecorder + (void)startTrackRecording; + (void)setTrackRecordingUpdateHandler:(TrackRecordingUpdatedHandler _Nullable)trackRecordingDidUpdate; + (void)stopTrackRecording; -+ (void)saveTrackRecordingWithName:(nullable NSString *)name; ++ (void)saveTrackRecordingWithName:(nonnull NSString *)name; + (BOOL)isTrackRecordingEnabled; + (BOOL)isTrackRecordingEmpty; /// Returns current track recording elevation info. diff --git a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm index 6b8ea0aa9a..47f6fe1461 100644 --- a/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm +++ b/iphone/CoreApi/CoreApi/Framework/MWMFrameworkHelper.mm @@ -235,8 +235,8 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore( GetFramework().StopTrackRecording(); } -+ (void)saveTrackRecordingWithName:(nullable NSString *)name { - GetFramework().SaveTrackRecordingWithName(name == nil ? "" : name.UTF8String); ++ (void)saveTrackRecordingWithName:(nonnull NSString *)name { + GetFramework().SaveTrackRecordingWithName(name.UTF8String); } + (BOOL)isTrackRecordingEnabled { @@ -248,7 +248,7 @@ static Framework::ProductsPopupCloseReason ConvertProductPopupCloseReasonToCore( } + (ElevationProfileData * _Nonnull)trackRecordingElevationInfo { - return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingCurrentElevationInfo()]; + return [[ElevationProfileData alloc] initWithElevationInfo:GetFramework().GetTrackRecordingElevationInfo()]; } // MARK: - ProductsManager diff --git a/iphone/Maps/Core/Location/MWMLocationManager.h b/iphone/Maps/Core/Location/MWMLocationManager.h index e662c47921..2bb3b033f7 100644 --- a/iphone/Maps/Core/Location/MWMLocationManager.h +++ b/iphone/Maps/Core/Location/MWMLocationManager.h @@ -3,8 +3,15 @@ NS_ASSUME_NONNULL_BEGIN +@protocol LocationService + ++ (BOOL)isLocationProhibited; ++ (void)checkLocationStatus; + +@end + NS_SWIFT_NAME(LocationManager) -@interface MWMLocationManager : NSObject +@interface MWMLocationManager : NSObject + (void)start; + (void)stop; @@ -14,10 +21,8 @@ NS_SWIFT_NAME(LocationManager) + (void)removeObserver:(id)observer NS_SWIFT_NAME(remove(observer:)); + (void)setMyPositionMode:(MWMMyPositionMode)mode; -+ (void)checkLocationStatus; + (nullable CLLocation *)lastLocation; -+ (BOOL)isLocationProhibited; + (nullable CLHeading *)lastHeading; + (void)applicationDidBecomeActive; diff --git a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift index 5012b2ed65..0cef948908 100644 --- a/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift +++ b/iphone/Maps/Core/TrackRecorder/TrackRecordingManager.swift @@ -4,31 +4,38 @@ enum TrackRecordingState: Int, Equatable { case active } -enum TrackRecordingAction: String, CaseIterable { +enum TrackRecordingAction { case start - case stop + case stopAndSave(name: String) } enum TrackRecordingError: Error { case locationIsProhibited + case trackIsEmpty + case systemError(Error) } -protocol TrackRecordingObserver: AnyObject { +enum TrackRecordingActionResult { + case success + case error(TrackRecordingError) +} + +@objc +protocol TrackRecordingObservable: AnyObject { + var recordingState: TrackRecordingState { get } + var trackRecordingInfo: TrackInfo { get } + func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) func removeObserver(_ observer: AnyObject) + func contains(_ observer: AnyObject) -> Bool } -typealias TrackRecordingStateHandler = (TrackRecordingState, TrackInfo?) -> Void +typealias TrackRecordingStateHandler = (TrackRecordingState, TrackInfo) -> Void @objcMembers final class TrackRecordingManager: NSObject { - typealias CompletionHandler = () -> Void - - private enum SavingOption { - case withoutSaving - case saveWithName(String? = nil) - } + typealias CompletionHandler = (TrackRecordingActionResult) -> Void fileprivate struct Observation { weak var observer: AnyObject? @@ -37,26 +44,33 @@ final class TrackRecordingManager: NSObject { static let shared: TrackRecordingManager = { let trackRecorder = FrameworkHelper.self + let locationManager = LocationManager.self var activityManager: TrackRecordingActivityManager? = nil #if canImport(ActivityKit) if #available(iOS 16.2, *) { activityManager = TrackRecordingLiveActivityManager.shared } #endif - return TrackRecordingManager(trackRecorder: trackRecorder, activityManager: activityManager) + return TrackRecordingManager(trackRecorder: trackRecorder, + locationService: locationManager, + activityManager: activityManager) }() private let trackRecorder: TrackRecorder.Type + private var locationService: LocationService.Type private var activityManager: TrackRecordingActivityManager? private var observations: [Observation] = [] - private var trackRecordingInfo: TrackInfo? + private(set) var trackRecordingInfo: TrackInfo = .empty() var recordingState: TrackRecordingState { trackRecorder.isTrackRecordingEnabled() ? .active : .inactive } - private init(trackRecorder: TrackRecorder.Type, activityManager: TrackRecordingActivityManager?) { + init(trackRecorder: TrackRecorder.Type, + locationService: LocationService.Type, + activityManager: TrackRecordingActivityManager?) { self.trackRecorder = trackRecorder + self.locationService = locationService self.activityManager = activityManager super.init() } @@ -84,22 +98,35 @@ final class TrackRecordingManager: NSObject { } func processAction(_ action: TrackRecordingAction, completion: (CompletionHandler)? = nil) { - switch action { - case .start: - start(completion: completion) - case .stop: - stop(completion: completion) + do { + switch action { + case .start: + try startRecording() + case .stopAndSave(let name): + stopRecording() + try checkIsTrackNotEmpty() + saveTrackRecording(name: name) + } + completion?(.success) + } catch { + handleError(error, completion: completion) } } // MARK: - Private methods - private func checkIsLocationEnabled() throws { - if LocationManager.isLocationProhibited() { + private func checkIsLocationEnabled() throws(TrackRecordingError) { + if locationService.isLocationProhibited() { throw TrackRecordingError.locationIsProhibited } } + private func checkIsTrackNotEmpty() throws(TrackRecordingError) { + if trackRecorder.isTrackRecordingEmpty() { + throw TrackRecordingError.trackIsEmpty + } + } + // MARK: - Handle track recording process private func subscribeOnTrackRecordingProgressUpdates() { @@ -113,92 +140,63 @@ final class TrackRecordingManager: NSObject { private func unsubscribeFromTrackRecordingProgressUpdates() { trackRecorder.setTrackRecordingUpdateHandler(nil) - trackRecordingInfo = nil } // MARK: - Handle Start/Stop event and Errors - private func start(completion: (CompletionHandler)? = nil) { - do { + private func startRecording() throws(TrackRecordingError) { + switch recordingState { + case .inactive: try checkIsLocationEnabled() - switch recordingState { - case .inactive: - subscribeOnTrackRecordingProgressUpdates() - trackRecorder.startTrackRecording() - notifyObservers() - try? activityManager?.start(with: trackRecordingInfo ?? .empty()) - case .active: - break + subscribeOnTrackRecordingProgressUpdates() + trackRecorder.startTrackRecording() + notifyObservers() + do { + try activityManager?.start(with: trackRecordingInfo) + } catch { + LOG(.warning, "Failed to start activity manager") + handleError(.systemError(error)) } - completion?() - } catch { - handleError(error, completion: completion) + case .active: + break } } - private func stop(completion: (CompletionHandler)? = nil) { - guard !trackRecorder.isTrackRecordingEmpty() else { - Toast.toast(withText: L("track_recording_toast_nothing_to_save")).show() - stopRecording(.withoutSaving, completion: completion) - return - } - Self.showOnFinishRecordingAlert(onSave: { [weak self] in - guard let self else { return } - // TODO: (KK) pass the user provided name from the track saving screen (when it will be implemented) - self.stopRecording(.saveWithName(), completion: completion) - }, - onStop: { [weak self] in - guard let self else { return } - self.stopRecording(.withoutSaving, completion: completion) - }, - onContinue: { - completion?() - }) - } - - private func stopRecording(_ savingOption: SavingOption, completion: (CompletionHandler)? = nil) { + private func stopRecording() { unsubscribeFromTrackRecordingProgressUpdates() trackRecorder.stopTrackRecording() + trackRecordingInfo = .empty() activityManager?.stop() notifyObservers() - - switch savingOption { - case .withoutSaving: - break - case .saveWithName(let name): - trackRecorder.saveTrackRecording(withName: name) - } - completion?() } - private func handleError(_ error: Error, completion: (CompletionHandler)? = nil) { - LOG(.error, error.localizedDescription) + private func saveTrackRecording(name: String) { + trackRecorder.saveTrackRecording(withName: name) + } + + private func handleError(_ error: TrackRecordingError, completion: (CompletionHandler)? = nil) { switch error { case TrackRecordingError.locationIsProhibited: // Show alert to enable location - LocationManager.checkLocationStatus() - default: + locationService.checkLocationStatus() + case TrackRecordingError.trackIsEmpty: + Toast.toast(withText: L("track_recording_toast_nothing_to_save")).show() + case TrackRecordingError.systemError(let error): + LOG(.error, error.localizedDescription) break } - stopRecording(.withoutSaving, completion: completion) - } - - private static func showOnFinishRecordingAlert(onSave: @escaping CompletionHandler, - onStop: @escaping CompletionHandler, - onContinue: @escaping CompletionHandler) { - let alert = UIAlertController(title: L("track_recording_alert_title"), message: nil, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: L("continue_recording"), style: .default, handler: { _ in onContinue() })) - alert.addAction(UIAlertAction(title: L("stop_without_saving"), style: .default, handler: { _ in onStop() })) - alert.addAction(UIAlertAction(title: L("save"), style: .cancel, handler: { _ in onSave() })) - UIViewController.topViewController().present(alert, animated: true) + DispatchQueue.main.async { + completion?(.error(error)) + } } } // MARK: - TrackRecordingObserver -extension TrackRecordingManager: TrackRecordingObserver { +extension TrackRecordingManager: TrackRecordingObservable { @objc func addObserver(_ observer: AnyObject, recordingIsActiveDidChangeHandler: @escaping TrackRecordingStateHandler) { + guard !observations.contains(where: { $0.observer === observer }) else { return } let observation = Observation(observer: observer, recordingStateDidChangeHandler: recordingIsActiveDidChangeHandler) observations.append(observation) recordingIsActiveDidChangeHandler(recordingState, trackRecordingInfo) @@ -209,6 +207,11 @@ extension TrackRecordingManager: TrackRecordingObserver { observations.removeAll { $0.observer === observer } } + @objc + func contains(_ observer: AnyObject) -> Bool { + observations.contains { $0.observer === observer } + } + private func notifyObservers() { observations = observations.filter { $0.observer != nil } observations.forEach { $0.recordingStateDidChangeHandler?(recordingState, trackRecordingInfo) } diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index bb2e8161b3..d316324134 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -242,6 +242,16 @@ extension PlacePageInteractor: ActionBarViewControllerDelegate { // TODO: 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 + TrackRecordingManager.shared.processAction(.stopAndSave(name: "")) { [weak self] result in + switch result { + case .success: + break + case .error: + self?.presenter?.closeAnimated() + } + } @unknown default: fatalError() } -- 2.45.3 From 29ee0da1b0467e2ff488051bf07b52d4a41547ef Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 16:46:22 +0400 Subject: [PATCH 04/10] [ios] implement PP for the track recording Signed-off-by: Kiryl Kaveryn --- .../TrackRecordingViewController.swift | 16 +-- iphone/Maps/Classes/MapViewController.h | 1 + iphone/Maps/Classes/MapViewController.mm | 24 ++++- iphone/Maps/Core/Theme/GlobalStyleSheet.swift | 1 + .../Contents.json | 15 +++ .../ic_track_save.png | Bin 0 -> 4317 bytes iphone/Maps/Maps.xcodeproj/project.pbxproj | 4 + .../Menu/BottomMenuInteractor.swift | 9 +- .../Components/ActionBarViewController.swift | 38 +++++--- .../PlacePageHeaderPresenter.swift | 2 - .../PlacePageHeaderViewController.swift | 10 -- .../Maps/UI/PlacePage/PlacePageBuilder.swift | 11 +-- .../UI/PlacePage/PlacePageInteractor.swift | 10 +- .../ActionBar/MWMActionBarButton.h | 1 + .../ActionBar/MWMActionBarButton.m | 8 +- .../PlacePageTrackRecordingLayout.swift | 92 ++++++++++++++++++ 16 files changed, 193 insertions(+), 49 deletions(-) create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json create mode 100644 iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png create mode 100644 iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift index a4385108aa..c11735f501 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift @@ -50,6 +50,11 @@ final class TrackRecordingViewController: MWMViewController { // MARK: - Public methods + @objc + func setHidden(_ hidden: Bool) { + button.isHidden = hidden + } + @objc func close(completion: @escaping (() -> Void)) { stopTimer() @@ -75,7 +80,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 } @@ -134,12 +139,7 @@ final class TrackRecordingViewController: MWMViewController { // MARK: - Actions @objc - private func onTrackRecordingButtonPressed(_ sender: Any) { - switch trackRecordingManager.recordingState { - case .inactive: - trackRecordingManager.processAction(.start) - case .active: - trackRecordingManager.processAction(.stop) - } + private func didTap(_ sender: Any) { + MapViewController.shared()?.showTrackRecordingPlacePage() } } diff --git a/iphone/Maps/Classes/MapViewController.h b/iphone/Maps/Classes/MapViewController.h index c6c1a3e3d5..e45d05b848 100644 --- a/iphone/Maps/Classes/MapViewController.h +++ b/iphone/Maps/Classes/MapViewController.h @@ -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; diff --git a/iphone/Maps/Classes/MapViewController.mm b/iphone/Maps/Classes/MapViewController.mm index 37944d4297..f07c0d3326 100644 --- a/iphone/Maps/Classes/MapViewController.mm +++ b/iphone/Maps/Classes/MapViewController.mm @@ -76,6 +76,7 @@ NSString *const kSettingsSegue = @"Map2Settings"; @property(nonatomic, readwrite) MWMMapViewControlsManager *controlsManager; @property(nonatomic, readwrite) SearchOnMapManager *searchManager; +@property(nonatomic, readwrite) TrackRecordingManager *trackRecordingManager; @property(nonatomic) BOOL disableStandbyOnLocationStateMode; @@ -119,9 +120,15 @@ NSString *const kSettingsSegue = @"Map2Settings"; #pragma mark - Map Navigation - (void)showTrackRecordingPlacePage { + if ([self.trackRecordingManager contains:self]) { + [self dismissPlacePage]; + return; + } __block PlacePageData * placePageData = [[PlacePageData alloc] initWithTrackInfo:TrackRecordingManager.shared.trackRecordingInfo elevationInfo:[MWMFrameworkHelper trackRecordingElevationInfo]]; - [TrackRecordingManager.shared addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * _Nonnull trackInfo) { + __weak __typeof(self) weakSelf = self; + [self.trackRecordingManager addObserver:self recordingIsActiveDidChangeHandler:^(TrackRecordingState state, TrackInfo * _Nonnull trackInfo) { + __strong __typeof(weakSelf) self = weakSelf; switch (state) { case TrackRecordingStateInactive: [self stopObservingTrackRecordingUpdates]; @@ -129,15 +136,19 @@ NSString *const kSettingsSegue = @"Map2Settings"; case TrackRecordingStateActive: if (UIApplication.sharedApplication.applicationState != UIApplicationStateActive) return; + [self.controlsManager.trackRecordingButton setHidden:YES]; [placePageData updateWithTrackInfo:trackInfo elevationInfo:[MWMFrameworkHelper trackRecordingElevationInfo]]; break; } }]; + [self.controlsManager.trackRecordingButton setHidden:YES]; [self showOrUpdatePlacePage:placePageData]; } - (void)stopObservingTrackRecordingUpdates { - [TrackRecordingManager.shared removeObserver:self]; + [self.trackRecordingManager removeObserver:self]; + if (self.trackRecordingManager.isActive) + [self.controlsManager.trackRecordingButton setHidden:NO]; } - (void)showOrUpdatePlacePage:(PlacePageData *)data { @@ -146,9 +157,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]; } @@ -754,6 +766,12 @@ NSString *const kSettingsSegue = @"Map2Settings"; return _searchManager; } +- (TrackRecordingManager *)trackRecordingManager { + if (!_trackRecordingManager) + _trackRecordingManager = TrackRecordingManager.shared; + return _trackRecordingManager; +} + - (UIView * _Nullable)searchViewContainer { return self.searchManager.viewController.view; } diff --git a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift index 60590d985b..e5bf69bc67 100644 --- a/iphone/Maps/Core/Theme/GlobalStyleSheet.swift +++ b/iphone/Maps/Core/Theme/GlobalStyleSheet.swift @@ -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 diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json new file mode 100644 index 0000000000..4c2879a332 --- /dev/null +++ b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "ic_track_save.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png b/iphone/Maps/Images.xcassets/Place Page/ic_placepage_save_track_recording.imageset/ic_track_save.png new file mode 100644 index 0000000000000000000000000000000000000000..d0bee6031e9efa62c6d8111eb1f6bedd96fdb328 GIT binary patch literal 4317 zcma)fXH*kR(>6s^5Tr`qFIoO2Q7#J8hOzs-k&_~)o zIe(Vk->)6@p%1KqcO8Qn7$h|Q38P{Db$2?*7;Iy#$AFj=*`_OJeD0XvVPI&?yg>6{ zVPN12GBLPg8_xI(n;t4W%r``+6%pWqG}QD2ovUTrv*l`KTIAzGT(M#ClFM>te8cw0 zz9EpsMvl@{?r6Z3hTIqpg`=~?e@lJk|xtGDX>uw(^$0k8eK2yr=VvSMfRb7~lC-Gs%(!zn6IW`uud^^&5@0O- zD0>&fKh*xdOsQ#YoaHrIiAhJFh}}me;o`fJpU9&89ZZN_=P92@ERlmxGJe}WGDnKW zI28XsM<8I~R(d(*?W3P>4|2X@#saFye4>n{*&jpQ9G!vo(_`JJcA7UmtB7qy9f3W0 z`Go}`1iS>%>5COt;AYZM0y+pSJ5oqdr>6P{u!td&sk_TYQocjc;kbP zcE02Vw)c`N%rl$iwfGl14Z$*f$5d;WDA=5DALZ}LxHgL}ukHVM``6Fq+cFvCWJ_f1 zJH?;B(K(jd*D+{Kn>T6HY}UwqpI z%cH6uEz!uxpvxY$BXzI zm>OSc^iys{H-~R0x=1F%!v3BVQ&pcq{JR#Zt~wT&x$t96SI$ifPxE?J2WR~A@HfGg zU#`Z1*R-@5f%DSA_tfxMS zU^b|X6?8$;D!!Eow?wylgo$2}GaT7^zngHP33H$3Xxe44mJKozpdtu?z1yVVbJaKGJfCcg_6UPJCIo9<2DdxnnZ-O2ttMZ#SyWQvwXJch z;GPerN3v1y<%wIeZb7R~&*Tvu3+o}4AG+9F<7s(wJ54J~2d74i(2;8V%{d?EI&%xp53YRQb@HdJ|q zC#^KF>DxyOpFf^yJBBHD&e&zl(8zU0vBQth52bV_!vD$^qhT3-*#CLA9S zaRZa1!~`W6;8@_2O_(?z56yS^ZPB&9%?)_EPBYFZ`9?O<#ew*xj#3{PLDJx z$gbr~P^&=M8DWZX3x8(<3+`%Yrqd{dw;t1F#q+L3N*Z@zkj2bZgWrBUGk=J@nM-k9eRzVoh}U<)BrY3$5RB-3G-qoia>pAVuh& zX>C<6qWc=z8M!wY4tqtc(uHDs5~!-6$C zaqt4RzHjE=SFga*5uY@gcDP45hd~?QKRkyOz&zx-Sr4rDVPOW!^`zsWyHIo66-W;J z>O?Ou>d*q##gGQxpjKgL;@f`>L&39XZbc}gv+k=ng4b%o+BNJBL8k-0CFid>Tl5*f@?HK1UU|}If-q1+A>ppi=-tn6(D``(3`f&Ej){D>BX* zM;-sw$M{VHha;`{lpfl?O0!H()8z#$wBRj%j28=E283rk@?qx%(oLYVBbkn#6x_h= zmQ-o+gHW6xX6>w{*2ge5@p;y|(*BJajRnvHi`BU!_ZemI^B8yq98FVR@sSS*cz&;tuz5Pm zW^_T^D1hMCQf{P*+Bj7?Wo%Z9kHX~ez3TkqL34j>Fb@1cA8I9=YMy z+SQ$Fs&9fm(6tISN|xuy+Hg6m-F>~ZSk9lo{2@2~$^LLZ_D!e=n0&7G45PSF%{;M_ zWle4WUg6zn6{gc%cOXfmVGa=ccxchjHnr2_s#Bi{^^&`PDkAaAv}~tX%!Vb1)rrDT z^$XFc{!n|MNFxY4|MAM8Z}}c!zN*R{a-I^nMrQ4 zv+1~#K1^%Jn^g&LwbXU@PUsGMfj)^@(y3{*po+X5w8>GUkJ8+9OtIAMLTycy7D8f{ z5TP+eh!2Fnb8^*o*OvKSy}`>T#k}lJsZsm!lzZQPdh@{xfP`T{bpJ%NU04TRZ37lk zqZx7?HfCOJ4Hc3<^S-CD+^LK>M9t08L(f{nvp8w$V=+Zj^K0|6LEF{yGRp^0)4M1O zRx@Li5!DXnBKW;hIQP|)Y?@JtNC-&NdppyLxw1Uprspdg6<*hgwV1hD(U3*ki@fJ6 zpDS4h+B$^O#-^`P1#t_`4ZQYW4Y}>*LpoB?3lIC6dI<~tk?Be%+tpd?(jwUG8DC&^ zJE0vzNF4{;g+-_!&9*J#`96*6vx4N;Q`lOnHJ63ItLiXJnU=pmRPZ1`GEkkk;J4e8 zw?5B`uaq#E^*?-dJg-tUP|Ifu;|kfu5Kbn%nmyQKz*p~mAJnF9ES(C#X}mPq;BC7I zIs9cO3iA+XBjGCB5P@;(5TcC2H+G;?oTz}_DA5++3Q9GR|D=J(%y@7b40VHp?iGfV zD0FQ!5qb7g@*K3}lEoD=eqV$thB^6@-!0ifmLyg<HJS*MC}R^#Ov*;0{4O*nAL~YwoDk~zYR$5O=XNz5T0TfrgsI8Jtf&Ek=Sy7Z3!Lr^Z z#K6Jh_3Nx)F_JlFsV@QBM#YvR0X^raAr^>R&QHq%Wc}rVkBri5af1FaH&^)AnMb2! z&~q^TB#+1jd$xt+A8pIWA(_}K(Is7(31nMzBW=ukx(N+2FMe)(?WPjrdRbD$9-qu= zMIYoBCXB^pd2FuO*omkg=i9W+-u@nCzmk|U=8n?Cv=Hmr?;7eXoO217MicPGeXWbn zv4!=ZN73?@tg;p5xZW<+t@u=3Lko?M!vEk`oq3EOWu986LwxY6>X!kbGOO$6Ao-gy zxUw5pSOz~<&x*I%SC6 zzfpEh@>AcB#4N@K`GS2gC%$BUH-B8?q-b!I3N$V@Q{J0rWN8-bi-F{)UI`%akzay?73j$7Tko(X46iSWS03jZkYw3dR4i4Gd+*P#Y!;6qBfirsr{et_-;aqEGmVK*G;gEoH_W94l{ zn0cQ`nImy?P`?crT(7`m!8GwYAmW^&x{TjUG91O&S~R#syM|8YzV=V`$Ove sN)9vU4sN=}B%`JO*AM^y3kw`LWe})oNuvB#=KZ(8#L&V3q30g|e{aZt7XSbN literal 0 HcmV?d00001 diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 1023b1e8e8..5bfc262864 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -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 */; }; @@ -1490,6 +1491,7 @@ ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SetFontStyle.swift"; sourceTree = ""; }; ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = ""; }; ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = ""; }; + ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacePageTrackRecordingLayout.swift; sourceTree = ""; }; EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = ""; }; EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LocationServicesDisabledAlert.xib; sourceTree = ""; }; EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServicesDisabledAlert.swift; sourceTree = ""; }; @@ -3031,6 +3033,7 @@ 99C6532123F2F506004322F3 /* IPlacePageLayout.swift */, 99F3EB0223F4178200C713F8 /* PlacePageCommonLayout.swift */, 993DF0B423F6B2EF00AC231A /* PlacePageTrackLayout.swift */, + ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */, ); path = Layouts; sourceTree = ""; @@ -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 */, diff --git a/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift b/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift index 8457445c3c..d761d01e18 100644 --- a/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift +++ b/iphone/Maps/UI/BottomMenu/Menu/BottomMenuInteractor.swift @@ -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() } } diff --git a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift index 1954964d90..f88c528079 100644 --- a/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/ActionBarViewController.swift @@ -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() { diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift index 8278ae5e67..daf2b7f323 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderPresenter.swift @@ -51,8 +51,6 @@ extension PlacePageHeaderPresenter: PlacePageHeaderPresenterProtocol { view?.isExpandViewHidden = true view?.isShadowViewHidden = false } - // TODO: (KK) Enable share button for the tracks to share the whole track gpx/kml - view?.isShareButtonHidden = false } func onClosePress() { diff --git a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift index 04c05e7a76..b77bfda011 100644 --- a/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift +++ b/iphone/Maps/UI/PlacePage/Components/PlacePageHeader/PlacePageHeaderViewController.swift @@ -2,7 +2,6 @@ protocol PlacePageHeaderViewProtocol: AnyObject { var presenter: PlacePageHeaderPresenterProtocol? { get set } var isExpandViewHidden: Bool { get set } var isShadowViewHidden: Bool { get set } - var isShareButtonHidden: Bool { get set } func setTitle(_ title: String?, secondaryTitle: String?) func showShareTrackMenu() @@ -78,15 +77,6 @@ extension PlacePageHeaderViewController: PlacePageHeaderViewProtocol { } } - var isShareButtonHidden: Bool { - get { - shareButton.isHidden - } - set { - shareButton.isHidden = newValue - } - } - func setTitle(_ title: String?, secondaryTitle: String?) { titleText = title secondaryText = secondaryTitle diff --git a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift index cac8307f61..8502a0d496 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageBuilder.swift @@ -15,8 +15,7 @@ case .track: layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data) case .trackRecording: - // TODO: Implement PlacePageTrackRecordingLayout - fatalError("PlacePageTrackRecordingLayout is not implemented") + layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data) @unknown default: fatalError() } @@ -34,14 +33,14 @@ data: data, mapViewController: MapViewController.shared()!) let layout: IPlacePageLayout + let storyboard = viewController.storyboard! switch data.objectType { case .POI, .bookmark: - layout = PlacePageCommonLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data) + layout = PlacePageCommonLayout(interactor: interactor, storyboard: storyboard, data: data) case .track: - layout = PlacePageTrackLayout(interactor: interactor, storyboard: viewController.storyboard!, data: data) + layout = PlacePageTrackLayout(interactor: interactor, storyboard: storyboard, data: data) case .trackRecording: - // TODO: Implement PlacePageTrackRecordingLayout - fatalError("PlacePageTrackRecordingLayout is not implemented") + layout = PlacePageTrackRecordingLayout(interactor: interactor, storyboard: storyboard, data: data) @unknown default: fatalError() } diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index d316324134..a685ff2d91 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -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) } } diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h index a82a84c698..d8c5f33ab1 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.h @@ -3,6 +3,7 @@ typedef NS_ENUM(NSInteger, MWMActionBarButtonType) { MWMActionBarButtonTypeBookingSearch, MWMActionBarButtonTypeBookmark, MWMActionBarButtonTypeTrack, + MWMActionBarButtonTypeSaveTrackRecording, MWMActionBarButtonTypeCall, MWMActionBarButtonTypeDownload, MWMActionBarButtonTypeMore, diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m index 0a7401bafa..dd38a97b9d 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/ActionBar/MWMActionBarButton.m @@ -19,6 +19,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) { case MWMActionBarButtonTypeBookmark: case MWMActionBarButtonTypeTrack: return L(isSelected ? @"delete" : @"save"); + case MWMActionBarButtonTypeSaveTrackRecording: + return L(@"save"); case MWMActionBarButtonTypeRouteFrom: return L(@"p2p_from_here"); case MWMActionBarButtonTypeRouteTo: @@ -55,7 +57,8 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) { self.label.text = titleForButton(self.type, isSelected); self.extraBackground.hidden = YES; self.button.coloring = MWMButtonColoringBlack; - + [self.button.imageView setContentMode:UIViewContentModeScaleAspectFit]; + switch (self.type) { case MWMActionBarButtonTypeDownload: { if (self.mapDownloadProgress) @@ -108,6 +111,9 @@ NSString *titleForButton(MWMActionBarButtonType type, BOOL isSelected) { [self.button setImage:[[UIImage imageNamed:@"ic_route_manager_trash"] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] forState:UIControlStateNormal]; self.button.coloring = MWMButtonColoringRed; break; + case MWMActionBarButtonTypeSaveTrackRecording: + [self.button setImage:[UIImage imageNamed:@"ic_placepage_save_track_recording"] forState:UIControlStateNormal]; + break; case MWMActionBarButtonTypeRouteFrom: [self.button setImage:[UIImage imageNamed:@"ic_route_from"] forState:UIControlStateNormal]; break; diff --git a/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift new file mode 100644 index 0000000000..5dba5ce430 --- /dev/null +++ b/iphone/Maps/UI/PlacePage/PlacePageLayout/Layouts/PlacePageTrackRecordingLayout.swift @@ -0,0 +1,92 @@ +final class PlacePageTrackRecordingLayout: IPlacePageLayout { + private var placePageData: PlacePageData + private var interactor: PlacePageInteractor + private let storyboard: UIStoryboard + weak var presenter: PlacePagePresenterProtocol? + + lazy var bodyViewControllers: [UIViewController] = { + return configureViewControllers() + }() + + var actionBar: ActionBarViewController? { + actionBarViewController + } + + var navigationBar: UIViewController? { + placePageNavigationViewController + } + + lazy var headerViewControllers: [UIViewController] = { + [headerViewController] + }() + + lazy var headerViewController: PlacePageHeaderViewController = { + return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .flexible) + }() + + lazy var placePageNavigationViewController: PlacePageHeaderViewController = { + return PlacePageHeaderBuilder.build(data: placePageData, delegate: interactor, headerType: .fixed) + }() + + lazy var editTrackViewController: PlacePageEditBookmarkOrTrackViewController = { + let vc = storyboard.instantiateViewController(ofType: PlacePageEditBookmarkOrTrackViewController.self) + vc.view.isHidden = true + vc.delegate = interactor + return vc + }() + + lazy var elevationProfileViewController: ElevationProfileViewController? = { + guard let trackData = placePageData.trackData else { + return nil + } + return ElevationProfileBuilder.build(trackInfo: trackData.trackInfo, + elevationProfileData: trackData.elevationProfileData, + delegate: interactor) + }() + + lazy var actionBarViewController: ActionBarViewController = { + let vc = storyboard.instantiateViewController(ofType: ActionBarViewController.self) + vc.placePageData = placePageData + vc.canAddStop = MWMRouter.canAddIntermediatePoint() + vc.isRoutePlanning = MWMNavigationDashboardManager.shared().state != .hidden + vc.delegate = interactor + return vc + }() + + init(interactor: PlacePageInteractor, storyboard: UIStoryboard, data: PlacePageData) { + self.interactor = interactor + self.storyboard = storyboard + self.placePageData = data + } + + private func configureViewControllers() -> [UIViewController] { + var viewControllers = [UIViewController]() + + if let elevationProfileViewController { + viewControllers.append(elevationProfileViewController) + } + + placePageData.onTrackRecordingProgressUpdate = { [weak self] in + self?.updateTrackRecordingRelatedSections() + } + + return viewControllers + } + + func calculateSteps(inScrollView scrollView: UIScrollView, compact: Bool) -> [PlacePageState] { + var steps: [PlacePageState] = [] + let scrollHeight = scrollView.height + steps.append(.closed(-scrollHeight)) + steps.append(.full(0)) + return steps + } +} + +private extension PlacePageTrackRecordingLayout { + func updateTrackRecordingRelatedSections() { + guard let elevationProfileViewController, let trackInfo = placePageData.trackData?.trackInfo else { return } + headerViewController.setTitle(placePageData.previewData.title, secondaryTitle: nil) + elevationProfileViewController.presenter?.update(trackInfo: trackInfo, profileData: placePageData.trackData?.elevationProfileData) + presenter?.layoutIfNeeded() + } +} -- 2.45.3 From c80cf0b98b8176af1fe64261961155c0a3bc9741 Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 16:57:11 +0400 Subject: [PATCH 05/10] [ios] refactor ElevationProfileViewController to handle live ele info updates 1. remove a stroryboard and implement VC and ElevationProfileDescriptionCell programmatically 2. move the description collection view over the chart 3. remove some unused code 4. add isChartViewInfoHidden to show/hide the info view and enable/disable user interation (will be used for the track recording) Signed-off-by: Kiryl Kaveryn --- .../ElevationProfile/ElevationProfilePresenter.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift index 585ae633a5..d4540645f7 100644 --- a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift +++ b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfilePresenter.swift @@ -85,6 +85,11 @@ extension ElevationProfilePresenter: ElevationProfilePresenterProtocol { view?.setChartData(ChartPresentationData(chartData, formatter: formatter)) view?.reloadDescription() + guard !profileData.isTrackRecording else { + view?.isChartViewInfoHidden = true + return + } + view?.setActivePoint(profileData.activePoint) view?.setMyPosition(profileData.myPosition) bookmarkManager.setElevationActivePointChanged(profileData.trackId) { [weak self] distance in -- 2.45.3 From b81489a0049fed989134ab7cdf574340c15394f5 Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Thu, 9 Jan 2025 15:38:51 +0400 Subject: [PATCH 06/10] [ios] decrease minimum altitude step to 25 to show ele info for flat areas Signed-off-by: Kiryl Kaveryn --- .../Components/ElevationProfile/ElevationProfileFormatter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift index 4a17c5c4c5..a6dbab87a8 100644 --- a/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift +++ b/iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationProfileFormatter.swift @@ -5,7 +5,7 @@ final class ElevationProfileFormatter { private enum Constants { static let metricToImperialMultiplier: CGFloat = 0.3048 - static var metricAltitudeStep: CGFloat = 50 + static var metricAltitudeStep: CGFloat = 25 static var imperialAltitudeStep: CGFloat = 100 } -- 2.45.3 From b1ffab6a7ee1f88825fb481c3cfbc787a030dd22 Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Fri, 10 Jan 2025 15:15:57 +0400 Subject: [PATCH 07/10] [ios] fix bm/ track/track recording updates handling on the PP Signed-off-by: Kiryl Kaveryn --- .../UI/PlacePage/PlacePageInteractor.swift | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift index a685ff2d91..28a1ebb2a1 100644 --- a/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift +++ b/iphone/Maps/UI/PlacePage/PlacePageInteractor.swift @@ -24,14 +24,29 @@ 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)*/ - guard isBookmark || isTrack else { - presenter?.closeAnimated() - return + func updatePlacePage() { + FrameworkHelper.updatePlacePageData() + placePageData.updateBookmarkStatus() + } + + switch placePageData.objectType { + case .POI, .trackRecording: + break + case .bookmark: + guard bookmarksManager.hasBookmark(placePageData.bookmarkData!.bookmarkId) else { + presenter?.closeAnimated() + return + } + updatePlacePage() + case .track: + guard bookmarksManager.hasTrack(placePageData.trackData!.trackId) else { + presenter?.closeAnimated() + return + } + updatePlacePage() + @unknown default: + fatalError("Unknown object type") } - FrameworkHelper.updatePlacePageData() - placePageData.updateBookmarkStatus() } private func addToBookmarksManagerObserverList() { @@ -311,6 +326,10 @@ extension PlacePageInteractor: PlacePageHeaderViewControllerDelegate { shareViewController.present(inParentViewController: mapViewController, anchorView: sourceView) case .track: presenter?.showShareTrackMenu() + case .trackRecording: + let currentLocation = LocationManager.lastLocation()?.coordinate ?? placePageData.locationCoordinate + let shareMyPositionViewController = ActivityViewController.share(forMyPosition: currentLocation) + shareMyPositionViewController.present(inParentViewController: mapViewController, anchorView: sourceView) default: fatalError() } -- 2.45.3 From 4193810f7c7a6e4b0263d1716f905e148aa8780c Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Wed, 26 Feb 2025 20:17:55 +0400 Subject: [PATCH 08/10] [ios] add TrackRecordingManager unit tests Signed-off-by: Kiryl Kaveryn --- iphone/Maps/Maps.xcodeproj/project.pbxproj | 12 ++ .../TrackRecordingManagerTests.swift | 198 ++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 5bfc262864..ac517746d6 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -525,6 +525,7 @@ 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 */; }; + ED9DDF9D2D6F6F7900645BC8 /* TrackRecordingManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED9DDF9B2D6F6DA300645BC8 /* TrackRecordingManagerTests.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 */; }; @@ -1492,6 +1493,7 @@ ED9857072C4ED02D00694F6C /* MailComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposer.swift; sourceTree = ""; }; ED99667D2B94FBC20083CE55 /* ColorPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPicker.swift; sourceTree = ""; }; ED9DDF872D6F151000645BC8 /* PlacePageTrackRecordingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlacePageTrackRecordingLayout.swift; sourceTree = ""; }; + ED9DDF9B2D6F6DA300645BC8 /* TrackRecordingManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingManagerTests.swift; sourceTree = ""; }; EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationProfileFormatter.swift; sourceTree = ""; }; EDBD68062B625724005DD151 /* LocationServicesDisabledAlert.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = LocationServicesDisabledAlert.xib; sourceTree = ""; }; EDBD680A2B62572E005DD151 /* LocationServicesDisabledAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServicesDisabledAlert.swift; sourceTree = ""; }; @@ -2767,6 +2769,7 @@ 4B4153B62BF9709100EE4B02 /* Core */ = { isa = PBXGroup; children = ( + ED9DDF982D6F6D8000645BC8 /* TrackRecorder */, EDF838AB2C00B9C7007E4E67 /* iCloudTests */, 4B4153B72BF970A000EE4B02 /* TextToSpeech */, ); @@ -3315,6 +3318,14 @@ path = ColorPicker; sourceTree = ""; }; + ED9DDF982D6F6D8000645BC8 /* TrackRecorder */ = { + isa = PBXGroup; + children = ( + ED9DDF9B2D6F6DA300645BC8 /* TrackRecordingManagerTests.swift */, + ); + path = TrackRecorder; + sourceTree = ""; + }; EDC4E3422C5D1BD3009286A2 /* RecentlyDeletedTests */ = { isa = PBXGroup; children = ( @@ -4911,6 +4922,7 @@ EDF838C32C00B9D6007E4E67 /* UbiquitousDirectoryMonitorDelegateMock.swift in Sources */, EDF838BE2C00B9D0007E4E67 /* LocalDirectoryMonitorDelegateMock.swift in Sources */, EDC4E3692C5E6F5B009286A2 /* MockRecentlyDeletedCategoriesManager.swift in Sources */, + ED9DDF9D2D6F6F7900645BC8 /* TrackRecordingManagerTests.swift in Sources */, EDF838BF2C00B9D0007E4E67 /* SynchronizationStateManagerTests.swift in Sources */, ED810EC52D566E9B00ECDE2C /* SearchOnMapTests.swift in Sources */, 4B83AE4B2C2E642100B0C3BC /* TTSTesterTest.m in Sources */, diff --git a/iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift b/iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift new file mode 100644 index 0000000000..ecb44380d2 --- /dev/null +++ b/iphone/Maps/Tests/Core/TrackRecorder/TrackRecordingManagerTests.swift @@ -0,0 +1,198 @@ +import XCTest +@testable import Organic_Maps__Debug_ + +final class TrackRecordingManagerTests: XCTestCase { + + private var trackRecordingManager: TrackRecordingManager! + + private var mockTrackRecorder: MockTrackRecorder.Type! + private var mockLocationService: MockLocationService.Type! + private var mockActivityManager: MockTrackRecordingActivityManager! + + override func setUp() { + super.setUp() + mockTrackRecorder = MockTrackRecorder.self + mockLocationService = MockLocationService.self + mockActivityManager = MockTrackRecordingActivityManager() + + trackRecordingManager = TrackRecordingManager( + trackRecorder: mockTrackRecorder, + locationService: mockLocationService, + activityManager: mockActivityManager + ) + } + + override func tearDown() { + trackRecordingManager = nil + mockTrackRecorder.reset() + mockLocationService.reset() + mockActivityManager = nil + super.tearDown() + } + + func test_GivenInitialSetup_WhenLocationEnabled_ThenStateIsInactive() { + mockLocationService.locationIsProhibited = false + mockTrackRecorder.trackRecordingIsEnabled = false + + trackRecordingManager.setup() + XCTAssertTrue(trackRecordingManager.recordingState == .inactive) + } + + func test_GivenInitialSetup_WhenLocationDisabled_ThenShouldHandleErrorAndIncativeState() { + mockLocationService.locationIsProhibited = true + + trackRecordingManager.setup() + + XCTAssertTrue(mockLocationService.checkLocationStatusCalled) + XCTAssertTrue(trackRecordingManager.recordingState == .inactive) + } + + func test_GivenStartRecording_WhenLocationEnabled_ThenSuccess() { + mockLocationService.locationIsProhibited = false + mockTrackRecorder.trackRecordingIsEnabled = false + + trackRecordingManager.processAction(.start) + + XCTAssertTrue(mockTrackRecorder.startTrackRecordingCalled) + XCTAssertTrue(mockActivityManager.startCalled) + XCTAssertTrue(trackRecordingManager.recordingState == .active) + } + + func test_GivenStartRecording_WhenLocationDisabled_ThenShouldFail() { + mockLocationService.locationIsProhibited = true + + trackRecordingManager.processAction(.start) { result in + switch result { + case .success: + XCTFail("Should not succeed") + case .error(let error): + switch error { + case .locationIsProhibited: + XCTAssertTrue(true) + default: + XCTFail("Unexpected error: \(error)") + } + } + } + XCTAssertFalse(self.mockTrackRecorder.startTrackRecordingCalled) + XCTAssertTrue(trackRecordingManager.recordingState == .inactive) + } + + func test_GivenStopRecording_WhenLocationEnabled_ThenSuccess() { + mockTrackRecorder.trackRecordingIsEnabled = true + mockTrackRecorder.trackRecordingIsEmpty = false + + trackRecordingManager.processAction(.stopAndSave(name: "Test Track")) { result in + switch result { + case .success: + XCTAssertTrue(true) + case .error(let error): + XCTFail("Unexpected error: \(error)") + } + } + XCTAssertTrue(mockTrackRecorder.stopTrackRecordingCalled) + XCTAssertTrue(mockTrackRecorder.saveTrackRecordingCalled) + XCTAssertTrue(mockActivityManager.stopCalled) + XCTAssertTrue(trackRecordingManager.recordingState == .inactive) + } + + func test_GivenStopRecording_WhenTrackIsEmpty_ThenShouldFail() { + mockTrackRecorder.trackRecordingIsEnabled = true + mockTrackRecorder.trackRecordingIsEmpty = true + + trackRecordingManager.processAction(.stopAndSave(name: "Test Track")) { result in + switch result { + case .success: + XCTFail("Should not succeed") + case .error(let error): + switch error { + case .trackIsEmpty: + XCTAssertTrue(true) + default: + XCTFail("Unexpected error: \(error)") + } + } + } + XCTAssertFalse(mockTrackRecorder.saveTrackRecordingCalled) + XCTAssertTrue(trackRecordingManager.recordingState == .inactive) + } +} + +// MARK: - Mock Classes + +private final class MockTrackRecorder: TrackRecorder { + static var trackRecordingIsEnabled = false + static var trackRecordingIsEmpty = false + static var startTrackRecordingCalled = false + static var stopTrackRecordingCalled = false + static var saveTrackRecordingCalled = false + + static func reset() { + trackRecordingIsEnabled = false + trackRecordingIsEmpty = false + startTrackRecordingCalled = false + stopTrackRecordingCalled = false + saveTrackRecordingCalled = false + } + + static func isTrackRecordingEnabled() -> Bool { + return trackRecordingIsEnabled + } + + static func isTrackRecordingEmpty() -> Bool { + return trackRecordingIsEmpty + } + + static func startTrackRecording() { + startTrackRecordingCalled = true + trackRecordingIsEnabled = true + } + + static func stopTrackRecording() { + stopTrackRecordingCalled = true + trackRecordingIsEnabled = false + } + + static func saveTrackRecording(withName name: String) { + saveTrackRecordingCalled = true + } + + static func setTrackRecordingUpdateHandler(_ handler: ((TrackInfo) -> Void)?) {} + + static func trackRecordingElevationInfo() -> ElevationProfileData { + ElevationProfileData() + } +} + +private final class MockLocationService: LocationService { + static var locationIsProhibited = false + static var checkLocationStatusCalled = false + + static func reset() { + locationIsProhibited = false + checkLocationStatusCalled = false + } + + static func isLocationProhibited() -> Bool { + return locationIsProhibited + } + + static func checkLocationStatus() { + checkLocationStatusCalled = true + } +} + +final class MockTrackRecordingActivityManager: TrackRecordingActivityManager { + var startCalled = false + var stopCalled = false + + func start(with info: TrackInfo) throws { + startCalled = true + } + + func stop() { + stopCalled = true + } + + func update(_ info: TrackInfo) {} +} -- 2.45.3 From dba6d303377fcdbcaeea360a8b04dc0dacd6236a Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Mon, 3 Mar 2025 18:13:01 +0400 Subject: [PATCH 09/10] [ios] rename TrackRecordingViewController to TrackRecordingButtonViewController because it holds only the 1 button Signed-off-by: Kiryl Kaveryn --- .../MapViewControls/MWMMapViewControlsManager.h | 4 ++-- .../MapViewControls/MWMMapViewControlsManager.mm | 2 +- ...ler.swift => TrackRecordingButtonViewController.swift} | 2 +- iphone/Maps/Maps.xcodeproj/project.pbxproj | 8 ++++---- .../Maps/UI/AvailableArea/TrackRecordingButtonArea.swift | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) rename iphone/Maps/Classes/CustomViews/MapViewControls/{TrackRecordingViewController.swift => TrackRecordingButtonViewController.swift} (98%) diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h index b57d9bef95..bca62c672d 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h @@ -4,7 +4,7 @@ @class MapViewController; @class BottomTabBarViewController; -@class TrackRecordingViewController; +@class TrackRecordingButtonViewController; @protocol MWMFeatureHolder; @@ -20,7 +20,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; diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm index 8357f5e63e..5cce5b7fc5 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm @@ -288,7 +288,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; _trackRecordingButton = nil; } else if (!trackRecordingButtonHidden && !_trackRecordingButton) { - _trackRecordingButton = [[TrackRecordingViewController alloc] init]; + _trackRecordingButton = [[TrackRecordingButtonViewController alloc] init]; [MWMMapWidgetsHelper updateLayoutForAvailableArea]; } } diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift similarity index 98% rename from iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift rename to iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift index c11735f501..7cf1a61f9c 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingViewController.swift +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/TrackRecordingButtonViewController.swift @@ -1,4 +1,4 @@ -final class TrackRecordingViewController: MWMViewController { +final class TrackRecordingButtonViewController: MWMViewController { private enum Constants { static let buttonDiameter = CGFloat(48) diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index ac517746d6..0cf4419213 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -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 */; }; @@ -1451,7 +1451,7 @@ ED46DDCF2D098A0B007CACD6 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; ED48BBB817C2B1E2003E7E92 /* CircleView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CircleView.h; sourceTree = ""; }; ED48BBB917C2B1E2003E7E92 /* CircleView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CircleView.m; sourceTree = ""; }; - ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingViewController.swift; sourceTree = ""; }; + ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackRecordingButtonViewController.swift; sourceTree = ""; }; ED4DC7732CAEDECC0029B338 /* ProductButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductButton.swift; sourceTree = ""; }; ED4DC7742CAEDECC0029B338 /* ProductsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewController.swift; sourceTree = ""; }; ED4DC7752CAEDECC0029B338 /* ProductsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductsViewModel.swift; sourceTree = ""; }; @@ -2573,7 +2573,7 @@ 34BC72091B0DECAE0012A34B /* MapViewControls */ = { isa = PBXGroup; children = ( - ED49D76F2CF0E3A8004AF27E /* TrackRecordingViewController.swift */, + ED49D76F2CF0E3A8004AF27E /* TrackRecordingButtonViewController.swift */, 340537621BBED98600D452C6 /* MWMMapViewControlsCommon.h */, 34BC72101B0DECAE0012A34B /* MWMMapViewControlsManager.h */, 34BC72111B0DECAE0012A34B /* MWMMapViewControlsManager.mm */, @@ -4503,7 +4503,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 */, diff --git a/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift b/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift index 5272dd4931..9f1ca5e4a2 100644 --- a/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift +++ b/iphone/Maps/UI/AvailableArea/TrackRecordingButtonArea.swift @@ -10,7 +10,7 @@ final class TrackRecordingButtonArea: AvailableArea { } override func notifyObserver() { - TrackRecordingViewController.updateAvailableArea(areaFrame) + TrackRecordingButtonViewController.updateAvailableArea(areaFrame) } } -- 2.45.3 From 0435b6eb2a04bf39afba43b0224e1035281607d7 Mon Sep 17 00:00:00 2001 From: Kiryl Kaveryn Date: Tue, 4 Mar 2025 17:12:41 +0400 Subject: [PATCH 10/10] [ios] add icons to the live activity widget Signed-off-by: Kiryl Kaveryn --- .../TrackRecordingActivityManager.swift | 20 ++++++++---- iphone/Maps/Maps.xcodeproj/project.pbxproj | 4 +++ .../ElevationProfile/Contents.json | 6 ++++ .../ic_em_ascent_24.imageset/Ascent.png | Bin 0 -> 391 bytes .../ic_em_ascent_24.imageset/Ascent@2x.png | Bin 0 -> 711 bytes .../ic_em_ascent_24.imageset/Ascent@3x.png | Bin 0 -> 954 bytes .../ic_em_ascent_24.imageset/Contents.json | 26 +++++++++++++++ .../ic_em_descent_24.imageset/Contents.json | 26 +++++++++++++++ .../ic_em_descent_24.imageset/Descent.png | Bin 0 -> 427 bytes .../ic_em_descent_24.imageset/Descent@2x.png | Bin 0 -> 791 bytes .../ic_em_descent_24.imageset/Descent@3x.png | Bin 0 -> 1129 bytes .../Contents.json | 26 +++++++++++++++ .../maxAltitude.png | Bin 0 -> 296 bytes .../maxAltitude@2x.png | Bin 0 -> 489 bytes .../maxAltitude@3x.png | Bin 0 -> 627 bytes .../Contents.json | 26 +++++++++++++++ .../minAltitude.png | Bin 0 -> 291 bytes .../minAltitude@2x.png | Bin 0 -> 481 bytes .../minAltitude@3x.png | Bin 0 -> 664 bytes .../LiveActivity/StatisticDetailView.swift | 29 +++++++++-------- .../LiveActivity/StatisticValueView.swift | 8 ++--- ...TrackRecordingLiveActivityAttributes.swift | 22 ++++++++----- ...ckRecordingLiveActivityConfiguration.swift | 2 +- .../TrackRecordingLiveActivityView.swift | 30 ++++++++++-------- .../ElevationDescription.swift | 26 +++++++++++++++ .../ElevationProfilePresenter.swift | 14 +++++--- 26 files changed, 212 insertions(+), 53 deletions(-) create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/Contents.json create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Ascent.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Ascent@2x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Ascent@3x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Contents.json create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Contents.json create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent@2x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent@3x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/Contents.json create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude@2x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude@3x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/Contents.json create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude@2x.png create mode 100644 iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude@3x.png create mode 100644 iphone/Maps/UI/PlacePage/Components/ElevationProfile/ElevationDescription.swift diff --git a/iphone/Maps/Core/TrackRecorder/TrackRecordingActivityManager.swift b/iphone/Maps/Core/TrackRecorder/TrackRecordingActivityManager.swift index 5df98663b4..8c0bc77439 100644 --- a/iphone/Maps/Core/TrackRecorder/TrackRecordingActivityManager.swift +++ b/iphone/Maps/Core/TrackRecorder/TrackRecordingActivityManager.swift @@ -55,12 +55,20 @@ private extension TrackRecordingLiveActivityAttributes.ContentState { let ascent = AltitudeFormatter.altitudeString(fromMeters: Double(trackInfo.ascent)) let descent = AltitudeFormatter.altitudeString(fromMeters: Double(trackInfo.descent)) - self.distance = StatisticsViewModel(key: "", value: distance) - self.duration = StatisticsViewModel(key: "", value: duration) - self.maxElevation = StatisticsViewModel(key: L("elevation_profile_max_elevation"), value: maxElevation) - self.minElevation = StatisticsViewModel(key: L("elevation_profile_min_elevation"), value: minElevation) - self.ascent = StatisticsViewModel(key: L("elevation_profile_ascent"), value: ascent) - self.descent = StatisticsViewModel(key: L("elevation_profile_descent"), value: descent) + self.distance = ValueViewModel(value: distance) + self.duration = ValueViewModel(value: duration) + self.ascent = DetailViewModel(.ascent, value: ascent) + self.descent = DetailViewModel(.descent, value: descent) + self.minElevation = DetailViewModel(.minElevation, value: minElevation) + self.maxElevation = DetailViewModel(.maxElevation, value: maxElevation) + } +} + +private extension TrackRecordingLiveActivityAttributes.ContentState.DetailViewModel { + init(_ elevationDescription: ElevationDescription, value: String) { + self.value = value + self.key = elevationDescription.title + self.imageName = elevationDescription.imageName } } diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 0cf4419213..ca01ebe9a2 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -519,6 +519,7 @@ ED810EC52D566E9B00ECDE2C /* SearchOnMapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED810EC42D566E9B00ECDE2C /* SearchOnMapTests.swift */; }; ED8270F02C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */; }; ED83880F2D54DEB3002A0536 /* UIImage+FilledWithColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED83880E2D54DEA4002A0536 /* UIImage+FilledWithColor.swift */; }; + ED8A921C2D772904009E063B /* ElevationDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8A921B2D772904009E063B /* ElevationDescription.swift */; }; ED914AB22D35063A00973C45 /* TextColorStyleSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914AB12D35063A00973C45 /* TextColorStyleSheet.swift */; }; ED914AB82D351DF000973C45 /* StyleApplicable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914AB72D351DF000973C45 /* StyleApplicable.swift */; }; ED914ABE2D351FF800973C45 /* UILabel+SetFontStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */; }; @@ -1487,6 +1488,7 @@ ED810EC42D566E9B00ECDE2C /* SearchOnMapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchOnMapTests.swift; sourceTree = ""; }; ED8270EF2C2071A3005966DA /* SettingsTableViewDetailedSwitchCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewDetailedSwitchCell.swift; sourceTree = ""; }; ED83880E2D54DEA4002A0536 /* UIImage+FilledWithColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+FilledWithColor.swift"; sourceTree = ""; }; + ED8A921B2D772904009E063B /* ElevationDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElevationDescription.swift; sourceTree = ""; }; ED914AB12D35063A00973C45 /* TextColorStyleSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextColorStyleSheet.swift; sourceTree = ""; }; ED914AB72D351DF000973C45 /* StyleApplicable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleApplicable.swift; sourceTree = ""; }; ED914ABD2D351FF800973C45 /* UILabel+SetFontStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+SetFontStyle.swift"; sourceTree = ""; }; @@ -3056,6 +3058,7 @@ 99DEF9D523E420D2006BFD21 /* ElevationProfile */ = { isa = PBXGroup; children = ( + ED8A921B2D772904009E063B /* ElevationDescription.swift */, 99514BB223E82B450085D3A7 /* ElevationProfilePresenter.swift */, EDA1EAA32CC7ECAD00DBDCAA /* ElevationProfileFormatter.swift */, 99514BB423E82B450085D3A7 /* ElevationProfileViewController.swift */, @@ -4502,6 +4505,7 @@ 99F3EB1223F418C900C713F8 /* PlacePageInteractor.swift in Sources */, 340708651F2905A500029ECC /* NavigationInfoArea.swift in Sources */, 993DF0CC23F6BD0600AC231A /* ElevationDetailsPresenter.swift in Sources */, + ED8A921C2D772904009E063B /* ElevationDescription.swift in Sources */, 34AB666B1FC5AA330078E451 /* TransportTransitCell.swift in Sources */, ED2E32912D10501700807A08 /* TrackRecordingButtonViewController.swift in Sources */, 47E8163323B17734008FD836 /* MWMStorage+UI.m in Sources */, diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/Contents.json b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Ascent.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Ascent.png new file mode 100644 index 0000000000000000000000000000000000000000..9609ee75c3f9bf575733aea095b2f37c0e0964df GIT binary patch literal 391 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O(AGM+AuAr*{oC+i9wa^Pu|XANXoBqlR8Ld8Qz z==8)3bC^35*;K?h-7{1zJlfuv=PNnu~Rx5;xIcio=5D{9{0|LW;41QyNxwfeUCB-)x57{Z@i_x=~HNo=ki05 zd?G6!OsHkyKPuO9!Yj^mo0>&-=Xov5nYuD8t?9xwi<;E6s&9)PE7~`yF>&QL>&WW` zho2u_#r7+1=SJ~8t2g>iYdn8`wZsdxSF0lz$YdX8KCQ^?AE-66Qr&gqCrO`)`ENMi z9oyC68pb?#ucUQ+a%_R>e z^e4e{mZB1S+mXhc(f3w;4!u5>1EWOJ;VK#lIwt^C64c1K*QRirWRt7+Uu2RRKuoxMK|4jb3YJ~gI+PlK z+E!4we(B^0SCJ*?lmIy2TP4BLX(L=kmY`Dt*cAO;2v?CT=(+%uQ_Q|U=QkCuB3aO} z0A?jz#VQg;G!}qzo?DErg{!FDa;pGF(8BfS{H}znprBV`FIWLkJh9DMU&2*vA|hxj z096uK_$XYpFY21b$|n;QfW=YJR{NqpkRzSC5{IQTLjR=`VhY*{;5cYoE3VQ=pO}KS zPGp>Ac!=RG0<(S=JnwVbtb>XBaO0vavk}wJiH*jTuR}4 zOO)64qB5v9%j%X8j`w-F@u)fN!(Mguf`(5gEDUd?EZ>&VHlG*ZUSYzt*i%9tg*VK31DZKFs* zQhubJAO-Jh%o5CSmU#7UJmo<-u-i?QM-5|^Xkys41fnn+vwSIxmt>EpiDB0g=vZ+K zee66i-p80F_+C{z>>2{GH2RiSUt^Z2-eH#tq@7?%$DB*m4!cw!O?fbTLOo9nW0t69 z*d+q7FVal#L>O<8J)UZYT_TXCV}1|0v|9fVu434I1=5z*DllH#m?f$hb{~N_!MWnN zA&pt0*s%Kuq)A=gB^=tAC5j9?S0L>KPlfRzv&U0p*tr6g_B^ePS)#XL=Ln=V<{>)f zTzVU}R-l}YIm9l-@vzLYS7@{XX(lM$CESHEOFTg)8iBME9CP+~XxLd_R!Sfm+f(OujnY(>6sq$0y>)S)oF;BK{c12o9wRGH8lnL zj_D%qnvHo-E-Xi074BkM@QTb|1HlTYDG>SEeGpA=55w~(<5+*u;f9J048t%C!!QiP cFpSag7dESkS3J)M9RL6T07*qoM6N<$f;yR}_y7O^ literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Contents.json b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Contents.json new file mode 100644 index 0000000000..7d1b47c4be --- /dev/null +++ b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_ascent_24.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "Ascent.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Ascent@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Ascent@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Contents.json b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Contents.json new file mode 100644 index 0000000000..f730e3973a --- /dev/null +++ b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "Descent.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Descent@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Descent@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent.png new file mode 100644 index 0000000000000000000000000000000000000000..339cf081ec4d56a7823f04ca4ef71969f5fdbce8 GIT binary patch literal 427 zcmV;c0aX5pP)G%mq&v%i(t0*_OdQx&kt6L<}F!3lT* zHozm{e@q}&fdlW%{!!u=IFdD2(~M|DD`-omiZd$vUVvlpO3t}`zqFd%EM|~X*&ZJa z5gSmMISZZ2Z-qpz>J_{I|3WQl1PdJZqL{^ST}ox z9ry<=2Ts;7Gvbcw!w&qj%HYr#Y>FEyThbAusl)~JL(~74x?r(ya47qs72jh=FbC#$ VHJ~oBrhNba002ovPDHLkV1hw4spS9w literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent@2x.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_descent_24.imageset/Descent@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..317f0619c3296c057195a848dc147ff894b14242 GIT binary patch literal 791 zcmV+y1L*vTP)?0FLownzhVK)x?`TPz1Xv1h%78*_m7i0u zjiq#8^rLqEm-idfri8faY)AMFl^=urQuEA5sE7A;X*Qk#E|sq$eJM~TaN)IG!WQ%f z>IKaNKcdhVjWf%#Y&-);4gL4AIBK6@@4`dFm+%NO@~)Zud?RdOaP_^wEeiQd4Hh~zC)nN(xGs3^+eEDhZ6Z4HS zCIKg$E?5V+DXPn?@9Rp*(ggf|F$s*2h5_OQ?Zj^l97}KJE|x8$41N#kU{L-6j=`_P z9%;6P$|tIXSbwQT-+!hS^a;EXRlXLLx4F6mQU~wQf+l0cGx0A%38WBih>)O9BleXRv|k?Xy@2uj zL2+*SZFTnluUSR#4r|dkDXkX2gl1SB6SQPaJ;eGeuuoVvERR+%^qEX1lgVUO%^jp6 VXoFWgf;RvF002ovPDHLkV1h3IYE6Lica^pAmy7cOqNn47j;cd_E)vx{%&kc;Rak1@#_pDQjNqGS3d5JgxN`iCx7 z(IH(4#Ejs>4bYqogflD)xjl#v^3=r^CLXfp;--s37e4~m|AH>$JSP715gm|QAd0i( zwJo?Wi8(VlGKgcIz$P1|pwBZ=IYwujgX(zB;PGi>(XQtpw>u-nX_6l0FD zfw*Ds;tUPD%?wI0W(mo#xk0_Be4gJuLcPr_hc+jIYc9Sckb!Lp#E08LGq__va`83L z$M%%13Rl?sNP`*o1pjQ|{x^xuIFMUG7v%aEzOfF=TQR;jz@NkuA*9Y9}Ull4gZ5@ag(ZX(bo z@*;W3HuSYoNaU0%!KnPltODg4T4glDeg_?27D!tB)u}!*Gqml>sX|@d4C-2( z^7qCJJGJ_#ovCpe85FNQRwKebo3AYD^XA zti>sihDk~m#o%lh%%BvLJ_}=xaTT&*r&J$9jNmO03$zTPG4@5A0?Dvbs*fSYu?|h3 zHcJ_8A)RC}yI!F_qgaO|P#=srMtSk)!^K`5!#=foE+Yw)do$V;={#&3>5Qb#m`Rsw zw0)&m*MzdA$cU89=^F@z{!j+>d1(1a=ldzuNA@2{7lZ`rqcO+O3|rgGs$S&_l%vL+ z8|mm-sy;G%)I$K@gZMTUaQ@(njh%hcKp9xd~xv-;WM}j{<+X?oKKiRTs(%R z>td)eTL#l>j*!S4{vS>-!@unL9+a$X&kz@TP4ETc0!y13`$p{c literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/Contents.json b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/Contents.json new file mode 100644 index 0000000000..a44c9fc25d --- /dev/null +++ b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/Contents.json @@ -0,0 +1,26 @@ +{ + "images" : [ + { + "filename" : "maxAltitude.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "maxAltitude@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "maxAltitude@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude.png new file mode 100644 index 0000000000000000000000000000000000000000..d03e4554408d1bae9f7699e3c879d7f7fa694f55 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GG!XV7ZFl!D-1!HlL zyA#8@b22Z19F}xPUq=Rpjs4tz5?O)#y`C>IUpXNmMl*rQh&Eul-YKF+Jlp_D>*;y2vj#u zw3&YI#PTKI{8wJ+W8S*p+^r@vHr}nvmYMC#>f$Lm5q8y2=f1M6zOO+7)ecT-aD**FEPni?@hG&P2C+>OUTYK6-xfolf$r)&sX@c?2@s o9^A?+X|uxm%Zb10Rl82ICp{Bl`K<7&59kpFPgg&ebxsLQ0EH)Ux&QzG literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude@2x.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..15bffb8645d24a161892fbeda30f6e856f3d5155 GIT binary patch literal 489 zcmV0f`WarMI0&i?#|3QiZY)x zieitgdHeHry@2C5j&sbMB;?FO+=aNnxO|0p3Gq6Vbc#3&j9)yNplP^4QUp}kA3w2# zGsO80F~#^oSp0K{PdJX#OUfjc!ij&2W*zwmu|@$>7BUI+@KyUu=qQgUAfi+cZ~>}2 z|IgcagE1)!SuSk#*^cXTY>P6Xgn);Edp9t~S^!)Aa^OCD;0lIV3s@xNnC@&XCozjp zXRsdU3 zGnQ+}dhZ{N0FHT!alC}WRy6|XIu=e+A5It;Cu*}TQ7BGR)7NCb|?)!5EtWoIOwnOwZu1oH2l)K1MCv`0R3C#h3 f<2a7vI0wNWd!8c5rX{ss00000NkvXXu0mjf=iSIJ literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude@3x.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_max_attitude_24.imageset/maxAltitude@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..637945804afb6518c882711d9080d7efd9ef9774 GIT binary patch literal 627 zcmV-(0*w8MP)M%7G9^VWl{*HOiFzPpU=j^MzBq*prDVSr4}jN!3~IV$?axl_Kti% z7z`!>`JCIyZVn(KA|fIpB5xbWGYT~fwTpiE3N;ONgG5_MHwkqfY8(9_f9p|v{DuUZ zNOy+*8X!?lKx-u@pa=BVH1YLWYmi*SRjB+_bUOMO$1(gnH)Z#UA z{#5K>E@!iPiG8ose^E zEAMe1`y~RpMn3nk?;VhI!76J>4d*WnJOh$7M`Ils!*T3;1|(BUV-i(Jn}93v3aDwn zmCJ4Hdj+IDqNR&2;lLvx`Lt}Yu-0%G`yK&JU}p?t)&Z?L)|WXz#`oGJIHISFQYp4wK}LP-7}}wMVbM1%^?(M1@wXL zd4~ecfF98ua=Wj}KTUIye!KzO2IfqLf|GMZ2^!*<4{e z_vjSwWZt)k`%jGPeldQxNj7& zSO(sH@SFG8%g*O)E$?II32oh&%XDbt)0U<4oX!i2Zn!CZ(Y@qIMu+ZhHE+Eoo4zFZ i{#$Gto26c*#Jtx+!MQu>?0KLc7(8A5T-G@yGywp3C~esQ literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude@2x.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a3c35f9024bd9ba96cb34980f73c85cbb06abf75 GIT binary patch literal 481 zcmV<70UrK|P)N3?4w7yf#Q@vZ>v=5tEsw?ORPsg6mwl>gX;8U})s#zQ0 zGJm0;<@g=dtPRi{{oKLG)4=CgYKv4KF!D4&H>rN1^#q3l_;B!tsm`Fm!JFc9#k&IZ z3e75TwP@H8Jjt8jOMJd`V41m=2R;{jMZhub-YG0`=o4ux5a(Mb+W;evH{|4QHoXm)@h2( zoVo!j#p*4@RGWB;(a1x|Y4$o}Q#$+9V+AOWRq4i^@*L`G@r=vEKQ}iMs^>{Agq>Bh ziF^GMw+`c&&y-^H*2L%qHa~b;rC3o-1Ezn<#D;Yj)I5oQQk`J)jd1V}i04NLA%ucI Xz0pF3vn>}f00000NkvXXu0mjfzzx$* literal 0 HcmV?d00001 diff --git a/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude@3x.png b/iphone/Maps/OMapsWidgetExtension/Assets.xcassets/ElevationProfile/ic_em_min_attitude_24.imageset/minAltitude@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..153f73cffcbd08b9457ddde88f45cdd925cec96e GIT binary patch literal 664 zcmV;J0%!e+P) zqvwMH5oP4qT{V5D0TB@q5fKp)kq_ky?98QF9@iY&xKDMGYJdFoVBcV89iwo*qaD8X z-&B86ox#9Y*!Y!d5&c-e=x-X#GN9i4h5@U9uF;_bc(WO}DyrK(vK+%(m8qM?jb3ipIu#3S%At z(I(=VuO9LWh)ptb?3<1CoL4{;HTF%%dd~8&ntM$3kZK;jcHr3aJXa8Ln_a;VSC4sP zp5`48?ZD4AoJ9oWHTKzfnY5H;tD!JIk)rHEG+_}(mryB zps}8d8uPi*v44o5v7T$Sf{0`PJ&$KyPwQ`K84$P4?mK?2Svb@(AmX-}HTHSd3LWdY z)&bG1vET9ofETTr^t8$7&Kq{vd21HS`$0qf!zd))EqZI~iHL}N9@W#j_7_*t_PTGW z$@SpkCz>^H6xUL%pzU?vQj_b!RP_F;=BUxrb>C7;DAdDQtZOv^^>7cdR`H|WnjXYG y{1c<={y)%{r&qquXHSQz_8=l6A|fIp-FpTLa$@a+>I5DD0000 [DescriptionsViewModel] { [ - DescriptionsViewModel(title: L("elevation_profile_ascent"), value: trackInfo.ascent, imageName: "ic_em_ascent_24"), - DescriptionsViewModel(title: L("elevation_profile_descent"), value: trackInfo.descent, imageName: "ic_em_descent_24"), - DescriptionsViewModel(title: L("elevation_profile_max_elevation"), value: trackInfo.maxElevation, imageName: "ic_em_max_attitude_24"), - DescriptionsViewModel(title: L("elevation_profile_min_elevation"), value: trackInfo.minElevation, imageName: "ic_em_min_attitude_24") + DescriptionsViewModel(.ascent, value: trackInfo.ascent), + DescriptionsViewModel(.descent, value: trackInfo.descent), + DescriptionsViewModel(.minElevation, value: trackInfo.maxElevation), + DescriptionsViewModel(.maxElevation, value: trackInfo.minElevation) ] } -- 2.45.3