diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 05a800581dcf..5ca57a22904b 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -462,6 +462,8 @@ 7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; }; 7A0B311E2B303A0D004B12E0 /* AccessbilityIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */; }; 7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */; }; + 7A0EAE9A2D01B41500D3EB8B /* MainButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */; }; + 7A0EAE9E2D01BCBF00D3EB8B /* View+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0EAE9D2D01BCBF00D3EB8B /* View+Size.swift */; }; 7A11DD0B2A9495D400098CD8 /* AppRoutes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5802EBC42A8E44AC00E5CE4C /* AppRoutes.swift */; }; 7A12D0762B062D5C00E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */; }; 7A12D0772B062D6500E9602D /* URLSessionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */; }; @@ -563,7 +565,6 @@ 7A8A19052CE4E9A9000BCB5B /* SwitchRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19042CE4E9A5000BCB5B /* SwitchRowView.swift */; }; 7A8A19072CE4E9D3000BCB5B /* SettingsInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19062CE4E9CC000BCB5B /* SettingsInfoView.swift */; }; 7A8A190A2CE5FFE9000BCB5B /* SettingsDAITAView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */; }; - 7A8A190C2CE618D3000BCB5B /* View+Size.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190B2CE618CE000BCB5B /* View+Size.swift */; }; 7A8A190E2CEB77C1000BCB5B /* SettingsRowViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190D2CEB77B7000BCB5B /* SettingsRowViewFooter.swift */; }; 7A8A19102CEE391B000BCB5B /* RowSeparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */; }; 7A8A19122CEF1E68000BCB5B /* SettingsInfoContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A8A19112CEF1E58000BCB5B /* SettingsInfoContainerView.swift */; }; @@ -605,6 +606,10 @@ 7A9F293D2CAD2FD5005F2089 /* InfoModalConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9F293C2CAD2FCF005F2089 /* InfoModalConfig.swift */; }; 7A9FA1422A2E3306000B728D /* CheckboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1412A2E3306000B728D /* CheckboxView.swift */; }; 7A9FA1442A2E3FE5000B728D /* CheckableSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */; }; + 7AA130992CFF365D00640DF9 /* ConnectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA130982CFF365A00640DF9 /* ConnectionView.swift */; }; + 7AA1309B2D0048D800640DF9 /* MainButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA1309A2D0048D800640DF9 /* MainButton.swift */; }; + 7AA1309F2D007B2500640DF9 /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA1309E2D007B2500640DF9 /* VisualEffectView.swift */; }; + 7AA130A12D01B1E200640DF9 /* SplitMainButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA130A02D01B1E200640DF9 /* SplitMainButton.swift */; }; 7AA513862BC91C6B00D081A4 /* LogRotationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */; }; 7AA7046A2C8EFE2B0045699D /* StoredRelays.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AA704682C8EFE050045699D /* StoredRelays.swift */; }; 7AB2B6702BA1EB8C00B03E3B /* ListCustomListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */; }; @@ -1834,6 +1839,8 @@ 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = ""; }; 7A0B311D2B303A0D004B12E0 /* AccessbilityIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessbilityIdentifier.swift; sourceTree = ""; }; 7A0C0F622A979C4A0058EFCE /* Coordinator+Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Coordinator+Router.swift"; sourceTree = ""; }; + 7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainButtonStyle.swift; sourceTree = ""; }; + 7A0EAE9D2D01BCBF00D3EB8B /* View+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Size.swift"; sourceTree = ""; }; 7A12D0752B062D5C00E9602D /* URLSessionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionProtocol.swift; sourceTree = ""; }; 7A1A26422A2612AE00B978AA /* PaymentAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentAlertPresenter.swift; sourceTree = ""; }; 7A1A26442A29CEF700B978AA /* RelayFilterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterViewController.swift; sourceTree = ""; }; @@ -1923,7 +1930,6 @@ 7A8A19042CE4E9A5000BCB5B /* SwitchRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchRowView.swift; sourceTree = ""; }; 7A8A19062CE4E9CC000BCB5B /* SettingsInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoView.swift; sourceTree = ""; }; 7A8A19092CE5FFDF000BCB5B /* SettingsDAITAView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDAITAView.swift; sourceTree = ""; }; - 7A8A190B2CE618CE000BCB5B /* View+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Size.swift"; sourceTree = ""; }; 7A8A190D2CEB77B7000BCB5B /* SettingsRowViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsRowViewFooter.swift; sourceTree = ""; }; 7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RowSeparator.swift; sourceTree = ""; }; 7A8A19112CEF1E58000BCB5B /* SettingsInfoContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInfoContainerView.swift; sourceTree = ""; }; @@ -1962,6 +1968,11 @@ 7A9F293C2CAD2FCF005F2089 /* InfoModalConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoModalConfig.swift; sourceTree = ""; }; 7A9FA1412A2E3306000B728D /* CheckboxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxView.swift; sourceTree = ""; }; 7A9FA1432A2E3FE5000B728D /* CheckableSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckableSettingsCell.swift; sourceTree = ""; }; + 7AA130982CFF365A00640DF9 /* ConnectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionView.swift; sourceTree = ""; }; + 7AA1309A2D0048D800640DF9 /* MainButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainButton.swift; sourceTree = ""; }; + 7AA1309C2D0072F900640DF9 /* View+Size.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Size.swift"; sourceTree = ""; }; + 7AA1309E2D007B2500640DF9 /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = ""; }; + 7AA130A02D01B1E200640DF9 /* SplitMainButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitMainButton.swift; sourceTree = ""; }; 7AA513852BC91C6B00D081A4 /* LogRotationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogRotationTests.swift; sourceTree = ""; }; 7AA704682C8EFE050045699D /* StoredRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredRelays.swift; sourceTree = ""; }; 7AB2B66E2BA1EB8C00B03E3B /* ListCustomListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListCustomListViewController.swift; sourceTree = ""; }; @@ -2982,6 +2993,7 @@ 583FE01E29C197D5006E85F9 /* Tunnel */ = { isa = PBXGroup; children = ( + 7AA130972CFF364F00640DF9 /* FeatureIndicators */, 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */, 5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */, 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */, @@ -3012,10 +3024,14 @@ F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */, 7A9F29382CABFAEC005F2089 /* InfoHeaderView.swift */, 7A5869942B32E9C700640D27 /* LinkButton.swift */, + 7AA1309A2D0048D800640DF9 /* MainButton.swift */, + 7A0EAE992D01B41500D3EB8B /* MainButtonStyle.swift */, 7A8A190F2CEE3918000BCB5B /* RowSeparator.swift */, 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */, + 7AA130A02D01B1E200640DF9 /* SplitMainButton.swift */, E1FD0DF428AA7CE400299DB4 /* StatusActivityView.swift */, 58EF581025D69DB400AEBA94 /* StatusImageView.swift */, + 7AA1309E2D007B2500640DF9 /* VisualEffectView.swift */, ); path = Views; sourceTree = ""; @@ -3091,7 +3107,7 @@ 7A21DACE2A30AA3700A787A9 /* UITextField+Appearance.swift */, 5878F4FF29CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift */, 7A516C2D2B6D357500BBD33D /* URL+Scoping.swift */, - 7A8A190B2CE618CE000BCB5B /* View+Size.swift */, + 7A0EAE9D2D01BCBF00D3EB8B /* View+Size.swift */, 7A8A18FA2CE4B66C000BCB5B /* View+TapAreaSize.swift */, ); path = Extensions; @@ -3593,6 +3609,7 @@ A9D9A4C12C36D53C004088DD /* MullvadRustRuntimeTests */, 58CE5E61224146200008646E /* Products */, 584F991F2902CBDD001F858D /* Frameworks */, + 7A0EAE982D01B29E00D3EB8B /* Recovered References */, ); sourceTree = ""; }; @@ -3893,6 +3910,14 @@ path = Edit; sourceTree = ""; }; + 7A0EAE982D01B29E00D3EB8B /* Recovered References */ = { + isa = PBXGroup; + children = ( + 7AA1309C2D0072F900640DF9 /* View+Size.swift */, + ); + name = "Recovered References"; + sourceTree = ""; + }; 7A2960F72A964A3500389B82 /* Alert */ = { isa = PBXGroup; children = ( @@ -4024,6 +4049,14 @@ path = SelectLocation; sourceTree = ""; }; + 7AA130972CFF364F00640DF9 /* FeatureIndicators */ = { + isa = PBXGroup; + children = ( + 7AA130982CFF365A00640DF9 /* ConnectionView.swift */, + ); + path = FeatureIndicators; + sourceTree = ""; + }; 7AD63A422CDA661B00445268 /* Extensions */ = { isa = PBXGroup; children = ( @@ -5830,6 +5863,7 @@ 7A8A19282CF603EB000BCB5B /* SettingsViewControllerFactory.swift in Sources */, 58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */, 7A8A19072CE4E9D3000BCB5B /* SettingsInfoView.swift in Sources */, + 7AA1309B2D0048D800640DF9 /* MainButton.swift in Sources */, 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */, 5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */, 7A8A19052CE4E9A9000BCB5B /* SwitchRowView.swift in Sources */, @@ -5900,6 +5934,7 @@ 586C0D872B03D39600E7CDD7 /* AccessMethodCellReuseIdentifier.swift in Sources */, 7A9CCCBD2A96302800DD6A34 /* LoginCoordinator.swift in Sources */, 7A7B3AB62C6DE4DA00D4BCCE /* RestorePurchasesView.swift in Sources */, + 7A0EAE9E2D01BCBF00D3EB8B /* View+Size.swift in Sources */, 58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */, F09A29822A9F8AD200EA3B6F /* RedeemVoucherInteractor.swift in Sources */, 58138E61294871C600684F0C /* DeviceDataThrottling.swift in Sources */, @@ -5924,7 +5959,10 @@ 586A950E290125F3007BAF2B /* ProductsRequestOperation.swift in Sources */, 7AF9BE902A39F26000DBFEDB /* Collection+Sorting.swift in Sources */, 58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */, + 7A0EAE9A2D01B41500D3EB8B /* MainButtonStyle.swift in Sources */, 58CEB3022AFD365600E6E088 /* SwitchCellContentConfiguration.swift in Sources */, + 7AA130A12D01B1E200640DF9 /* SplitMainButton.swift in Sources */, + 7AA1309F2D007B2500640DF9 /* VisualEffectView.swift in Sources */, 7A9CCCB52A96302800DD6A34 /* AddCreditSucceededCoordinator.swift in Sources */, 7A0C0F632A979C4A0058EFCE /* Coordinator+Router.swift in Sources */, 7A6F2FAB2AFD3097006D0856 /* CustomDNSCellFactory.swift in Sources */, @@ -6038,7 +6076,6 @@ 58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */, 586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */, 58B26E262943522400D5980C /* NotificationProvider.swift in Sources */, - 7A8A190C2CE618D3000BCB5B /* View+Size.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, F0DA87492A9CBA9F006044F1 /* AccountDeviceRow.swift in Sources */, 58FF9FE42B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift in Sources */, @@ -6068,6 +6105,7 @@ 58EFC7712AFB45E500E9F4CB /* SettingsChildCoordinator.swift in Sources */, 7A8A19102CEE391B000BCB5B /* RowSeparator.swift in Sources */, 58CCA01222424D11004F3011 /* SettingsViewController.swift in Sources */, + 7AA130992CFF365D00640DF9 /* ConnectionView.swift in Sources */, F0E8CC0A2A4EE127007ED3B4 /* SetupAccountCompletedContentView.swift in Sources */, 581DA2752A1E283E0046ED47 /* WgKeyRotation.swift in Sources */, 5827B0BB2B14A28300CCBBA1 /* MethodTestingStatusCellContentView.swift in Sources */, diff --git a/ios/MullvadVPN/Extensions/UIColor+Helpers.swift b/ios/MullvadVPN/Extensions/UIColor+Helpers.swift index f3c89ba88974..0157058b89f2 100644 --- a/ios/MullvadVPN/Extensions/UIColor+Helpers.swift +++ b/ios/MullvadVPN/Extensions/UIColor+Helpers.swift @@ -6,9 +6,13 @@ // Copyright © 2019 Mullvad VPN AB. All rights reserved. // -import UIKit +import SwiftUI extension UIColor { + var color: Color { + Color(self) + } + /// Returns the color lighter by the given percent (in range from 0..1) func lightened(by percent: CGFloat) -> UIColor? { darkened(by: -percent) diff --git a/ios/MullvadVPN/Supporting Files/Assets.xcassets/MapPNG.imageset/Contents.json b/ios/MullvadVPN/Supporting Files/Assets.xcassets/MapPNG.imageset/Contents.json new file mode 100644 index 000000000000..9635dca5b05c --- /dev/null +++ b/ios/MullvadVPN/Supporting Files/Assets.xcassets/MapPNG.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "MapPNG.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/MullvadVPN/Supporting Files/Assets.xcassets/MapPNG.imageset/MapPNG.png b/ios/MullvadVPN/Supporting Files/Assets.xcassets/MapPNG.imageset/MapPNG.png new file mode 100644 index 000000000000..89b3dc4f57da Binary files /dev/null and b/ios/MullvadVPN/Supporting Files/Assets.xcassets/MapPNG.imageset/MapPNG.png differ diff --git a/ios/MullvadVPN/UI appearance/UIMetrics.swift b/ios/MullvadVPN/UI appearance/UIMetrics.swift index e68d8b0c3207..1d0f98e8f06c 100644 --- a/ios/MullvadVPN/UI appearance/UIMetrics.swift +++ b/ios/MullvadVPN/UI appearance/UIMetrics.swift @@ -134,6 +134,10 @@ enum UIMetrics { static let inRowHeight: CGFloat = 22 static let outRowHeight: CGFloat = 44 } + + enum MainButton { + static let cornerRadius: CGFloat = 4 + } } extension UIMetrics { diff --git a/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift new file mode 100644 index 000000000000..2984986c2647 --- /dev/null +++ b/ios/MullvadVPN/View controllers/Tunnel/FeatureIndicators/ConnectionView.swift @@ -0,0 +1,93 @@ +// +// ConnectionView.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-12-03. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +// TODO: Replace all hardcoded values with real values dependent on tunnel state. To be addressed in upcoming PR. + +struct ConnectionView: View { + var body: some View { + ZStack { + BlurView() + + VStack(alignment: .leading, spacing: 16) { + ConnectionPanel() + ButtonPanel() + } + .padding(16) + } + .cornerRadius(12) + .padding(16) + // Importing UIView in SwitftUI (see BlurView) has sizing limitations, so we need to help the view + // understand its width constraints. + .frame(maxWidth: UIScreen.main.bounds.width) + } +} + +#Preview { + ZStack { + VStack { + Spacer() + ConnectionView() + } + } + .background(UIColor.secondaryColor.color) + .ignoresSafeArea() +} + +private struct BlurView: View { + var body: some View { + Spacer() + .overlay { + VisualEffectView(effect: UIBlurEffect(style: .dark)) + .opacity(0.8) + } + } +} + +private struct ConnectionPanel: View { + var body: some View { + VStack(alignment: .leading) { + Text("Connected") + .textCase(.uppercase) + .font(.title3.weight(.semibold)) + .foregroundStyle(UIColor.successColor.color) + .padding(.bottom, 4) + Text("Country, City") + .font(.title3.weight(.semibold)) + .foregroundStyle(UIColor.primaryTextColor.color) + Text("Server") + .font(.body) + .foregroundStyle(UIColor.primaryTextColor.color.opacity(0.6)) + } + } +} + +private struct ButtonPanel: View { + var body: some View { + VStack(spacing: 16) { + SplitMainButton( + text: "Switch location", + image: .iconReload, + style: .default, + primaryAction: { + print("Switch location tapped") + }, secondaryAction: { + print("Reload tapped") + } + ) + + MainButton( + text: "Cancel", + style: .danger + ) { + print("Cancel tapped") + } + } + } +} diff --git a/ios/MullvadVPN/Views/MainButton.swift b/ios/MullvadVPN/Views/MainButton.swift new file mode 100644 index 000000000000..9b894f8cc276 --- /dev/null +++ b/ios/MullvadVPN/Views/MainButton.swift @@ -0,0 +1,34 @@ +// +// Untitled.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-12-04. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct MainButton: View { + var text: String + var style: MainButtonStyle.Style + + var action: () -> Void + + var body: some View { + Button(action: action, label: { + HStack { + Spacer() + Text(text) + Spacer() + } + }) + .buttonStyle(MainButtonStyle(style)) + .cornerRadius(UIMetrics.MainButton.cornerRadius) + } +} + +#Preview { + MainButton(text: "Connect", style: .default) { + print("Tapped") + } +} diff --git a/ios/MullvadVPN/Views/MainButtonStyle.swift b/ios/MullvadVPN/Views/MainButtonStyle.swift new file mode 100644 index 000000000000..06a32b560689 --- /dev/null +++ b/ios/MullvadVPN/Views/MainButtonStyle.swift @@ -0,0 +1,49 @@ +// +// MainButtonStyle.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct MainButtonStyle: ButtonStyle { + @State var style: Style + + init(_ style: Style) { + self.style = style + } + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(.horizontal, 8) + .frame(height: 44) + .foregroundColor( + configuration.isPressed + ? UIColor.secondaryTextColor.color + : UIColor.primaryTextColor.color + ) + .background(style.color) + .font(.body.weight(.semibold)) + } +} + +extension MainButtonStyle { + enum Style { + case `default` + case danger + case success + + var color: Color { + switch self { + case .default: + Color(UIColor.primaryColor) + case .danger: + Color(UIColor.dangerColor) + case .success: + Color(UIColor.successColor) + } + } + } +} diff --git a/ios/MullvadVPN/Views/SplitMainButton.swift b/ios/MullvadVPN/Views/SplitMainButton.swift new file mode 100644 index 000000000000..59a5d1b2eafc --- /dev/null +++ b/ios/MullvadVPN/Views/SplitMainButton.swift @@ -0,0 +1,58 @@ +// +// SplitMainButton.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-12-05. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct SplitMainButton: View { + var text: String + var image: ImageResource + var style: MainButtonStyle.Style + + var primaryAction: () -> Void + var secondaryAction: () -> Void + + @State private var width: CGFloat = 0 + + var body: some View { + HStack(spacing: 1) { + Button(action: primaryAction, label: { + HStack { + Spacer() + Text(text) + Spacer() + } + .padding(.trailing, -width) + }) + Button(action: secondaryAction, label: { + Image(image) + .resizable() + .scaledToFit() + .padding(4) + }) + .aspectRatio(1, contentMode: .fit) + .sizeOfView { width = $0.width } + } + .buttonStyle(MainButtonStyle(style)) + .cornerRadius(UIMetrics.MainButton.cornerRadius) + } +} + +#Preview { + SplitMainButton( + text: "Connect", + image: .iconReload, + style: .default, + primaryAction: { + print("Tapped primary") + }, + secondaryAction: { + print("Tapped secondary") + } + ) + .frame(maxWidth: .infinity) +} diff --git a/ios/MullvadVPN/Views/VisualEffectView.swift b/ios/MullvadVPN/Views/VisualEffectView.swift new file mode 100644 index 000000000000..0cad1b06d710 --- /dev/null +++ b/ios/MullvadVPN/Views/VisualEffectView.swift @@ -0,0 +1,21 @@ +// +// VisualEffectView.swift +// MullvadVPN +// +// Created by Jon Petersson on 2024-12-04. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import SwiftUI + +struct VisualEffectView: UIViewRepresentable { + var effect: UIVisualEffect? + + func makeUIView(context: UIViewRepresentableContext) -> UIVisualEffectView { + UIVisualEffectView() + } + + func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext) { + uiView.effect = effect + } +}