From 1482b26664c6450dd33eeac5e1d18788cbc0d4a3 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Thu, 16 Jan 2025 15:25:58 +0100 Subject: [PATCH 1/4] Move the Progress view away from the ConnectionView --- .../Tunnel/ActivityIndicator.swift | 12 +- .../ConnectionView/ConnectionView.swift | 3 - .../FI_TunnelViewController.swift | 221 ------------------ .../Tunnel/TunnelViewController.swift | 23 +- 4 files changed, 32 insertions(+), 227 deletions(-) delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FI_TunnelViewController.swift diff --git a/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift b/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift index 9b42bab8e63e..241515798c00 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift @@ -11,6 +11,7 @@ import SwiftUI struct CustomProgressView: View { var style: Style @State private var angle: Double = 0 + @ObservedObject var connectionViewModel: ConnectionViewViewModel var body: some View { Image(.iconSpinner) @@ -22,12 +23,19 @@ struct CustomProgressView: View { angle = 360 } } + .onDisappear { + withAnimation(Animation.linear(duration: 0.6).repeatForever(autoreverses: false)) { + angle = 0 + } + } +// .showIf(connectionViewModel.showsActivityIndicator) } } #Preview { - CustomProgressView(style: .large) - .background(UIColor.secondaryColor.color) + ConnectionViewComponentPreview(showIndicators: true, isExpanded: true) { _, viewModel, _ in + CustomProgressView(style: .large, connectionViewModel: viewModel) + } } extension CustomProgressView { diff --git a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift index 6dfee3abed08..51a6b99efff1 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/ConnectionView/ConnectionView.swift @@ -22,9 +22,6 @@ struct ConnectionView: View { .accessibilityIdentifier(AccessibilityIdentifier.connectionView.asString) VStack(spacing: 22) { - CustomProgressView(style: .large) - .showIf(connectionViewModel.showsActivityIndicator) - ZStack { BlurView(style: .dark) diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FI_TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FI_TunnelViewController.swift deleted file mode 100644 index bb754a0c25fa..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FI_TunnelViewController.swift +++ /dev/null @@ -1,221 +0,0 @@ -// -// FI_TunnelViewController.swift -// MullvadVPN -// -// Created by Jon Petersson on 2024-12-10. -// Copyright © 2024 Mullvad VPN AB. All rights reserved. -// - -import Combine -import MapKit -import MullvadLogging -import MullvadSettings -import MullvadTypes -import SwiftUI - -// NOTE: This ViewController will replace TunnelViewController once feature indicators work is done. - -class FI_TunnelViewController: UIViewController, RootContainment { - private let logger = Logger(label: "TunnelViewController") - private let interactor: TunnelViewControllerInteractor - private var tunnelState: TunnelState = .disconnected - private var connectionViewViewModel: ConnectionViewViewModel - private var indicatorsViewViewModel: FeatureIndicatorsViewModel - private var connectionView: ConnectionView - private var connectionController: UIHostingController? - - var shouldShowSelectLocationPicker: (() -> Void)? - var shouldShowCancelTunnelAlert: (() -> Void)? - - private let mapViewController = MapViewController() - - override var preferredStatusBarStyle: UIStatusBarStyle { - .lightContent - } - - var preferredHeaderBarPresentation: HeaderBarPresentation { - switch interactor.deviceState { - case .loggedIn, .revoked: - return HeaderBarPresentation( - style: tunnelState.isSecured ? .secured : .unsecured, - showsDivider: false - ) - case .loggedOut: - return HeaderBarPresentation(style: .default, showsDivider: true) - } - } - - var prefersHeaderBarHidden: Bool { - false - } - - init(interactor: TunnelViewControllerInteractor) { - self.interactor = interactor - - tunnelState = interactor.tunnelStatus.state - connectionViewViewModel = ConnectionViewViewModel(tunnelStatus: interactor.tunnelStatus) - indicatorsViewViewModel = FeatureIndicatorsViewModel( - tunnelSettings: interactor.tunnelSettings, - ipOverrides: interactor.ipOverrides - ) - - connectionView = ConnectionView( - connectionViewModel: self.connectionViewViewModel, - indicatorsViewModel: self.indicatorsViewViewModel - ) - - super.init(nibName: nil, bundle: nil) - - // When content size is updated in SwiftUI we need to explicitly tell UIKit to - // update its view size. This is not necessary on iOS 16 where we can set - // hostingController.sizingOptions instead. - connectionView.onContentUpdate = { [weak self] in - self?.connectionController?.view.setNeedsUpdateConstraints() - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - interactor.didUpdateDeviceState = { [weak self] _, _ in - self?.setNeedsHeaderBarStyleAppearanceUpdate() - } - - interactor.didUpdateTunnelStatus = { [weak self] tunnelStatus in - self?.connectionViewViewModel.tunnelStatus = tunnelStatus - self?.setTunnelState(tunnelStatus.state, animated: true) - self?.view.setNeedsLayout() - } - - interactor.didGetOutgoingAddress = { [weak self] connectionInfo in - self?.connectionViewViewModel.outgoingConnectionInfo = connectionInfo - } - - interactor.didUpdateTunnelSettings = { [weak self] tunnelSettings in - self?.indicatorsViewViewModel.tunnelSettings = tunnelSettings - } - - interactor.didUpdateIpOverrides = { [weak self] overrides in - self?.indicatorsViewViewModel.ipOverrides = overrides - } - - connectionView.action = { [weak self] action in - switch action { - case .connect: - self?.interactor.startTunnel() - - case .cancel: - if case .waitingForConnectivity(.noConnection) = self?.interactor.tunnelStatus.state { - self?.shouldShowCancelTunnelAlert?() - } else { - self?.interactor.stopTunnel() - } - - case .disconnect: - self?.interactor.stopTunnel() - - case .reconnect: - self?.interactor.reconnectTunnel(selectNewRelay: true) - - case .selectLocation: - self?.shouldShowSelectLocationPicker?() - } - } - - addMapController() - addContentView() - updateMap(animated: false) - } - - func setMainContentHidden(_ isHidden: Bool, animated: Bool) { - let actions = { - _ = self.connectionView.opacity(isHidden ? 0 : 1) - } - - if animated { - UIView.animate(withDuration: 0.25, animations: actions) - } else { - actions() - } - } - - // MARK: - Private - - private func setTunnelState(_ tunnelState: TunnelState, animated: Bool) { - self.tunnelState = tunnelState - - setNeedsHeaderBarStyleAppearanceUpdate() - - guard isViewLoaded else { return } - - updateMap(animated: animated) - } - - private func updateMap(animated: Bool) { - switch tunnelState { - case let .connecting(tunnelRelays, _, _): - mapViewController.removeLocationMarker() - mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated) - connectionViewViewModel.showsActivityIndicator = true - - case let .reconnecting(tunnelRelays, _, _), let .negotiatingEphemeralPeer(tunnelRelays, _, _, _): - mapViewController.removeLocationMarker() - mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated) - connectionViewViewModel.showsActivityIndicator = true - - case let .connected(tunnelRelays, _, _): - let center = tunnelRelays.exit.location.geoCoordinate - mapViewController.setCenter(center, animated: animated) { - self.connectionViewViewModel.showsActivityIndicator = false - - // Connection can change during animation, so make sure we're still connected before adding marker. - if case .connected = self.tunnelState { - self.mapViewController.addLocationMarker(coordinate: center) - } - } - - case .pendingReconnect: - mapViewController.removeLocationMarker() - connectionViewViewModel.showsActivityIndicator = true - - case .waitingForConnectivity, .error: - mapViewController.removeLocationMarker() - connectionViewViewModel.showsActivityIndicator = false - - case .disconnected, .disconnecting: - mapViewController.removeLocationMarker() - mapViewController.setCenter(nil, animated: animated) - connectionViewViewModel.showsActivityIndicator = false - } - } - - private func addMapController() { - let mapView = mapViewController.view! - - addChild(mapViewController) - mapViewController.didMove(toParent: self) - - view.addConstrainedSubviews([mapView]) { - mapView.pinEdgesToSuperview() - } - } - - private func addContentView() { - let connectionController = UIHostingController(rootView: connectionView) - self.connectionController = connectionController - - let connectionViewProxy = connectionController.view! - connectionViewProxy.backgroundColor = .clear - - addChild(connectionController) - connectionController.didMove(toParent: self) - - view.addConstrainedSubviews([connectionViewProxy]) { - connectionViewProxy.pinEdgesToSuperview(.all()) - } - } -} diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index a486014e7241..846b460837f9 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -21,6 +21,8 @@ class TunnelViewController: UIViewController, RootContainment { private var indicatorsViewViewModel: FeatureIndicatorsViewModel private var connectionView: ConnectionView private var connectionController: UIHostingController? + private var progressView: CustomProgressView + private var progressViewController: UIHostingController? var shouldShowSelectLocationPicker: (() -> Void)? var shouldShowCancelTunnelAlert: (() -> Void)? @@ -61,6 +63,10 @@ class TunnelViewController: UIViewController, RootContainment { connectionViewModel: self.connectionViewViewModel, indicatorsViewModel: self.indicatorsViewViewModel ) + progressView = CustomProgressView( + style: .large, + connectionViewModel: connectionViewViewModel + ) super.init(nibName: nil, bundle: nil) @@ -69,6 +75,7 @@ class TunnelViewController: UIViewController, RootContainment { // hostingController.sizingOptions instead. connectionView.onContentUpdate = { [weak self] in self?.connectionController?.view.setNeedsUpdateConstraints() + self?.progressViewController?.view.setNeedsUpdateConstraints() } } @@ -212,7 +219,21 @@ class TunnelViewController: UIViewController, RootContainment { addChild(connectionController) connectionController.didMove(toParent: self) - view.addConstrainedSubviews([connectionViewProxy]) { + let progressViewController = UIHostingController(rootView: progressView) + self.progressViewController = progressViewController + + let progressViewProxy = progressViewController.view! + progressViewProxy.backgroundColor = .clear + + addChild(progressViewController) + progressViewController.didMove(toParent: self) + + let verticalCenteredAnchor = progressViewProxy.centerYAnchor.anchorWithOffset(to: view.centerYAnchor) + view.addConstrainedSubviews([progressViewProxy, connectionViewProxy]) { + progressViewProxy.centerXAnchor.constraint(equalTo: view.centerXAnchor) + // Align the progress view to 2/3 of the height of the map + verticalCenteredAnchor.constraint(equalTo: progressViewProxy.heightAnchor, multiplier: 3.0 / 2.0) + connectionViewProxy.pinEdgesToSuperview(.all()) } } From 1ce16a3821918f3f0633ea26310eeae0d0bbe04c Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Fri, 17 Jan 2025 16:38:56 +0100 Subject: [PATCH 2/4] Center the progress view higher on the screen --- .../Tunnel/TunnelViewController.swift | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index 846b460837f9..4cee64947adf 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -21,12 +21,19 @@ class TunnelViewController: UIViewController, RootContainment { private var indicatorsViewViewModel: FeatureIndicatorsViewModel private var connectionView: ConnectionView private var connectionController: UIHostingController? - private var progressView: CustomProgressView - private var progressViewController: UIHostingController? var shouldShowSelectLocationPicker: (() -> Void)? var shouldShowCancelTunnelAlert: (() -> Void)? + let activityIndicator: SpinnerActivityIndicatorView = { + let activityIndicator = SpinnerActivityIndicatorView(style: .large) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.tintColor = .white + activityIndicator.setContentHuggingPriority(.defaultHigh, for: .horizontal) + activityIndicator.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + return activityIndicator + }() + private let mapViewController = MapViewController() override var preferredStatusBarStyle: UIStatusBarStyle { @@ -63,10 +70,6 @@ class TunnelViewController: UIViewController, RootContainment { connectionViewModel: self.connectionViewViewModel, indicatorsViewModel: self.indicatorsViewViewModel ) - progressView = CustomProgressView( - style: .large, - connectionViewModel: connectionViewViewModel - ) super.init(nibName: nil, bundle: nil) @@ -75,7 +78,6 @@ class TunnelViewController: UIViewController, RootContainment { // hostingController.sizingOptions instead. connectionView.onContentUpdate = { [weak self] in self?.connectionController?.view.setNeedsUpdateConstraints() - self?.progressViewController?.view.setNeedsUpdateConstraints() } } @@ -132,7 +134,7 @@ class TunnelViewController: UIViewController, RootContainment { } addMapController() - addContentView() + addConnectionView() updateMap(animated: false) } @@ -166,8 +168,10 @@ class TunnelViewController: UIViewController, RootContainment { mapViewController.removeLocationMarker() mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated) connectionViewViewModel.showsActivityIndicator = true + activityIndicator.startAnimating() case let .reconnecting(tunnelRelays, _, _), let .negotiatingEphemeralPeer(tunnelRelays, _, _, _): + activityIndicator.startAnimating() mapViewController.removeLocationMarker() mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated) connectionViewViewModel.showsActivityIndicator = true @@ -180,18 +184,22 @@ class TunnelViewController: UIViewController, RootContainment { // Connection can change during animation, so make sure we're still connected before adding marker. if case .connected = self.tunnelState { self.mapViewController.addLocationMarker(coordinate: center) + self.activityIndicator.stopAnimating() } } case .pendingReconnect: + activityIndicator.startAnimating() mapViewController.removeLocationMarker() connectionViewViewModel.showsActivityIndicator = true case .waitingForConnectivity, .error: + activityIndicator.stopAnimating() mapViewController.removeLocationMarker() connectionViewViewModel.showsActivityIndicator = false case .disconnected, .disconnecting: + activityIndicator.stopAnimating() mapViewController.removeLocationMarker() mapViewController.setCenter(nil, animated: animated) connectionViewViewModel.showsActivityIndicator = false @@ -202,6 +210,7 @@ class TunnelViewController: UIViewController, RootContainment { let mapView = mapViewController.view! addChild(mapViewController) + mapViewController.alignmentView = activityIndicator mapViewController.didMove(toParent: self) view.addConstrainedSubviews([mapView]) { @@ -209,7 +218,13 @@ class TunnelViewController: UIViewController, RootContainment { } } - private func addContentView() { + /// Computers a constraint multiplier based on the screen size + private func computeHeightBreakpointMultiplier() -> CGFloat { + let screenBounds = UIWindow().screen.coordinateSpace.bounds + return screenBounds.height < 700 ? 2.0 : 1.5 + } + + private func addConnectionView() { let connectionController = UIHostingController(rootView: connectionView) self.connectionController = connectionController @@ -218,21 +233,17 @@ class TunnelViewController: UIViewController, RootContainment { addChild(connectionController) connectionController.didMove(toParent: self) - - let progressViewController = UIHostingController(rootView: progressView) - self.progressViewController = progressViewController - - let progressViewProxy = progressViewController.view! - progressViewProxy.backgroundColor = .clear - - addChild(progressViewController) - progressViewController.didMove(toParent: self) - - let verticalCenteredAnchor = progressViewProxy.centerYAnchor.anchorWithOffset(to: view.centerYAnchor) - view.addConstrainedSubviews([progressViewProxy, connectionViewProxy]) { - progressViewProxy.centerXAnchor.constraint(equalTo: view.centerXAnchor) - // Align the progress view to 2/3 of the height of the map - verticalCenteredAnchor.constraint(equalTo: progressViewProxy.heightAnchor, multiplier: 3.0 / 2.0) + // If the device doesn't have a lot of vertical screen estate, center the progress view higher on the map + // so the connection view details do not shadow it unless fully expanded if possible + let heightConstraintMultiplier = computeHeightBreakpointMultiplier() + + let verticalCenteredAnchor = activityIndicator.centerYAnchor.anchorWithOffset(to: view.centerYAnchor) + view.addConstrainedSubviews([activityIndicator, connectionViewProxy]) { + activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor) + verticalCenteredAnchor.constraint( + equalTo: activityIndicator.heightAnchor, + multiplier: heightConstraintMultiplier + ) connectionViewProxy.pinEdgesToSuperview(.all()) } From 7f4cb8142709a5d77964605293159db47ee40722 Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Fri, 17 Jan 2025 16:41:30 +0100 Subject: [PATCH 3/4] Delete unused SwiftUI Spinner view --- ios/MullvadVPN.xcodeproj/project.pbxproj | 4 -- .../Tunnel/ActivityIndicator.swift | 56 ------------------- 2 files changed, 60 deletions(-) delete mode 100644 ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 1a1df0918b19..5af100575d38 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -663,7 +663,6 @@ 7AF9BE902A39F26000DBFEDB /* Collection+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */; }; 7AF9BE952A40461100DBFEDB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */; }; 7AF9BE972A41C71F00DBFEDB /* ChipViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */; }; - 7AFBE3872D084C9D002335FC /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */; }; 7AFBE3892D089163002335FC /* TunnelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE3882D08915D002335FC /* TunnelViewController.swift */; }; 7AFBE38B2D09AAFF002335FC /* SinglehopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */; }; 7AFBE38D2D09AB2E002335FC /* MultihopPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */; }; @@ -2045,7 +2044,6 @@ 7AF9BE8F2A39F26000DBFEDB /* Collection+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Sorting.swift"; sourceTree = ""; }; 7AF9BE942A40461100DBFEDB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = ""; }; 7AF9BE962A41C71F00DBFEDB /* ChipViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChipViewCell.swift; sourceTree = ""; }; - 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; }; 7AFBE3882D08915D002335FC /* TunnelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewController.swift; sourceTree = ""; }; 7AFBE38A2D09AAFF002335FC /* SinglehopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SinglehopPicker.swift; sourceTree = ""; }; 7AFBE38C2D09AB2E002335FC /* MultihopPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultihopPicker.swift; sourceTree = ""; }; @@ -3059,7 +3057,6 @@ isa = PBXGroup; children = ( 4419AA862D28264D001B13C9 /* ConnectionView */, - 7AFBE3862D084C96002335FC /* ActivityIndicator.swift */, 5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */, 58C3F4F82964B08300D72515 /* MapViewController.swift */, F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */, @@ -5897,7 +5894,6 @@ F0ADC3742CD3C47400A1AD97 /* ChipFlowLayout.swift in Sources */, 587B75412668FD7800DEF7E9 /* AccountExpirySystemNotificationProvider.swift in Sources */, 587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */, - 7AFBE3872D084C9D002335FC /* ActivityIndicator.swift in Sources */, F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */, 58DFF7D82B02774C00F864E0 /* ListItemPickerViewController.swift in Sources */, 5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */, diff --git a/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift b/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift deleted file mode 100644 index 241515798c00..000000000000 --- a/ios/MullvadVPN/View controllers/Tunnel/ActivityIndicator.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// ActivityIndicator.swift -// MullvadVPN -// -// Created by Jon Petersson on 2024-12-10. -// Copyright © 2024 Mullvad VPN AB. All rights reserved. -// - -import SwiftUI - -struct CustomProgressView: View { - var style: Style - @State private var angle: Double = 0 - @ObservedObject var connectionViewModel: ConnectionViewViewModel - - var body: some View { - Image(.iconSpinner) - .resizable() - .frame(width: style.size.width, height: style.size.height) - .rotationEffect(.degrees(angle)) - .onAppear { - withAnimation(Animation.linear(duration: 0.6).repeatForever(autoreverses: false)) { - angle = 360 - } - } - .onDisappear { - withAnimation(Animation.linear(duration: 0.6).repeatForever(autoreverses: false)) { - angle = 0 - } - } -// .showIf(connectionViewModel.showsActivityIndicator) - } -} - -#Preview { - ConnectionViewComponentPreview(showIndicators: true, isExpanded: true) { _, viewModel, _ in - CustomProgressView(style: .large, connectionViewModel: viewModel) - } -} - -extension CustomProgressView { - enum Style { - case small, medium, large - - var size: CGSize { - switch self { - case .small: - CGSize(width: 16, height: 16) - case .medium: - CGSize(width: 20, height: 20) - case .large: - CGSize(width: 60, height: 60) - } - } - } -} From 1cfba069c0e066a34c98b2c80bc0a91174d3f7ef Mon Sep 17 00:00:00 2001 From: Bug Magnet Date: Mon, 20 Jan 2025 09:02:07 +0100 Subject: [PATCH 4/4] Add spinner in a different method --- .../Tunnel/TunnelViewController.swift | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift index 4cee64947adf..6eb73692889d 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift @@ -135,6 +135,7 @@ class TunnelViewController: UIViewController, RootContainment { addMapController() addConnectionView() + addActivityIndicator() updateMap(animated: false) } @@ -218,33 +219,37 @@ class TunnelViewController: UIViewController, RootContainment { } } - /// Computers a constraint multiplier based on the screen size + /// Computes a constraint multiplier based on the screen size private func computeHeightBreakpointMultiplier() -> CGFloat { let screenBounds = UIWindow().screen.coordinateSpace.bounds return screenBounds.height < 700 ? 2.0 : 1.5 } - private func addConnectionView() { - let connectionController = UIHostingController(rootView: connectionView) - self.connectionController = connectionController - - let connectionViewProxy = connectionController.view! - connectionViewProxy.backgroundColor = .clear - - addChild(connectionController) - connectionController.didMove(toParent: self) + private func addActivityIndicator() { // If the device doesn't have a lot of vertical screen estate, center the progress view higher on the map // so the connection view details do not shadow it unless fully expanded if possible let heightConstraintMultiplier = computeHeightBreakpointMultiplier() let verticalCenteredAnchor = activityIndicator.centerYAnchor.anchorWithOffset(to: view.centerYAnchor) - view.addConstrainedSubviews([activityIndicator, connectionViewProxy]) { + view.addConstrainedSubviews([activityIndicator]) { activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor) verticalCenteredAnchor.constraint( equalTo: activityIndicator.heightAnchor, multiplier: heightConstraintMultiplier ) + } + } + + private func addConnectionView() { + let connectionController = UIHostingController(rootView: connectionView) + self.connectionController = connectionController + let connectionViewProxy = connectionController.view! + connectionViewProxy.backgroundColor = .clear + + addChild(connectionController) + connectionController.didMove(toParent: self) + view.addConstrainedSubviews([activityIndicator, connectionViewProxy]) { connectionViewProxy.pinEdgesToSuperview(.all()) } }