diff --git a/Meshtastic.xcodeproj/project.pbxproj b/Meshtastic.xcodeproj/project.pbxproj index c0fe6473..eec33c62 100644 --- a/Meshtastic.xcodeproj/project.pbxproj +++ b/Meshtastic.xcodeproj/project.pbxproj @@ -1414,6 +1414,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1471,6 +1472,7 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-expression-type-checking=200"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -1568,6 +1570,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 2.3.10; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1601,6 +1604,7 @@ "@executable_path/../../Frameworks", ); MARKETING_VERSION = 2.3.10; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = gvh.MeshtasticClient.Widgets; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift index f8ee5165..27f6b7e6 100644 --- a/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift +++ b/Meshtastic/Views/Nodes/Helpers/Map/NodeMapSwiftUI.swift @@ -35,172 +35,203 @@ struct NodeMapSwiftUI: View { @State private var mapRegion = MKCoordinateRegion.init() - @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], - predicate: NSPredicate( - format: "expire == nil || expire >= %@", Date() as NSDate - ), animation: .none) + @FetchRequest( + sortDescriptors: [NSSortDescriptor(key: "name", ascending: false)], + predicate: NSPredicate( + format: "expire == nil || expire >= %@", Date() as NSDate + ), + animation: .none + ) private var waypoints: FetchedResults - var body: some View { - var mostRecent = node.positions?.lastObject as? PositionEntity + private var title: String { + String((node.user?.shortName ?? "unknown".localized) + (" \(node.positions?.count ?? 0) points")) + } + + private var mostRecent: PositionEntity? { + node.positions?.lastObject as? PositionEntity + } - if node.hasPositions { - ZStack { - MapReader { _ in - Map(position: $position, bounds: MapCameraBounds(minimumDistance: 1, maximumDistance: .infinity), scope: mapScope) { - NodeMapContent(node: node) - } - .mapScope(mapScope) - .mapStyle(mapStyle) - .mapControls { - MapScaleView(scope: mapScope) - .mapControlVisibility(.visible) - if showUserLocation { - MapUserLocationButton(scope: mapScope) - .mapControlVisibility(.visible) - } - MapPitchToggle(scope: mapScope) - .mapControlVisibility(.visible) - MapCompass(scope: mapScope) - .mapControlVisibility(.visible) - } - .controlSize(.regular) - .overlay(alignment: .bottom) { - if scene != nil && isLookingAround { - LookAroundPreview(initialScene: scene) - .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .padding(.horizontal, 20) - } - } - .overlay(alignment: .bottom) { - if !isLookingAround && isShowingAltitude { - PositionAltitudeChart(node: node) - .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .padding(.horizontal, 20) - } - } - .sheet(isPresented: $isEditingSettings) { - MapSettingsForm(traffic: $showTraffic, pointsOfInterest: $showPointsOfInterest, mapLayer: $selectedMapLayer, meshMap: $isMeshMap) - .onChange(of: (selectedMapLayer)) { newMapLayer in - switch selectedMapLayer { - case .standard: - UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .hybrid: - UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .satellite: - UserDefaults.mapLayer = newMapLayer - mapStyle = MapStyle.imagery(elevation: .flat) - case .offline: - return - } - } - } - .onChange(of: node) { - isLookingAround = false + + + private var mapControls: some View { + HStack { + MapScaleView(scope: mapScope) + .mapControlVisibility(.visible) + if showUserLocation { + MapUserLocationButton(scope: mapScope) + .mapControlVisibility(.visible) + } + MapPitchToggle(scope: mapScope) + .mapControlVisibility(.visible) + MapCompass(scope: mapScope) + .mapControlVisibility(.visible) + } + } + + private var mapAccessoryControls: some View { + HStack { + Button(action: { + withAnimation { + isEditingSettings = !isEditingSettings + } + }) { + Image(systemName: isEditingSettings ? "info.circle.fill" : "info.circle") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + /// Look Around Button + if self.scene != nil { + Button(action: { + if isShowingAltitude { isShowingAltitude = false - mostRecent = node.positions?.lastObject as? PositionEntity - if node.positions?.count ?? 0 > 1 { - position = .automatic - } else { - position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60)) - } - if let mostRecent { - Task { - scene = try? await fetchScene(for: mostRecent.coordinate) - } - } } - .onAppear { - UIApplication.shared.isIdleTimerDisabled = true - switch selectedMapLayer { - case .standard: - mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .hybrid: - mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - case .satellite: - mapStyle = MapStyle.imagery(elevation: .flat) - case .offline: - mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) - } - mostRecent = node.positions?.lastObject as? PositionEntity - if node.positions?.count ?? 0 > 1 { - position = .automatic - } else { - position = .camera(MapCamera(centerCoordinate: mostRecent!.coordinate, distance: 8000, heading: 0, pitch: 60)) - } - if self.scene == nil { - Task { - scene = try? await fetchScene(for: mostRecent!.coordinate) - } - } + isLookingAround = !isLookingAround + }) { + Image(systemName: isLookingAround ? "binoculars.fill" : "binoculars") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + } + /// Altitude Button + if node.positions?.count ?? 0 > 1 { + Button(action: { + if isLookingAround { + isLookingAround = false } - .safeAreaInset(edge: .bottom, alignment: .trailing) { - HStack { - Button(action: { - withAnimation { - isEditingSettings = !isEditingSettings - } - }) { - Image(systemName: isEditingSettings ? "info.circle.fill" : "info.circle") - .padding(.vertical, 5) - } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - /// Look Around Button - if self.scene != nil { - Button(action: { - if isShowingAltitude { - isShowingAltitude = false - } - isLookingAround = !isLookingAround - }) { - Image(systemName: isLookingAround ? "binoculars.fill" : "binoculars") - .padding(.vertical, 5) - } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - } - /// Altitude Button - if node.positions?.count ?? 0 > 1 { - Button(action: { - if isLookingAround { - isLookingAround = false - } - isShowingAltitude = !isShowingAltitude - }) { - Image(systemName: isShowingAltitude ? "mountain.2.fill" : "mountain.2") - .padding(.vertical, 5) - } - .tint(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.accentColor) - .buttonStyle(.borderedProminent) - } - } - .controlSize(.regular) - .padding(5) + isShowingAltitude = !isShowingAltitude + }) { + Image(systemName: isShowingAltitude ? "mountain.2.fill" : "mountain.2") + .padding(.vertical, 5) + } + .tint(Color(UIColor.secondarySystemBackground)) + .foregroundColor(.accentColor) + .buttonStyle(.borderedProminent) + } + } + .controlSize(.regular) + .padding(5) + } + + private var connectedDeviceIndicator: some View { + ConnectedDevice( + bluetoothOn: bleManager.isSwitchedOn, + deviceConnected: bleManager.connectedPeripheral != nil, + name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?" + ) + } + + var body: some View { + if node.hasPositions { + Map( + position: $position, + bounds: MapCameraBounds( + minimumDistance: 1, + maximumDistance: .infinity + ), + scope: mapScope + ) { + NodeMapContent(node: node) + } + .mapScope(mapScope) + .mapStyle(mapStyle) + .mapControls { + mapControls + } + .controlSize(.regular) + .overlay(alignment: .bottom) { + if scene != nil, isLookingAround { + LookAroundPreview(initialScene: scene) + .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .padding(.horizontal, 20) + } else if isShowingAltitude { + PositionAltitudeChart(node: node) + .frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 250 : 400) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .padding(.horizontal, 20) + } + } + .sheet(isPresented: $isEditingSettings) { + MapSettingsForm( + traffic: $showTraffic, + pointsOfInterest: $showPointsOfInterest, + mapLayer: $selectedMapLayer, + meshMap: $isMeshMap + ).onChange(of: (selectedMapLayer)) { newMapLayer in + switch selectedMapLayer { + case .standard: + UserDefaults.mapLayer = newMapLayer + mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .hybrid: + UserDefaults.mapLayer = newMapLayer + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .satellite: + UserDefaults.mapLayer = newMapLayer + mapStyle = MapStyle.imagery(elevation: .flat) + case .offline: + return } - .onDisappear { - UIApplication.shared.isIdleTimerDisabled = false + } + } + .onChange(of: node) { + isLookingAround = false + isShowingAltitude = false + if node.positions?.count ?? 0 > 1 { + position = .automatic + } else if let coordinate = mostRecent?.coordinate { + position = .camera(MapCamera(centerCoordinate: coordinate, distance: 8000, heading: 0, pitch: 60)) + } + if let coordinate = mostRecent?.coordinate { + Task { + scene = try await fetchScene(for: coordinate) } - }} - .navigationBarTitle(String((node.user?.shortName ?? "unknown".localized) + (" \(node.positions?.count ?? 0) points")), displayMode: .inline) - .navigationBarItems(trailing: - ZStack { - ConnectedDevice( - bluetoothOn: bleManager.isSwitchedOn, - deviceConnected: bleManager.connectedPeripheral != nil, - name: (bleManager.connectedPeripheral != nil) ? bleManager.connectedPeripheral.shortName : "?") - }) + } + } + .onAppear { + setupMapView() + } + .safeAreaInset(edge: .bottom, alignment: .trailing) { + mapAccessoryControls + } + .onDisappear { + UIApplication.shared.isIdleTimerDisabled = false + } + .navigationBarTitle(title, displayMode: .inline) + .navigationBarItems(trailing: connectedDeviceIndicator) } else { ContentUnavailableView("No Positions", systemImage: "mappin.slash") } } + + private func setupMapView() { + UIApplication.shared.isIdleTimerDisabled = true + switch selectedMapLayer { + case .standard: + mapStyle = MapStyle.standard(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .hybrid: + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + case .satellite: + mapStyle = MapStyle.imagery(elevation: .flat) + case .offline: + mapStyle = MapStyle.hybrid(elevation: .flat, pointsOfInterest: showPointsOfInterest ? .all : .excludingAll, showsTraffic: showTraffic) + } + if node.positions?.count ?? 0 > 1 { + position = .automatic + } else if let coordinate = mostRecent?.coordinate { + position = .camera(MapCamera(centerCoordinate: coordinate, distance: 8000, heading: 0, pitch: 60)) + } + if scene == nil, let coordinate = mostRecent?.coordinate { + Task { + scene = try await fetchScene(for: coordinate) + } + } + } + /// Get the look around scene private func fetchScene(for coordinate: CLLocationCoordinate2D) async throws -> MKLookAroundScene? { let lookAroundScene = MKLookAroundSceneRequest(coordinate: coordinate)