From bff4b2348a1007adf48fbc4e3b1aacd09559af25 Mon Sep 17 00:00:00 2001 From: Yannik Bloscheck Date: Wed, 6 Aug 2025 16:01:57 +0200 Subject: [PATCH] [ios] WIP: Switching main/map buttons to SwiftUI Signed-off-by: Yannik Bloscheck --- .../CoreApi/Traffic/MWMMapOverlayManager.mm | 4 + iphone/Maps/Bridging-Header.h | 1 + iphone/Maps/Bridging/BridgeControllers.swift | 7 + iphone/Maps/Bridging/Controls.h | 11 ++ iphone/Maps/Bridging/Controls.mm | 75 +++++++++ iphone/Maps/Bridging/Controls.swift | 12 ++ .../MWMMapViewControlsManager.h | 3 +- .../MWMMapViewControlsManager.mm | 22 ++- .../MWMTrafficButtonViewController.xib | 10 +- .../MWMNavigationDashboardManager.mm | 2 +- iphone/Maps/Classes/MapViewController.h | 1 - iphone/Maps/Classes/MapViewController.mm | 5 +- iphone/Maps/Classes/MapsAppDelegate.mm | 2 - .../Maps/Core/DeepLink/DeepLinkHandler.swift | 4 +- .../TrackRecorder/TrackRecordingManager.swift | 1 + .../en.lproj/Localizable.strings | 4 + iphone/Maps/Maps.xcodeproj/project.pbxproj | 104 +++++++----- .../Model/Settings Types/LeftButtonType.swift | 52 ------ iphone/Maps/Model/Settings.swift | 10 +- iphone/Maps/UI/AvailableArea/TabBarArea.swift | 4 - .../{TabBar => }/BottomTabBarButton.swift | 2 + .../BottomMenu/Menu/BottomMenuPresenter.swift | 10 +- .../TabBar/BottomTabBarBuilder.swift | 14 -- .../TabBar/BottomTabBarInteractor.swift | 75 --------- .../TabBar/BottomTabBarPresenter.swift | 37 ----- .../BottomMenu/TabBar/BottomTabBarView.swift | 28 ---- .../TabBar/BottomTabBarViewController.swift | 131 ---------------- .../TabBar/BottomTabBarViewController.xib | 148 ------------------ iphone/Maps/UI/ControlsView.swift | 144 +++++++++++++++++ iphone/Maps/UI/FloatingButtonStyle.swift | 39 +++++ iphone/Maps/UI/MainButton.swift | 79 ++++++++++ iphone/Maps/UI/MainButtonType.swift | 81 ++++++++++ iphone/Maps/UI/New Group/MapLayerButton.swift | 27 ++++ .../Maps/UI/New Group/MapPositionButton.swift | 39 +++++ .../UI/New Group/MapPositionButtonMode.swift | 48 ++++++ .../New Group/MapTrackRecordingButton.swift | 45 ++++++ iphone/Maps/UI/New Group/MapZoomButton.swift | 28 ++++ .../Maps/UI/New Group/MapZoomButtonKind.swift | 38 +++++ iphone/Maps/UI/Settings/SettingsView.swift | 14 +- 39 files changed, 791 insertions(+), 570 deletions(-) create mode 100644 iphone/Maps/Bridging/Controls.h create mode 100644 iphone/Maps/Bridging/Controls.mm create mode 100644 iphone/Maps/Bridging/Controls.swift delete mode 100644 iphone/Maps/Model/Settings Types/LeftButtonType.swift rename iphone/Maps/UI/BottomMenu/{TabBar => }/BottomTabBarButton.swift (94%) delete mode 100644 iphone/Maps/UI/BottomMenu/TabBar/BottomTabBarBuilder.swift delete mode 100644 iphone/Maps/UI/BottomMenu/TabBar/BottomTabBarInteractor.swift delete mode 100644 iphone/Maps/UI/BottomMenu/TabBar/BottomTabBarPresenter.swift delete mode 100644 iphone/Maps/UI/BottomMenu/TabBar/BottomTabBarView.swift delete mode 100644 iphone/Maps/UI/BottomMenu/TabBar/BottomTabBarViewController.swift delete mode 100644 iphone/Maps/UI/BottomMenu/TabBar/BottomTabBarViewController.xib create mode 100644 iphone/Maps/UI/ControlsView.swift create mode 100644 iphone/Maps/UI/FloatingButtonStyle.swift create mode 100644 iphone/Maps/UI/MainButton.swift create mode 100644 iphone/Maps/UI/MainButtonType.swift create mode 100644 iphone/Maps/UI/New Group/MapLayerButton.swift create mode 100644 iphone/Maps/UI/New Group/MapPositionButton.swift create mode 100644 iphone/Maps/UI/New Group/MapPositionButtonMode.swift create mode 100644 iphone/Maps/UI/New Group/MapTrackRecordingButton.swift create mode 100644 iphone/Maps/UI/New Group/MapZoomButton.swift create mode 100644 iphone/Maps/UI/New Group/MapZoomButtonKind.swift diff --git a/iphone/CoreApi/CoreApi/Traffic/MWMMapOverlayManager.mm b/iphone/CoreApi/CoreApi/Traffic/MWMMapOverlayManager.mm index 3b9d0bd9c..18ae853e3 100644 --- a/iphone/CoreApi/CoreApi/Traffic/MWMMapOverlayManager.mm +++ b/iphone/CoreApi/CoreApi/Traffic/MWMMapOverlayManager.mm @@ -29,6 +29,7 @@ static NSString *didChangeOutdoorMapStyle = @"didChangeOutdoorMapStyle"; if (self) { _observers = [NSHashTable weakObjectsHashTable]; GetFramework().GetTrafficManager().SetStateListener([self](TrafficManager::TrafficState state) { + [NSNotificationCenter.defaultCenter postNotificationName:@"LayersChanged" object:nil]; for (id observer in self.observers) { if ([observer respondsToSelector:@selector(onTrafficStateUpdated)]) { [observer onTrafficStateUpdated]; @@ -36,6 +37,7 @@ static NSString *didChangeOutdoorMapStyle = @"didChangeOutdoorMapStyle"; } }); GetFramework().GetTransitManager().SetStateListener([self](TransitReadManager::TransitSchemeState state) { + [NSNotificationCenter.defaultCenter postNotificationName:@"LayersChanged" object:nil]; for (id observer in self.observers) { if ([observer respondsToSelector:@selector(onTransitStateUpdated)]) { [observer onTransitStateUpdated]; @@ -43,6 +45,7 @@ static NSString *didChangeOutdoorMapStyle = @"didChangeOutdoorMapStyle"; } }); GetFramework().GetIsolinesManager().SetStateListener([self](IsolinesManager::IsolinesState state) { + [NSNotificationCenter.defaultCenter postNotificationName:@"LayersChanged" object:nil]; for (id observer in self.observers) { if ([observer respondsToSelector:@selector(onIsoLinesStateUpdated)]) { [observer onIsoLinesStateUpdated]; @@ -50,6 +53,7 @@ static NSString *didChangeOutdoorMapStyle = @"didChangeOutdoorMapStyle"; } }); [NSNotificationCenter.defaultCenter addObserverForName:didChangeOutdoorMapStyle object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { + [NSNotificationCenter.defaultCenter postNotificationName:@"LayersChanged" object:nil]; for (id observer in self.observers) { if ([observer respondsToSelector:@selector(onOutdoorStateUpdated)]) { [observer onOutdoorStateUpdated]; diff --git a/iphone/Maps/Bridging-Header.h b/iphone/Maps/Bridging-Header.h index e41abb02b..95dc1376f 100644 --- a/iphone/Maps/Bridging-Header.h +++ b/iphone/Maps/Bridging-Header.h @@ -10,6 +10,7 @@ #import +#import "Controls.h" #import "DeepLinkRouteStrategyAdapter.h" #import "EAGLView.h" #import "FirstSession.h" diff --git a/iphone/Maps/Bridging/BridgeControllers.swift b/iphone/Maps/Bridging/BridgeControllers.swift index e7ade0cd4..ffe67bdd9 100644 --- a/iphone/Maps/Bridging/BridgeControllers.swift +++ b/iphone/Maps/Bridging/BridgeControllers.swift @@ -16,6 +16,13 @@ import UIKit routinOptionsBridgeController.view.backgroundColor = .systemGroupedBackground return routinOptionsBridgeController } + + /// The `ControlsView` for presentation in an alert + @objc static func mapControls() -> UIViewController { + let controlsBridgeController = UIHostingController(rootView: ControlsView()) + controlsBridgeController.view.backgroundColor = .clear + return controlsBridgeController + } } diff --git a/iphone/Maps/Bridging/Controls.h b/iphone/Maps/Bridging/Controls.h new file mode 100644 index 000000000..76fec2e6f --- /dev/null +++ b/iphone/Maps/Bridging/Controls.h @@ -0,0 +1,11 @@ +NS_SWIFT_NAME(Controls) +@interface Controls : NSObject + ++ (void)zoomIn; ++ (void)zoomOut; ++ (void)switchToNextPositionMode; ++ (NSString *)positionModeRawValue; ++ (Controls *)shared; +- (bool)hasMainButtons; + +@end diff --git a/iphone/Maps/Bridging/Controls.mm b/iphone/Maps/Bridging/Controls.mm new file mode 100644 index 000000000..a89553e2a --- /dev/null +++ b/iphone/Maps/Bridging/Controls.mm @@ -0,0 +1,75 @@ +#import "Controls.h" +#import "SwiftBridge.h" + +#include + +@implementation Controls + +static Controls *shared = nil; + ++ (Controls *)shared +{ + if (shared == nil) + { + shared = [[super allocWithZone:NULL] init]; + } + + return shared; +} + ++ (id)allocWithZone:(NSZone *)zone +{ + return [self shared]; +} + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +- (id)init +{ + self = [super init]; + if (self != nil) + { + // Initialize instance variables here + } + return self; +} + ++ (void)zoomIn; +{ + GetFramework().Scale(Framework::SCALE_MAG, true); +} + ++ (void)zoomOut; +{ + GetFramework().Scale(Framework::SCALE_MIN, true); +} + ++ (NSString *)positionModeRawValue; +{ + location::EMyPositionMode mode = GetFramework().GetMyPositionMode(); + switch (mode) + { + case location::EMyPositionMode::NotFollowNoPosition: return @"Locate"; + case location::EMyPositionMode::NotFollow: return @"Locate"; + case location::EMyPositionMode::PendingPosition: return @"Locating"; + case location::EMyPositionMode::Follow: return @"Following"; + case location::EMyPositionMode::FollowAndRotate: return @"FollowingAndRotated"; + } + return @"Locate"; +} + ++ (void)switchToNextPositionMode; +{ + [MWMLocationManager enableLocationAlert]; + GetFramework().SwitchMyPositionNextMode(); +} + +- (bool)hasMainButtons; +{ + return true; +} + +@end diff --git a/iphone/Maps/Bridging/Controls.swift b/iphone/Maps/Bridging/Controls.swift new file mode 100644 index 000000000..e0d8c7d45 --- /dev/null +++ b/iphone/Maps/Bridging/Controls.swift @@ -0,0 +1,12 @@ +extension Controls { + @objc static let changeChangeTrackRecordingNotificationName: Notification.Name = Notification.Name(rawValue: "ChangeTrackRecording") + + /// The notification name for switching position mode + @objc static let switchPositionModeNotificationName: Notification.Name = Notification.Name(rawValue: "SwitchPositionMode") + + @objc static let changeVisibilityMainButtonsNotificationName: Notification.Name = Notification.Name(rawValue: "ChangeVisibilityMainButtons") + + static var positionMode: MapPositionButton.Mode { + return MapPositionButton.Mode(rawValue: Controls.positionModeRawValue()) ?? .locate + } +} diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h index 677a73eed..cf5a83420 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.h @@ -3,7 +3,6 @@ #import "MWMNavigationDashboardManager.h" @class MapViewController; -@class BottomTabBarViewController; @class TrackRecordingButtonViewController; @class SearchQuery; @@ -26,7 +25,7 @@ typedef NS_ENUM(NSUInteger, TrackRecordingButtonState) { @property(nonatomic) MWMBottomMenuState menuState; @property(nonatomic) MWMBottomMenuState menuRestoreState; @property(nonatomic) BOOL isDirectionViewHidden; -@property(nonatomic) BottomTabBarViewController * tabBarController; +@property(nonatomic) UIViewController * tabBarController; @property(nonatomic) TrackRecordingButtonViewController * trackRecordingButton; - (instancetype)init __attribute__((unavailable("init is not available"))); diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm index 728b3e1c9..f79c8716a 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/MWMMapViewControlsManager.mm @@ -64,6 +64,9 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; self.menuRestoreState = MWMBottomMenuStateInactive; self.isAddingPlace = NO; self.searchManager = controller.searchManager; + + [self tabBarController]; + return self; } @@ -220,13 +223,20 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; return _trafficButton; } -- (BottomTabBarViewController *)tabBarController { +- (UIViewController *)tabBarController { if (!_tabBarController) { MapViewController * ownerController = _ownerController; - _tabBarController = [BottomTabBarBuilder buildWithMapViewController:ownerController controlsManager:self]; + _tabBarController = [BridgeControllers mapControls]; [ownerController addChildViewController:_tabBarController]; UIView * tabBarViewSuperView = ownerController.controlsView; [tabBarViewSuperView addSubview:_tabBarController.view]; + _tabBarController.view.translatesAutoresizingMaskIntoConstraints = NO; + [NSLayoutConstraint activateConstraints:@[ + [tabBarViewSuperView.topAnchor constraintEqualToAnchor:_tabBarController.view.topAnchor], + [tabBarViewSuperView.leadingAnchor constraintEqualToAnchor:_tabBarController.view.leadingAnchor], + [tabBarViewSuperView.bottomAnchor constraintEqualToAnchor:_tabBarController.view.bottomAnchor], + [tabBarViewSuperView.trailingAnchor constraintEqualToAnchor:_tabBarController.view.trailingAnchor] + ]]; } return _tabBarController; @@ -289,7 +299,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; MapViewController * ownerController = _ownerController; switch (_menuState) { case MWMBottomMenuStateActive: - _tabBarController.isHidden = NO; + _tabBarController.view.hidden = NO; if (_menuController == nil) { _menuController = [BottomMenuBuilder buildMenuWithMapViewController:ownerController controlsManager:self @@ -298,7 +308,7 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; } break; case MWMBottomMenuStateLayers: - _tabBarController.isHidden = NO; + _tabBarController.view.hidden = NO; if (_menuController == nil) { _menuController = [BottomMenuBuilder buildLayersWithMapViewController:ownerController controlsManager:self @@ -307,14 +317,14 @@ NSString *const kMapToCategorySelectorSegue = @"MapToCategorySelectorSegue"; } break; case MWMBottomMenuStateInactive: - _tabBarController.isHidden = NO; + _tabBarController.view.hidden = NO; if (_menuController != nil) { [_menuController dismissViewControllerAnimated:YES completion:nil]; _menuController = nil; } break; case MWMBottomMenuStateHidden: - _tabBarController.isHidden = YES; + _tabBarController.view.hidden = YES; if (_menuController != nil) { [_menuController dismissViewControllerAnimated:YES completion:nil]; _menuController = nil; diff --git a/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.xib b/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.xib index 044bc8658..def870a17 100644 --- a/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.xib +++ b/iphone/Maps/Classes/CustomViews/MapViewControls/TrafficButton/MWMTrafficButtonViewController.xib @@ -1,11 +1,9 @@ - - - - + + - + @@ -18,12 +16,12 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/iphone/Maps/UI/ControlsView.swift b/iphone/Maps/UI/ControlsView.swift new file mode 100644 index 000000000..e0c57f55e --- /dev/null +++ b/iphone/Maps/UI/ControlsView.swift @@ -0,0 +1,144 @@ +import SwiftUI + +/// View for the interface button +struct ControlsView: View { + // MARK: Properties + + /// The dismiss action of the environment + @Environment(\.verticalSizeClass) private var verticalSizeClass + + + @State private var hasZoomButtons: Bool = true + + + @State private var leftMainButtonKind: MainButton.Kind = .hidden + + + @State private var hasMainButtons: Bool = true + + + /// The publisher to know when settings changed + private let settingsPublisher = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification) + + + /// The publisher to know when settings changed + private let changeVisibilityMainButtonsPublisher = NotificationCenter.default.publisher(for: Controls.changeVisibilityMainButtonsNotificationName) + + + /// The actual view + var body: some View { + ZStack { + if verticalSizeClass != .compact { + VStack(alignment: .trailing) { + HStack { + MapLayerButton() + + Spacer(minLength: 0) + + VStack { + MapTrackRecordingButton() + } + } + + Spacer(minLength: 0) + + VStack(spacing: 72) { + if hasZoomButtons { + VStack(spacing: 36) { + MapZoomButton(kind: .in) + + MapZoomButton(kind: .out) + } + } + + MapPositionButton() + } + + Spacer(minLength: 0) + + if hasMainButtons { + HStack { + if leftMainButtonKind != .hidden { + MainButton(kind: leftMainButtonKind) + + Spacer(minLength: 0) + } + + MainButton(kind: .search) + + Spacer(minLength: 0) + + MainButton(kind: .bookmarks) + + Spacer(minLength: 0) + + MainButton(kind: .more) + } + .frame(maxWidth: .infinity) + } + } + .padding([.leading, .trailing], 10) + } else { + HStack { + VStack(alignment: .leading) { + MapLayerButton() + + Spacer(minLength: 0) + + if hasMainButtons { + HStack(spacing: 32) { + if leftMainButtonKind != .hidden { + MainButton(kind: leftMainButtonKind) + } + + MainButton(kind: .search) + + MainButton(kind: .bookmarks) + + MainButton(kind: .more) + } + } + } + + Spacer(minLength: 0) + + VStack(alignment: .trailing) { + VStack { + MapTrackRecordingButton() + } + + Spacer(minLength: 0) + + if hasZoomButtons { + VStack(spacing: 36) { + MapZoomButton(kind: .in) + + MapZoomButton(kind: .out) + } + } + + Spacer(minLength: 0) + + MapPositionButton() + } + } + } + } + .padding(.top, 14) + .padding(.bottom, 2) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .onAppear { + hasZoomButtons = Settings.hasZoomButtons + leftMainButtonKind = Settings.leftMainButtonKind + hasMainButtons = Controls.shared().hasMainButtons() + } + .onReceive(settingsPublisher) { _ in + hasZoomButtons = Settings.hasZoomButtons + leftMainButtonKind = Settings.leftMainButtonKind + } + .onReceive(changeVisibilityMainButtonsPublisher) { _ in + hasMainButtons = Controls.shared().hasMainButtons() + } + } +} + diff --git a/iphone/Maps/UI/FloatingButtonStyle.swift b/iphone/Maps/UI/FloatingButtonStyle.swift new file mode 100644 index 000000000..b4f9da990 --- /dev/null +++ b/iphone/Maps/UI/FloatingButtonStyle.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct FloatingButtonStyle: ButtonStyle { + /// If the button is round + var isRound: Bool = true + + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .labelStyle(.iconOnly) + .padding(10) + .aspectRatio(1, contentMode: .fill) + .background { + if isRound { + Circle() + .stroke(Color.white.opacity(0.7), lineWidth: 1) + .background { + Color.white.opacity(configuration.isPressed ? 0.7 : 0.8) + .clipShape(Circle()) + } + .aspectRatio(1, contentMode: .fill) + .shadow(radius: 2) + } else { + RoundedRectangle(cornerRadius: 8) + .stroke(Color.white.opacity(0.7), lineWidth: 1) + .background { + Color.white.opacity(configuration.isPressed ? 0.7 : 0.8) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + .aspectRatio(1, contentMode: .fill) + .shadow(radius: 3) + } + } + .font(.title2) + .foregroundStyle(configuration.role == .destructive ? Color(.BaseColors.red) : Color.secondary) + .scaleEffect(configuration.isPressed ? (isRound ? 0.96 : 0.98) : 1) + .animation(.smooth, value: configuration.isPressed) + } +} diff --git a/iphone/Maps/UI/MainButton.swift b/iphone/Maps/UI/MainButton.swift new file mode 100644 index 000000000..d0a32d186 --- /dev/null +++ b/iphone/Maps/UI/MainButton.swift @@ -0,0 +1,79 @@ +import SwiftUI + +/// View for a main button +struct MainButton: View { + // MARK: Properties + + /// The kind + var kind: MainButton.Kind + + + /// The actual view + var body: some View { + if kind != .hidden { + Button { + if kind == .addPlace { + if let controlsManager = MWMMapViewControlsManager.manager() as? BottomMenuDelegate { + controlsManager.addPlace() + } + } else if kind == .recordTrack { + let trackRecorder: TrackRecordingManager = .shared + switch trackRecorder.recordingState { + case .active: + MapViewController.shared()?.showTrackRecordingPlacePage() + case .inactive: + trackRecorder.start { result in + switch result { + case .success: + MapViewController.shared()?.showTrackRecordingPlacePage() + case .failure: + break + } + } + } + } else if kind == .search { + if let searchManager = MapViewController.shared()?.searchManager { + if searchManager.isSearching { + searchManager.close() + } else { + searchManager.startSearching(isRouting: false) + } + } + } else if kind == .bookmarks { + MapViewController.shared()?.bookmarksCoordinator.open() + } else if kind == .settings { + MapViewController.shared()?.openSettings() + } else if kind == .help { + MapViewController.shared()?.openAbout() + } else if kind == .more { + if let controlsManager = MWMMapViewControlsManager.manager() { + if controlsManager.menuState == .active { + controlsManager.menuState = .inactive + } else if controlsManager.menuState == .inactive { + controlsManager.menuState = .active + } + } + } else if kind == .layers { + if MapOverlayManager.trafficEnabled() || MapOverlayManager.transitEnabled() || MapOverlayManager.isoLinesEnabled() || MapOverlayManager.outdoorEnabled() { + MapOverlayManager.setTrafficEnabled(false) + MapOverlayManager.setTransitEnabled(false) + MapOverlayManager.setIsoLinesEnabled(false) + MapOverlayManager.setOutdoorEnabled(false) + } else { + MWMMapViewControlsManager.manager()?.menuState = .layers + } + } + } label: { + Label { + Text(kind.description) + } icon: { + if kind != .layers, let image = kind.image { + image + } + } + } + .buttonStyle(FloatingButtonStyle(isRound: false)) + .frame(minWidth: 44, idealWidth: 44, minHeight: 44, idealHeight: 44) + } + } +} diff --git a/iphone/Maps/UI/MainButtonType.swift b/iphone/Maps/UI/MainButtonType.swift new file mode 100644 index 000000000..a0ce1031b --- /dev/null +++ b/iphone/Maps/UI/MainButtonType.swift @@ -0,0 +1,81 @@ +import SwiftUI + +extension MainButton { + /// The type of the left bottom bar button + enum Kind: String, Codable, CaseIterable, Identifiable { + case hidden = "Hidden" + case addPlace = "AddPlace" + case recordTrack = "RecordTrack" + case search = "Search" + case bookmarks = "Bookmarks" + case settings = "Settings" + case help = "Help" + case more = "More" + case layers = "Layers" + + + + // MARK: Properties + + /// The configurable cases + static var configurableCases: [MainButton.Kind] { + allCases.filter { kind in + return kind != .more && kind != .bookmarks && kind != .search + } + } + + + /// The id + var id: Self { self } + + + /// The description text + var description: String { + switch self { + case .hidden: + return String(localized: "disabled") + case .addPlace: + return String(localized: "placepage_add_place_button") + case .recordTrack: + return String(localized: "start_track_recording") + case .search: + return String(localized: "search") + case .bookmarks: + return String(localized: "bookmarks") + case .settings: + return String(localized: "settings") + case .help: + return String(localized: "help") + case .more: + return String(localized: "placepage_more_button") + case .layers: + return String(localized: "layers_title") + } + } + + + /// The image + var image: Image? { + switch self { + case .addPlace: + return Image(systemName: "plus") + case .recordTrack: + return Image(.MainButtons.LeftButton.recordTrack) + case .search: + return Image(systemName: "magnifyingglass") + case .bookmarks: + return Image(systemName: "list.star") + case .settings: + return Image(systemName: "gearshape.fill") + case .help: + return Image(systemName: "info.circle") + case .layers: + return Image(systemName: "square.stack.3d.up.fill") + case .more: + return Image(systemName: "ellipsis.circle") + default: + return nil + } + } + } +} diff --git a/iphone/Maps/UI/New Group/MapLayerButton.swift b/iphone/Maps/UI/New Group/MapLayerButton.swift new file mode 100644 index 000000000..2401b6f0c --- /dev/null +++ b/iphone/Maps/UI/New Group/MapLayerButton.swift @@ -0,0 +1,27 @@ +import SwiftUI + +/// View for a map layer button +struct MapLayerButton: View { + // MARK: Properties + + /// The actual view + var body: some View { + Group { + if Settings.leftMainButtonKind != .layers { + Button { + if MapOverlayManager.trafficEnabled() || MapOverlayManager.transitEnabled() || MapOverlayManager.isoLinesEnabled() || MapOverlayManager.outdoorEnabled() { + MapOverlayManager.setTrafficEnabled(false) + MapOverlayManager.setTransitEnabled(false) + MapOverlayManager.setIsoLinesEnabled(false) + MapOverlayManager.setOutdoorEnabled(false) + } else { + MWMMapViewControlsManager.manager()?.menuState = .layers + } + } label: { + Label("Show Layers", systemImage: "square.stack.3d.up.fill") + } + .buttonStyle(FloatingButtonStyle()) + } + } + } +} diff --git a/iphone/Maps/UI/New Group/MapPositionButton.swift b/iphone/Maps/UI/New Group/MapPositionButton.swift new file mode 100644 index 000000000..d3a5d1e55 --- /dev/null +++ b/iphone/Maps/UI/New Group/MapPositionButton.swift @@ -0,0 +1,39 @@ +import SwiftUI + +/// View for a map position mode button +struct MapPositionButton: View { + // MARK: Properties + + /// The mode + @State private var mode: MapPositionButton.Mode = .locate + + + /// The publisher to know when to stop showing the Safari view for the login form + private let switchPositionModePublisher = NotificationCenter.default.publisher(for: Controls.switchPositionModeNotificationName) + + + /// The actual view + var body: some View { + Button { + Controls.switchToNextPositionMode() + } label: { + Label { + Text(mode.description) + } icon: { + if mode == .following || mode == .followingAndRotated { + mode.image + .foregroundStyle(Color.BaseColors.blue) + } else { + mode.image + } + } + } + .buttonStyle(FloatingButtonStyle()) + .onAppear { + mode = Controls.positionMode + } + .onReceive(switchPositionModePublisher) { _ in + mode = Controls.positionMode + } + } +} diff --git a/iphone/Maps/UI/New Group/MapPositionButtonMode.swift b/iphone/Maps/UI/New Group/MapPositionButtonMode.swift new file mode 100644 index 000000000..4aa254b5e --- /dev/null +++ b/iphone/Maps/UI/New Group/MapPositionButtonMode.swift @@ -0,0 +1,48 @@ +import SwiftUI + +extension MapPositionButton { + /// The mode of the map position button + enum Mode: String, Codable, CaseIterable, Identifiable { + case locate = "Locate" + case locating = "Locating" + case following = "Following" + case followingAndRotated = "FollowingAndRotated" + + + + // MARK: Properties + + /// The id + var id: Self { self } + + + /// The description text + var description: String { + switch self { + case .locate: + return String(localized: "Find own location") + case .locating: + return String(localized: "Finding own location...") + case .following: + return String(localized: "Rotate map towards own direction") + case .followingAndRotated: + return String(localized: "Rotate map towards North") + } + } + + + /// The image + var image: Image { + switch self { + case .locate: + return Image(systemName: "location") + case .locating: + return Image(systemName: "progress.indicator") + case .following: + return Image(systemName: "location.fill") + case .followingAndRotated: + return Image(systemName: "location.north.line.fill") + } + } + } +} diff --git a/iphone/Maps/UI/New Group/MapTrackRecordingButton.swift b/iphone/Maps/UI/New Group/MapTrackRecordingButton.swift new file mode 100644 index 000000000..a7d804fbd --- /dev/null +++ b/iphone/Maps/UI/New Group/MapTrackRecordingButton.swift @@ -0,0 +1,45 @@ +import SwiftUI + +/// View for a map track recording button +struct MapTrackRecordingButton: View { + // MARK: Properties + + /// The mode + @State private var isRecording: Bool = false + + + /// The publisher to know when to stop showing the Safari view for the login form + private let changeChangeTrackRecordingPublisher = NotificationCenter.default.publisher(for: Controls.changeChangeTrackRecordingNotificationName) + + + /// The actual view + var body: some View { + ZStack { + if isRecording { + Button(role: .destructive) { + if isRecording { + MapViewController.shared()?.showTrackRecordingPlacePage() + } else { + TrackRecordingManager.shared.start { result in + switch result { + case .success: + MapViewController.shared()?.showTrackRecordingPlacePage() + case .failure: + break + } + } + } + } label: { + Label("Show Track Recording", systemImage: "record.circle") + } + .buttonStyle(FloatingButtonStyle()) + } + } + .onAppear { + isRecording = (TrackRecordingManager.shared.recordingState == .active) + } + .onReceive(changeChangeTrackRecordingPublisher) { _ in + isRecording = (TrackRecordingManager.shared.recordingState == .active) + } + } +} diff --git a/iphone/Maps/UI/New Group/MapZoomButton.swift b/iphone/Maps/UI/New Group/MapZoomButton.swift new file mode 100644 index 000000000..b5a9564c6 --- /dev/null +++ b/iphone/Maps/UI/New Group/MapZoomButton.swift @@ -0,0 +1,28 @@ +import SwiftUI + +/// View for a map zoom button +struct MapZoomButton: View { + // MARK: Properties + + /// The kind + var kind: MapZoomButton.Kind + + + /// The actual view + var body: some View { + Button { + if kind == .in { + Controls.zoomIn() + } else if kind == .out { + Controls.zoomOut() + } + } label: { + Label { + Text(kind.description) + } icon: { + kind.image + } + } + .buttonStyle(FloatingButtonStyle()) + } +} diff --git a/iphone/Maps/UI/New Group/MapZoomButtonKind.swift b/iphone/Maps/UI/New Group/MapZoomButtonKind.swift new file mode 100644 index 000000000..48d1704a0 --- /dev/null +++ b/iphone/Maps/UI/New Group/MapZoomButtonKind.swift @@ -0,0 +1,38 @@ +import SwiftUI + +extension MapZoomButton { + /// The type of the map zoom button + enum Kind: String, Codable, CaseIterable, Identifiable { + case `in` = "In" + case out = "Out" + + + + // MARK: Properties + + /// The id + var id: Self { self } + + + /// The description text + var description: String { + switch self { + case .in: + return String(localized: "zoom_in") + case .out: + return String(localized: "zoom_out") + } + } + + + /// The image + var image: Image { + switch self { + case .in: + return Image(systemName: "plus") + case .out: + return Image(systemName: "minus") + } + } + } +} diff --git a/iphone/Maps/UI/Settings/SettingsView.swift b/iphone/Maps/UI/Settings/SettingsView.swift index c2f1e65d8..e6fa0f843 100644 --- a/iphone/Maps/UI/Settings/SettingsView.swift +++ b/iphone/Maps/UI/Settings/SettingsView.swift @@ -17,7 +17,7 @@ struct SettingsView: View { /// The selected left button type - @State private var selectedLeftButtonType: Settings.LeftButtonType = .help + @State private var selectedLeftMainButtonKind: MainButton.Kind = .help /// If 3D buildings should be displayed @@ -112,9 +112,9 @@ struct SettingsView: View { Toggle("pref_zoom_title", isOn: $hasZoomButtons) .tint(.accent) - Picker(selection: $selectedLeftButtonType) { - ForEach(Settings.LeftButtonType.allCases) { leftButtonType in - Text(leftButtonType.description) + Picker(selection: $selectedLeftMainButtonKind) { + ForEach(MainButton.Kind.configurableCases) { leftMainButtonKind in + Text(leftMainButtonKind.description) } } label: { Text("pref_left_button_type") @@ -291,7 +291,7 @@ struct SettingsView: View { .onAppear { selectedDistanceUnit = Settings.distanceUnit hasZoomButtons = Settings.hasZoomButtons - selectedLeftButtonType = Settings.leftButtonType + selectedLeftMainButtonKind = Settings.leftMainButtonKind has3dBuildings = Settings.has3dBuildings hasAutomaticDownload = Settings.hasAutomaticDownload hasIncreasedFontsize = Settings.hasIncreasedFontsize @@ -311,8 +311,8 @@ struct SettingsView: View { .onChange(of: hasZoomButtons) { changedHasZoomButtons in Settings.hasZoomButtons = changedHasZoomButtons } - .onChange(of: selectedLeftButtonType) { changedSelectedLeftButtonType in - Settings.leftButtonType = changedSelectedLeftButtonType + .onChange(of: selectedLeftMainButtonKind) { changedSelectedLeftMainButtonKind in + Settings.leftMainButtonKind = changedSelectedLeftMainButtonKind } .onChange(of: has3dBuildings) { changedHas3dBuildings in Settings.has3dBuildings = changedHas3dBuildings