diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/UserDefaults+NetworkProtectionShared.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/UserDefaults+NetworkProtectionShared.swift index 3c85dee8ee..d1ec419a3b 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/UserDefaults+NetworkProtectionShared.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/UserDefaults+NetworkProtectionShared.swift @@ -46,3 +46,21 @@ extension UserDefaults { }.eraseToAnyPublisher() } } + +extension NetworkProtectionUI.OnboardingStatus { + /// The default onboarding status. + /// + /// This default is defined in our browser app because it's inherently tied to the specific build-configuration of the browser + /// app: + /// - For AppStore builds the default is asking the user to allow the VPN configuration. + /// - For DeveloperID builds the default is asking the user to allow the System Extension. + /// + public static let `default`: OnboardingStatus = { +#if NETP_SYSTEM_EXTENSION + .isOnboarding(step: .userNeedsToAllowExtension) +#else + .isOnboarding(step: .userNeedsToAllowVPNConfiguration) +#endif + }() +} + diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Configuration/OnboardingStatus.swift b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Configuration/OnboardingStatus.swift index 07a0eeafc0..a31c678084 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Configuration/OnboardingStatus.swift +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Configuration/OnboardingStatus.swift @@ -31,19 +31,6 @@ public enum OnboardingStatus: RawRepresentable { case isOnboarding(step: OnboardingStep) - /// The default onboarding status. - /// - /// For AppStore builds the default is asking the user to allow the VPN configuration. - /// For DeveloperID builds the default is asking the user to allow the System Extension. - /// - public static let `default`: OnboardingStatus = { -#if NETP_SYSTEM_EXTENSION - .isOnboarding(step: .userNeedsToAllowExtension) -#else - .isOnboarding(step: .userNeedsToAllowVPNConfiguration) -#endif - }() - public init?(rawValue: Int) { if rawValue == 0 { self = .completed diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/NetworkProtectionColor.swift b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/NetworkProtectionColor.swift new file mode 100644 index 0000000000..298a290694 --- /dev/null +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/NetworkProtectionColor.swift @@ -0,0 +1,42 @@ +// +// NetworkProtectionColor.swift +// +// Copyright © 2022 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import SwiftUI + +extension Color { + /// Convenience initializer to make it easier to use our custom colors. + /// + init(_ networkProtectionColor: NetworkProtectionColor) { + self = networkProtectionColor.asColor + } +} + +/// NetworkProtectionUI bundled color definitions +/// +enum NetworkProtectionColor: String { + case alertBubbleBackground = "AlertBubbleBackground" + case defaultText = "TextColor" + case linkColor = "LinkBlueColor" + case onboardingStepBorder = "OnboardingStepBorderColor" + case onboardingStepBackground = "OnboardingStepBackgroundColor" + + var asColor: Color { + Color(rawValue, bundle: .module) + } +} diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/OnboardingStepBackgroundColor.colorset/Contents.json b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/OnboardingStepBackgroundColor.colorset/Contents.json new file mode 100644 index 0000000000..d18777e9b1 --- /dev/null +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/OnboardingStepBackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.010", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.030", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/OnboardingStepBorderColor.colorset/Contents.json b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/OnboardingStepBorderColor.colorset/Contents.json new file mode 100644 index 0000000000..524f806670 --- /dev/null +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Colors/OnboardingStepBorderColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.090", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.090", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/Contents.json b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/Contents.json index d74f6e1bce..582d9c46d0 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "exention-icon@2x.png", + "filename" : "image 40.pdf", "idiom" : "universal" } ], diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/exention-icon@2x.png b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/exention-icon@2x.png deleted file mode 100644 index 32d03e4fcd..0000000000 Binary files a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/exention-icon@2x.png and /dev/null differ diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/image 40.pdf b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/image 40.pdf new file mode 100644 index 0000000000..624d0b06f3 Binary files /dev/null and b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vault-icon.imageset/image 40.pdf differ diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/Contents.json b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/Contents.json index ee47f5c833..24ab8245de 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "vpn-icon@2x 1.png", + "filename" : "image 39.pdf", "idiom" : "universal" } ], diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/image 39.pdf b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/image 39.pdf new file mode 100644 index 0000000000..5c0951b202 Binary files /dev/null and b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/image 39.pdf differ diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/vpn-icon@2x 1.png b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/vpn-icon@2x 1.png deleted file mode 100644 index 3cbed5ec6a..0000000000 Binary files a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Icons/apple-vpn-icon.imageset/vpn-icon@2x 1.png and /dev/null differ diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Contents.json b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Contents.json index 5a02b1cfd9..38320bc8c8 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Contents.json +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "settings-example@2x 2.png", + "filename" : "Mask group.pdf", "idiom" : "universal" } ], diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Mask group.pdf b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Mask group.pdf new file mode 100644 index 0000000000..60aef4ae93 Binary files /dev/null and b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/Mask group.pdf differ diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/settings-example@2x 2.png b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/settings-example@2x 2.png deleted file mode 100644 index 704c65a3aa..0000000000 Binary files a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Resources/Assets.xcassets/Images/allow-sysex-screenshot.imageset/settings-example@2x 2.png and /dev/null differ diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepView.swift b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepView.swift index ecaecf00c1..3b713d208c 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepView.swift +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepView.swift @@ -19,7 +19,124 @@ import Foundation import SwiftUI +private let defaultTextColor = Color("TextColor", bundle: .module) + +fileprivate enum NetworkProtectionFont { + static var connectionStatusDetail: Font { + .system(size: 13, weight: .regular, design: .default) + } + + static var content: Font { + .system(size: 13, weight: .regular, design: .default) + } + + static var description: Font { + .system(size: 13, weight: .regular, design: .default) + } + + static var menu: Font { + .system(size: 13, weight: .regular, design: .default) + } + + static var label: Font { + .system(size: 13, weight: .regular, design: .default) + } + + static var sectionHeader: Font { + .system(size: 12, weight: .semibold, design: .default) + } + + static var timer: Font { + .system(size: 13, weight: .regular, design: .default) + .monospacedDigit() + } + + static var stepTitle: Font { + .system(size: 13, weight: .bold, design: .default) + } +} + +private enum Opacity { + static func connectionStatusDetail(colorScheme: ColorScheme) -> Double { + colorScheme == .light ? Double(0.6) : Double(0.5) + } + + static let content = Double(0.58) + static let label = Double(0.9) + static let description = Double(0.9) + static let menu = Double(0.9) + static let link = Double(1) + + static func sectionHeader(colorScheme: ColorScheme) -> Double { + colorScheme == .light ? Double(0.84) : Double(0.85) + } + + static func timer(colorScheme: ColorScheme) -> Double { + colorScheme == .light ? Double(0.6) : Double(0.5) + } + + static let stepTitle = Double(0.9) +} + +fileprivate extension View { + func applyConnectionStatusDetailAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.connectionStatusDetail(colorScheme: colorScheme)) + .font(NetworkProtectionFont.connectionStatusDetail) + .foregroundColor(defaultTextColor) + } + + func applyContentAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.content) + .font(NetworkProtectionFont.content) + .foregroundColor(defaultTextColor) + } + + func applyDescriptionAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.description) + .font(NetworkProtectionFont.description) + .foregroundColor(defaultTextColor) + } + + func applyMenuAttributes() -> some View { + opacity(Opacity.menu) + .font(NetworkProtectionFont.menu) + .foregroundColor(defaultTextColor) + } + + func applyLinkAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.link) + .font(NetworkProtectionFont.content) + .foregroundColor(defaultTextColor) + } + + func applyLabelAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.label) + .font(NetworkProtectionFont.label) + .foregroundColor(defaultTextColor) + } + + func applySectionHeaderAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.sectionHeader(colorScheme: colorScheme)) + .font(NetworkProtectionFont.sectionHeader) + .foregroundColor(defaultTextColor) + } + + func applyTimerAttributes(colorScheme: ColorScheme) -> some View { + opacity(Opacity.timer(colorScheme: colorScheme)) + .font(NetworkProtectionFont.timer) + .foregroundColor(defaultTextColor) + } + + func applyStepTitleAttributes(colorScheme: ColorScheme) -> some View { + self.font(Font.custom("SF Pro Text", size: 13).weight(.bold)) + .foregroundColor(.black) + } +} + struct OnboardingStepView: View { + @Environment(\.colorScheme) var colorScheme + + // MARK: - Model private let model: Model @@ -31,50 +148,69 @@ struct OnboardingStepView: View { // MARK: - View + struct Constants { + static let IconsMenuMac: Color = .black.opacity(0.9) + } + public var body: some View { VStack(spacing: 0) { + HStack(alignment: .top, spacing: 12) { + Image(model.icon) - HStack(alignment: .top, spacing: 0) { + HStack { + VStack(alignment: .leading, spacing: 5) { + Text(model.title) + .applyStepTitleAttributes(colorScheme: colorScheme) + .multilineText() - Image(model.icon) - .resizable() // Just a note that this is only necessary right now due to the asset being a really small placeholder. This attribute should go away when we replace with the final assets. - .frame(width: 40, height: 40) - .padding(.trailing, 12) + model.description.reduce(Text("")) { previous, fragment in + var newText = Text(fragment.text) - VStack(alignment: .leading, spacing: 0) { - Text(model.title) - .multilineText() + if fragment.isBold { + newText = newText.bold() + } - Text(model.description) + return previous + newText + } + .font(Font.custom("SF Pro Text", size: 13)) + .foregroundColor(.black) .multilineText() - Button(model.title, action: model.action) + Button(model.actionTitle, action: model.action) + .buttonStyle(.plain) + .padding(.horizontal, 12) + .padding(.vertical, 0) + .frame(height: 20, alignment: .center) + .background(Color.white) + .cornerRadius(5) + .shadow(color: .black.opacity(0.1), radius: 0.5, x: 0, y: 1) + .shadow(color: .black.opacity(0.05), radius: 0.5, x: 0, y: 0) + .overlay( + RoundedRectangle(cornerRadius: 5) + .inset(by: -0.25) + .stroke(.black.opacity(0.1), lineWidth: 0.5) + ) + } Spacer() } - .layoutPriority(1) - - Spacer() } - .layoutPriority(1) - .padding(.vertical, 16) + .padding(.top, 16) + .padding(.bottom, model.actionScreenshot != nil ? 4 : 16) .padding(.horizontal, 10) - .frame(maxWidth: .infinity) - .shadow(color: .black.opacity(0.18), radius: 5, x: 0, y: 0) - - Image(.allowSysexScreenshot) - .resizable() // Just a note that this is only necessary right now due to the asset being a really small placeholder. This attribute should go away when we replace with the final assets. - .frame(width: 321, height: 130) - Spacer() + if let actionScreenshot = model.actionScreenshot { + Image(actionScreenshot) + .shadow(color: .black.opacity(0.18), radius: 5, x: 0, y: 0) + } } - .layoutPriority(1) + .cornerRadius(8) .background( RoundedRectangle(cornerRadius: 8, style: .circular) - .stroke(Color.white.opacity(0.06)) + .stroke(Color(.onboardingStepBorder)) .background( RoundedRectangle(cornerRadius: 8, style: .circular) - .fill(Color.white.opacity(0.03)) + .fill(Color(.onboardingStepBackground)) )) } } diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepViewModel.swift b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepViewModel.swift index 67f646fe05..b8bb17d22e 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepViewModel.swift +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/OnboardingStepView/OnboardingStepViewModel.swift @@ -16,6 +16,7 @@ // limitations under the License. // +import AppKit import Foundation extension OnboardingStepView { @@ -23,6 +24,16 @@ extension OnboardingStepView { /// Model for AllowSystemExtensionView /// final class Model: ObservableObject { + struct StyledTextFragment { + let text: String + let isBold: Bool + + init(text: String, isBold: Bool = false) { + self.text = text + self.isBold = isBold + } + } + private let step: OnboardingStep init(step: OnboardingStep) { @@ -41,27 +52,48 @@ extension OnboardingStepView { var title: String { switch step { case .userNeedsToAllowExtension: - return "Allow extension" + return "Step 1 of 2: Allow System Extension" case .userNeedsToAllowVPNConfiguration: - return "Allow configuration" + return "Step 2 of 2: Add VPN Configuration" } } - var description: String { + var description: [StyledTextFragment] { switch step { case .userNeedsToAllowExtension: - return "Allow extension description" + return [ + .init(text: "Open "), + .init(text: "System Settings", isBold: true), + .init(text: " to "), + .init(text: "Privacy & Security", isBold: true), + .init(text: ". Scroll and select "), + .init(text: "Allow", isBold: true), + .init(text: " for DuckDuckGo software.") + ] case .userNeedsToAllowVPNConfiguration: - return "Allow configuration description" + return [ + .init(text: "Select "), + .init(text: "Allow", isBold: true), + .init(text: " when prompted to finish setting up Network Protection.") + ] } } var actionTitle: String { switch step { case .userNeedsToAllowExtension: - return "Allow extension action" + return "Open System Settings..." + case .userNeedsToAllowVPNConfiguration: + return "Add VPN Configuration..." + } + } + + var actionScreenshot: NetworkProtectionAsset? { + switch step { + case .userNeedsToAllowExtension: + return .allowSysexScreenshot case .userNeedsToAllowVPNConfiguration: - return "Allow configuration action" + return nil } } @@ -69,7 +101,8 @@ extension OnboardingStepView { switch step { case .userNeedsToAllowExtension: return { - print("Allow extension clicked") + let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Security")! + NSWorkspace.shared.open(url) } case .userNeedsToAllowVPNConfiguration: return { diff --git a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift index 27b2f6e0dd..8ffe4f96ad 100644 --- a/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift +++ b/LocalPackages/NetworkProtectionUI/Sources/NetworkProtectionUI/Views/StatusView/NetworkProtectionStatusView.swift @@ -166,7 +166,6 @@ public struct NetworkProtectionStatusView: View { OnboardingStepView(model: onboardingStepViewModel) .padding(.horizontal, 5) .padding(.top, 5) - .layoutPriority(1) } else { if let healthWarning = model.issueDescription { connectionHealthWarningView(message: healthWarning)