From ed14e916b47e5cedec81c313627eb1acffe892bf Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Wed, 4 Dec 2024 10:18:22 +0100 Subject: [PATCH] Add toggle in connection view --- ios/MullvadVPN.xcodeproj/project.pbxproj | 8 ++ .../Root/RootContainerViewController.swift | 2 +- .../FeatureIndicators/ConnectionView.swift | 96 ++++++++++++++++--- ...FeatureIndicatorsScrollContainerView.swift | 75 +++++++++++++++ .../HeaderBarSwiftUIHostedView.swift | 30 ++++++ 5 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsScrollContainerView.swift create mode 100644 ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/HeaderBarSwiftUIHostedView.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ee87bbf32a11..08937ee14a50 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -704,6 +704,7 @@ 85EC620C2B838D10005AFFB5 /* MullvadAPIWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85557B132B5983CF00795FE1 /* MullvadAPIWrapper.swift */; }; 85FB5A0C2B6903990015DCED /* WelcomePage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0B2B6903990015DCED /* WelcomePage.swift */; }; 85FB5A102B6960A30015DCED /* AccountDeletionPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85FB5A0F2B6960A30015DCED /* AccountDeletionPage.swift */; }; + A90216DB2D0C3E03001626E3 /* HeaderBarSwiftUIHostedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90216DA2D0C3E03001626E3 /* HeaderBarSwiftUIHostedView.swift */; }; A90763B02B2857D50045ADF0 /* Socks5ConnectCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A02B2857D50045ADF0 /* Socks5ConnectCommand.swift */; }; A90763B12B2857D50045ADF0 /* Socks5Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A12B2857D50045ADF0 /* Socks5Endpoint.swift */; }; A90763B22B2857D50045ADF0 /* Socks5EndpointReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90763A22B2857D50045ADF0 /* Socks5EndpointReader.swift */; }; @@ -755,6 +756,7 @@ A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; }; A988DF272ADE86ED00D807EF /* WireGuardObfuscationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */; }; A988DF2A2ADE880300D807EF /* TunnelSettingsV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */; }; + A98E31752D0B1CDC00C092B7 /* FeatureIndicatorsScrollContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98E31742D0B1CDC00C092B7 /* FeatureIndicatorsScrollContainerView.swift */; }; A992DA202C24709F00DE7CE5 /* MullvadRustRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */; settings = {ATTRIBUTES = (Public, ); }; }; A992DA232C24709F00DE7CE5 /* MullvadRustRuntime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A992DA1D2C24709F00DE7CE5 /* MullvadRustRuntime.framework */; }; A992DA242C24709F00DE7CE5 /* MullvadRustRuntime.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A992DA1D2C24709F00DE7CE5 /* MullvadRustRuntime.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -2083,6 +2085,7 @@ A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DevicesProxy+Stubs.swift"; sourceTree = ""; }; A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIProxy+Stubs.swift"; sourceTree = ""; }; A900E9BF2ACC661900C95F67 /* AccessTokenManager+Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccessTokenManager+Stubs.swift"; sourceTree = ""; }; + A90216DA2D0C3E03001626E3 /* HeaderBarSwiftUIHostedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderBarSwiftUIHostedView.swift; sourceTree = ""; }; A90763A02B2857D50045ADF0 /* Socks5ConnectCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socks5ConnectCommand.swift; sourceTree = ""; }; A90763A12B2857D50045ADF0 /* Socks5Endpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socks5Endpoint.swift; sourceTree = ""; }; A90763A22B2857D50045ADF0 /* Socks5EndpointReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Socks5EndpointReader.swift; sourceTree = ""; }; @@ -2137,6 +2140,7 @@ A98502022B627B120061901E /* LocalNetworkProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkProbe.swift; sourceTree = ""; }; A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardObfuscationSettings.swift; sourceTree = ""; }; A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV3.swift; sourceTree = ""; }; + A98E31742D0B1CDC00C092B7 /* FeatureIndicatorsScrollContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIndicatorsScrollContainerView.swift; sourceTree = ""; }; A98F1B502C19C48D003C869E /* EphemeralPeerExchangeActorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerExchangeActorTests.swift; sourceTree = ""; }; A992DA1D2C24709F00DE7CE5 /* MullvadRustRuntime.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadRustRuntime.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A992DA1F2C24709F00DE7CE5 /* MullvadRustRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadRustRuntime.h; sourceTree = ""; }; @@ -4088,6 +4092,8 @@ F0B4957B2D03154200CFEC2A /* FeatureIndicatorsView.swift */, F0ADF1D22D01B6B400299F09 /* FeatureIndicatorsViewModel.swift */, 7AFBE3882D08915D002335FC /* FI_TunnelViewController.swift */, + A98E31742D0B1CDC00C092B7 /* FeatureIndicatorsScrollContainerView.swift */, + A90216DA2D0C3E03001626E3 /* HeaderBarSwiftUIHostedView.swift */, ); path = FeatureIndicators; sourceTree = ""; @@ -6113,6 +6119,7 @@ 58CE5E66224146200008646E /* LoginViewController.swift in Sources */, F0C6FA852A6A733700F521F0 /* InAppPurchaseInteractor.swift in Sources */, 58CEB2F92AFD136E00E6E088 /* UIBackgroundConfiguration+Extensions.swift in Sources */, + A98E31752D0B1CDC00C092B7 /* FeatureIndicatorsScrollContainerView.swift in Sources */, 5878F50029CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift in Sources */, A98502032B627B120061901E /* LocalNetworkProbe.swift in Sources */, 7A6F2FA92AFD0842006D0856 /* CustomDNSDataSource.swift in Sources */, @@ -6224,6 +6231,7 @@ 5827B0BF2B14B37D00CCBBA1 /* Publisher+PreviousValue.swift in Sources */, 7A9CCCB62A96302800DD6A34 /* OutOfTimeCoordinator.swift in Sources */, 5827B0AA2B0F4C9100CCBBA1 /* EditAccessMethodViewControllerDelegate.swift in Sources */, + A90216DB2D0C3E03001626E3 /* HeaderBarSwiftUIHostedView.swift in Sources */, F050AE5E2B739A73003F4EDB /* LocationDataSourceProtocol.swift in Sources */, A99E5EE22B762ED30033F241 /* ProblemReportViewController+ViewManagement.swift in Sources */, 7A5869A22B502EA800640D27 /* MethodSettingsSectionIdentifier.swift in Sources */, diff --git a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift index 2c405395bc40..712ca4ba82d4 100644 --- a/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift +++ b/ios/MullvadVPN/Containers/Root/RootContainerViewController.swift @@ -12,7 +12,7 @@ import UIKit enum HeaderBarStyle { case transparent, `default`, unsecured, secured - fileprivate func backgroundColor() -> UIColor { + func backgroundColor() -> UIColor { switch self { case .transparent: return UIColor.clear diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift index 3a1bf7d9afe3..01a90e2bf448 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift @@ -6,7 +6,10 @@ // Copyright © 2024 Mullvad VPN AB. All rights reserved. // +import MullvadREST import MullvadSettings +import MullvadTypes +import Network import SwiftUI typealias ButtonAction = (ConnectionViewViewModel.TunnelControlAction) -> Void @@ -14,9 +17,11 @@ typealias ButtonAction = (ConnectionViewViewModel.TunnelControlAction) -> Void struct ConnectionView: View { @StateObject var viewModel: ConnectionViewViewModel @StateObject var indicatorsViewModel: FeatureIndicatorsViewModel + @State var expandConnectionDetails = false var action: ButtonAction? var onContentUpdate: (() -> Void)? + var onChevronToggle: (() -> Void)? var body: some View { VStack(spacing: 22) { @@ -33,6 +38,17 @@ struct ConnectionView: View { if !indicatorsViewModel.chips.isEmpty { FeatureIndicatorsView(viewModel: indicatorsViewModel) } + ConnectionPanel(viewModel: viewModel, onChevronToggle: { + expandConnectionDetails.toggle() + }, isExpanded: $expandConnectionDetails) + Divider() + .background(UIColor.secondaryTextColor.color) + FeatureIndicatorsScrollContainerView( + isExpanded: $expandConnectionDetails, + content: { Text("Hello") } + ) + .frame(maxWidth: .infinity) + .border(.white) ButtonPanel(viewModel: viewModel, action: action) } @@ -60,31 +76,83 @@ struct ConnectionView: View { indicatorsViewModel: FeatureIndicatorsViewModel(tunnelSettings: LatestTunnelSettings(), ipOverrides: []) ) { action in print(action) + let selectedRelays = SelectedRelays( + entry: nil, + exit: SelectedRelay( + endpoint: MullvadEndpoint( + ipv4Relay: IPv4Endpoint(ip: .loopback, port: 42), + ipv4Gateway: IPv4Address.loopback, + ipv6Gateway: IPv6Address.loopback, + publicKey: Data() + ), + hostname: "se-got-wg-001", + location: Location( + country: "Sweden", + countryCode: "se", + city: "Gothenburg", + cityCode: "got", + latitude: 42, + longitude: 42 + ) + ), + retryAttempt: 0 + ) + let connectedState = TunnelState.connected(selectedRelays, isPostQuantum: true, isDaita: true) + + return ZStack { + VStack { + HeaderBarSwiftUIHostedView() + .frame(maxHeight: 100) + ConnectionView( + viewModel: ConnectionViewViewModel(tunnelState: connectedState), + action: { action in print(action) }, + onContentUpdate: { print("On content Update") }, + onChevronToggle: { print("Chevron toggle") } + ) + } } .background(UIColor.secondaryColor.color) } private struct ConnectionPanel: View { @StateObject var viewModel: ConnectionViewViewModel + var onChevronToggle: (() -> Void)? + var isExpanded: Binding var body: some View { - VStack(alignment: .leading) { - Text(viewModel.localizedTitleForSecureLabel) - .textCase(.uppercase) - .font(.title3.weight(.semibold)) - .foregroundStyle(viewModel.textColorForSecureLabel.color) - .padding(.bottom, 4) - - if let countryAndCity = viewModel.titleForCountryAndCity, let server = viewModel.titleForServer { - Text(countryAndCity) + HStack(alignment: .top) { + VStack(alignment: .leading) { + Text(viewModel.localizedTitleForSecureLabel) + .textCase(.uppercase) .font(.title3.weight(.semibold)) - .foregroundStyle(UIColor.primaryTextColor.color) - Text(server) - .font(.body) - .foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6)) + .foregroundStyle(viewModel.textColorForSecureLabel.color) + .padding(.bottom, 4) + if let countryAndCity = viewModel.titleForCountryAndCity, let server = viewModel.titleForServer { + Text(countryAndCity) + .font(.title3.weight(.semibold)) + .foregroundStyle(UIColor.primaryTextColor.color) + Text(server) + .font(.body) + .foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6)) + } + } + .accessibilityLabel(viewModel.localizedAccessibilityLabel) + if case .connected = viewModel.tunnelState { + if let onChevronToggle { + Spacer() + Button(action: onChevronToggle) { + Image(.iconChevron) + .renderingMode(.template) + .rotationEffect(isExpanded.wrappedValue ? .degrees(-90) : .degrees(90)) + .frame(width: 44, height: 44, alignment: .topTrailing) + .foregroundStyle(.white) + .transaction { transaction in + transaction.animation = nil + } + } + } } } - .accessibilityLabel(viewModel.localizedAccessibilityLabel) } } diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsScrollContainerView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsScrollContainerView.swift new file mode 100644 index 000000000000..c4a5ff0f2355 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/FeatureIndicatorsScrollContainerView.swift @@ -0,0 +1,75 @@ +// +// FeatureIndicatorsScrollContainerView.swift +// MullvadVPN +// +// Created by Marco Nikic on 2024-12-12. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct FeatureIndicatorsScrollContainerView: View { + var isExpanded: Binding + @ViewBuilder + let content: ContentView + + var body: some View { + ScrollView { + content + .frame(maxWidth: .infinity) + .background(.blue) + } + .frame(maxHeight: isExpanded.wrappedValue ? .infinity : 40) + } +} + +#Preview { + ExampleView().background(UIColor.secondaryColor.color) +} + +private struct ExampleView: View { + @State var isExpanded = false + + var body: some View { + VStack { + Button(action: { + isExpanded.toggle() + }, label: { + Text("Toggle layout") + }) + FeatureIndicatorsScrollContainerView(isExpanded: $isExpanded) { + if isExpanded { + BigLayoutView() + } else { + SmallLayoutView() + } + } + } + } +} + +private struct SmallLayoutView: View { + var body: some View { + HStack { + ForEach(0 ..< 3) { index in + Text("hehehjr \(index)") + } + } + } +} + +private struct BigLayoutView: View { + var body: some View { + Group { + VStack { + ForEach(0 ..< 5) { _ in + HStack { + ForEach(0 ..< 8) { index in + Text("hello \(index)") + } + } + } + } + } + } +} diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/HeaderBarSwiftUIHostedView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/HeaderBarSwiftUIHostedView.swift new file mode 100644 index 000000000000..dc5ddde6209e --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/HeaderBarSwiftUIHostedView.swift @@ -0,0 +1,30 @@ +// +// HeaderBarSwiftUIHostedView.swift +// MullvadVPN +// +// Created by Marco Nikic on 2024-12-13. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import SwiftUI + +struct HeaderBarSwiftUIHostedView: UIViewRepresentable { + typealias UIViewType = HeaderBarView + + func makeUIView(context: Context) -> HeaderBarView { + let headerBarView = HeaderBarView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + headerBarView.translatesAutoresizingMaskIntoConstraints = false + headerBarView.insetsLayoutMarginsFromSafeArea = false + + var headerBarPresentation = HeaderBarPresentation.default + headerBarView.backgroundColor = headerBarPresentation.style.backgroundColor() + headerBarView.showsDivider = headerBarPresentation.showsDivider + + return headerBarView + } + + func updateUIView(_ uiView: HeaderBarView, context: Context) { + print("update") + } +}