Add Home View
@@ -3,17 +3,16 @@ import UIComponents
|
||||
import Theme
|
||||
|
||||
public struct HomeView: View {
|
||||
@ObservedObject private var viewModel = HomeViewViewModel(selectedNetwork: .mixnet)
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
VStack {
|
||||
CustomNavBar(
|
||||
title: "NymVPN".localizedString,
|
||||
rightButtonConfig: settingsButtonConfig()
|
||||
)
|
||||
Spacer()
|
||||
.frame(height: 50)
|
||||
StatusButton(config: .connected)
|
||||
navbar()
|
||||
statusAreaSection()
|
||||
networkSection()
|
||||
connectionSection()
|
||||
connectButton()
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
@@ -25,7 +24,82 @@ public struct HomeView: View {
|
||||
}
|
||||
|
||||
private extension HomeView {
|
||||
func settingsButtonConfig() -> CustomNavBarButtonConfig {
|
||||
CustomNavBarButtonConfig(type: .settingsGear) {}
|
||||
@ViewBuilder
|
||||
func navbar() -> some View {
|
||||
CustomNavBar(
|
||||
title: "NymVPN".localizedString,
|
||||
leftButton: CustomNavBarButton(type: .empty, action: {}),
|
||||
rightButton: CustomNavBarButton(type: .settingsGear, action: {})
|
||||
)
|
||||
Spacer()
|
||||
.frame(height: 50)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func statusAreaSection() -> some View {
|
||||
StatusButton(config: .disconnected)
|
||||
Spacer()
|
||||
.frame(height: 8)
|
||||
|
||||
StatusInfoView()
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func networkSection() -> some View {
|
||||
HStack {
|
||||
Text("selectNetwork".localizedString)
|
||||
.textStyle(.Title.Medium.primary)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
NetworkButton(viewModel: NetworkButtonViewModel(type: .mixnet, selectedNetwork: $viewModel.selectedNetwork))
|
||||
.padding(EdgeInsets(top: 0, leading: 16, bottom: 16, trailing: 16))
|
||||
.onTapGesture {
|
||||
viewModel.selectedNetwork = .mixnet
|
||||
}
|
||||
|
||||
NetworkButton(viewModel: NetworkButtonViewModel(type: .wireguard, selectedNetwork: $viewModel.selectedNetwork))
|
||||
.padding(.horizontal, 16)
|
||||
.onTapGesture {
|
||||
viewModel.selectedNetwork = .wireguard
|
||||
}
|
||||
Spacer()
|
||||
.frame(height: 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func connectionSection() -> some View {
|
||||
HStack {
|
||||
Text("connectTo".localizedString)
|
||||
.foregroundStyle(NymColor.sysOnSurfaceWhite)
|
||||
.textStyle(.Title.Medium.primary)
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
|
||||
VStack {
|
||||
HopButton(country: Country(name: "Germany", code: "de"))
|
||||
Spacer()
|
||||
.frame(height: 24)
|
||||
HopButton(country: Country(name: "Switzerland", code: "ch"))
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
Spacer()
|
||||
.frame(height: 32)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func connectButton() -> some View {
|
||||
ConnectButton()
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import SwiftUI
|
||||
import UIComponents
|
||||
|
||||
public class HomeViewViewModel: ObservableObject {
|
||||
@Published var selectedNetwork: NetworkButtonViewModel.ButtonType
|
||||
|
||||
public init(selectedNetwork: NetworkButtonViewModel.ButtonType) {
|
||||
self.selectedNetwork = selectedNetwork
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ch.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ch" viewBox="0 0 512 512">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="red" d="M0 0h512v512H0z"/>
|
||||
<g fill="#fff">
|
||||
<path d="M96 208h320v96H96z"/>
|
||||
<path d="M208 96h96v320h-96z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 288 B |
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "de.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 512 512">
|
||||
<path fill="#ffce00" d="M0 341.3h512V512H0z"/>
|
||||
<path fill="#000001" d="M0 0h512v170.7H0z"/>
|
||||
<path fill="red" d="M0 170.7h512v170.6H0z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -21,7 +21,8 @@ let package = Package(
|
||||
resources: [
|
||||
.copy("Resources/Fonts/Lato-Bold.ttf"),
|
||||
.copy("Resources/Fonts/Lato-Regular.ttf"),
|
||||
.process("Resources/Assets.xcassets"),
|
||||
.copy("Resources/Fonts/Lato-SemiBold.ttf"),
|
||||
.process("Resources/Colors.xcassets"),
|
||||
.process("Resources/Localizable.xcstrings")
|
||||
]
|
||||
),
|
||||
|
||||
@@ -7,7 +7,15 @@ public struct NymColor {
|
||||
|
||||
// MARK: - Status button -
|
||||
public static let statusButtonBackground = Color(.statusButtonBackground)
|
||||
public static let statusButtonTitleConnecting = Color(.statusButtonTitleConnecting)
|
||||
public static let sysOnSurfaceWhite = Color(.sysOnSurfaceWhite)
|
||||
public static let statusInfoText = Color(.statusInfoText)
|
||||
public static let statusTimer = Color(.statusTimer)
|
||||
|
||||
// MARK: - Network button -
|
||||
public static let networkButtonCircle = Color(.networkButtonCircle)
|
||||
|
||||
// MARK: - Connect -
|
||||
public static let connectTitle = Color(.connectTitle)
|
||||
|
||||
// MARK: - Background
|
||||
public static let background = Color(.background)
|
||||
@@ -15,8 +23,12 @@ public struct NymColor {
|
||||
// MARK: - Sys -
|
||||
public static let sysOnSecondary = Color(.sysOnSecondary)
|
||||
public static let sysOnSurface = Color(.sysOnSurface)
|
||||
public static let sysOutline = Color(.sysOutline)
|
||||
|
||||
// MARK: - Key -
|
||||
public static let confirm = Color(.confirm)
|
||||
public static let statusGreen = Color(.statusGreen)
|
||||
|
||||
// MARK: - Primary -
|
||||
public static let primaryOrange = Color(.primaryOrange)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ extension NymFont {
|
||||
public enum LatoWeight: String, CaseIterable {
|
||||
case regular = "Regular"
|
||||
case bold = "Bold"
|
||||
case semibold = "SemiBold"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ import SwiftUI
|
||||
public struct NymTextStyle {
|
||||
let nymFont: NymFont
|
||||
let lineSpacing: CGFloat
|
||||
let kerning: CGFloat
|
||||
|
||||
init(nymFont: NymFont, lineSpacing: CGFloat = 0) {
|
||||
init(nymFont: NymFont, lineSpacing: CGFloat = 0, kerning: CGFloat = 0) {
|
||||
self.nymFont = nymFont
|
||||
self.lineSpacing = lineSpacing
|
||||
self.kerning = kerning
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +21,12 @@ extension NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 22, weight: .regular))
|
||||
}
|
||||
}
|
||||
|
||||
public struct Medium {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 16, weight: .semibold), kerning: 0.15)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Label -
|
||||
@@ -28,5 +36,32 @@ extension NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 18, weight: .bold))
|
||||
}
|
||||
}
|
||||
|
||||
public struct Large {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 14, weight: .bold), kerning: 0.1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Body -
|
||||
public struct Body {
|
||||
public struct Large {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 16, weight: .semibold), kerning: 0.5)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Medium {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 14, weight: .regular), kerning: 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
public struct Small {
|
||||
public static var primary: NymTextStyle {
|
||||
NymTextStyle(nymFont: .lato(size: 12, weight: .regular), kerning: 0.4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ public struct NymTextStyleModifier: ViewModifier {
|
||||
public func body(content: Content) -> some View {
|
||||
content
|
||||
.font(textStyle.nymFont.font)
|
||||
.kerning(textStyle.kerning)
|
||||
.lineSpacing(textStyle.lineSpacing)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "113",
|
||||
"green" : "101",
|
||||
"red" : "105"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "153",
|
||||
"green" : "143",
|
||||
"red" : "147"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "28",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "250",
|
||||
"red" : "254"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -23,9 +23,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.898",
|
||||
"green" : "0.882",
|
||||
"red" : "0.902"
|
||||
"blue" : "229",
|
||||
"green" : "225",
|
||||
"red" : "230"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "126",
|
||||
"green" : "116",
|
||||
"red" : "121"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "153",
|
||||
"green" : "143",
|
||||
"red" : "147"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "31",
|
||||
"green" : "27",
|
||||
"red" : "28"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "255",
|
||||
"green" : "255",
|
||||
"red" : "255"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "78",
|
||||
"green" : "110",
|
||||
"red" : "251"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "126",
|
||||
"green" : "116",
|
||||
"red" : "121"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "208",
|
||||
"green" : "196",
|
||||
"red" : "202"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,61 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"2hopWireGuardSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Best for browsing, streaming, sharing"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"2hopWireGuardTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "2-hop WireGuard"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5hopMixnetSubtitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Best for payments, emails, messages"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"5hopMixnetTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "5-hop mixnet"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connect" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -23,6 +78,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"connectTo" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Connect to"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"disconnected" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -34,6 +100,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"disconnecting" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Disconnecting..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"firstHop" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "First hop"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"NymVPN" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -44,6 +132,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectNetwork" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Select network"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version" : "1.0"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct ConnectButton: View {
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
Text("Connect")
|
||||
.foregroundStyle(NymColor.connectTitle)
|
||||
.textStyle(.Label.Huge.primary)
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 56, maxHeight: 56)
|
||||
.background(NymColor.primaryOrange)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct HopButton: View {
|
||||
private let country: Country
|
||||
|
||||
public init(country: Country) {
|
||||
self.country = country
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
Image(country.code)
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.cornerRadius(50)
|
||||
.padding(16)
|
||||
|
||||
Text(country.name)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.textStyle(.Body.Large.primary)
|
||||
Spacer()
|
||||
Image("arrowRight", bundle: .module)
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
.padding(0)
|
||||
.frame(maxWidth: .infinity, minHeight: 56, maxHeight: 56)
|
||||
.cornerRadius(8)
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.inset(by: 0.5)
|
||||
.stroke(Color(red: 0.29, green: 0.27, blue: 0.31), lineWidth: 1)
|
||||
}
|
||||
.overlay(alignment: .topLeading) {
|
||||
Text("firstHop".localizedString)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.textStyle(.Body.Small.primary)
|
||||
.padding(4)
|
||||
.background(NymColor.background)
|
||||
.position(x: 40, y: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct Country {
|
||||
public let name: String
|
||||
public let code: String
|
||||
|
||||
public init(name: String, code: String) {
|
||||
self.name = name
|
||||
self.code = code
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct NetworkButton: View {
|
||||
private let viewModel: NetworkButtonViewModel
|
||||
|
||||
public init(viewModel: NetworkButtonViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
Image(viewModel.selectionImageName, bundle: .module)
|
||||
.foregroundStyle(viewModel.selectionImageColor)
|
||||
.padding(.leading, 16)
|
||||
|
||||
Image(viewModel.type.imageName, bundle: .module)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.padding(.leading, 8)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(viewModel.type.title)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.textStyle(.Body.Large.primary)
|
||||
Text(viewModel.type.subtitle)
|
||||
.foregroundStyle(NymColor.sysOutline)
|
||||
.textStyle(.Body.Medium.primary)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 64, maxHeight: 64)
|
||||
.background(NymColor.navigationBarBackground)
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.inset(by: 0.5)
|
||||
.stroke(viewModel.selectionStrokeColor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public struct NetworkButtonViewModel {
|
||||
public enum ButtonType {
|
||||
case mixnet
|
||||
case wireguard
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
return "mixnetIcon"
|
||||
case .wireguard:
|
||||
return "wireguardIcon"
|
||||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
"5hopMixnetTitle".localizedString
|
||||
case .wireguard:
|
||||
"2hopWireGuardTitle".localizedString
|
||||
}
|
||||
}
|
||||
|
||||
var subtitle: String {
|
||||
switch self {
|
||||
case .mixnet:
|
||||
"5hopMixnetSubtitle".localizedString
|
||||
case .wireguard:
|
||||
"2hopWireGuardSubtitle".localizedString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let type: ButtonType
|
||||
|
||||
@Binding var selectedNetwork: ButtonType
|
||||
|
||||
public init(type: ButtonType, selectedNetwork: Binding<ButtonType>) {
|
||||
self.type = type
|
||||
self._selectedNetwork = selectedNetwork
|
||||
}
|
||||
|
||||
private var isSelected: Bool {
|
||||
type == selectedNetwork
|
||||
}
|
||||
|
||||
var selectionImageName: String {
|
||||
isSelected ? "networkSelectedCircle" : "networkCircle"
|
||||
}
|
||||
|
||||
var selectionImageColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : NymColor.networkButtonCircle
|
||||
}
|
||||
|
||||
var selectionStrokeColor: Color {
|
||||
isSelected ? NymColor.primaryOrange : .clear
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct CustomNavBar: View {
|
||||
public let title: String
|
||||
public let leftButton: CustomNavBarButton?
|
||||
public let rightButton: CustomNavBarButton?
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
leftButton: CustomNavBarButton? = nil,
|
||||
rightButton: CustomNavBarButton? = nil
|
||||
) {
|
||||
self.title = title
|
||||
self.leftButton = leftButton
|
||||
self.rightButton = rightButton
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
leftButton
|
||||
Spacer()
|
||||
Text(title)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.textStyle(.Title.Large.primary)
|
||||
Spacer()
|
||||
rightButton
|
||||
}
|
||||
.frame(height: 64)
|
||||
.background {
|
||||
NymColor.navigationBarBackground
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct CustomNavBarButton: View {
|
||||
public enum ButtonType: String {
|
||||
case settingsGear
|
||||
case empty
|
||||
|
||||
var imageName: String? {
|
||||
switch self {
|
||||
case .settingsGear:
|
||||
self.rawValue
|
||||
case .empty:
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let type: ButtonType
|
||||
public let action: (() -> Void)?
|
||||
|
||||
public init(type: ButtonType, action: (() -> Void)?) {
|
||||
self.type = type
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Button {
|
||||
action?()
|
||||
} label: {
|
||||
if let imageName = type.imageName {
|
||||
Image(imageName, bundle: .module)
|
||||
.foregroundStyle(NymColor.navigationBarSettingsGear)
|
||||
}
|
||||
}
|
||||
.frame(width: 48, height: 48)
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct CustomNavBar: View {
|
||||
public let title: String
|
||||
public let leftButtonConfig: CustomNavBarButtonConfig?
|
||||
public let rightButtonConfig: CustomNavBarButtonConfig?
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
leftButtonConfig: CustomNavBarButtonConfig? = nil,
|
||||
rightButtonConfig: CustomNavBarButtonConfig? = nil
|
||||
) {
|
||||
self.title = title
|
||||
self.leftButtonConfig = leftButtonConfig
|
||||
self.rightButtonConfig = rightButtonConfig
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack {
|
||||
button(with: leftButtonConfig)
|
||||
Spacer()
|
||||
Text(title)
|
||||
.foregroundStyle(NymColor.sysOnSurface)
|
||||
.textStyle(.Title.Large.primary)
|
||||
Spacer()
|
||||
button(with: rightButtonConfig)
|
||||
}
|
||||
.frame(height: 64)
|
||||
.background {
|
||||
NymColor.navigationBarBackground
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension CustomNavBar {
|
||||
@ViewBuilder
|
||||
func button(with config: CustomNavBarButtonConfig?) -> some View {
|
||||
Button {
|
||||
config?.action?()
|
||||
} label: {
|
||||
if let type = config?.type {
|
||||
Image(uiImage: UIImage(named: type.imageName) ?? UIImage())
|
||||
.tint(NymColor.navigationBarSettingsGear)
|
||||
}
|
||||
}
|
||||
.frame(width: 48, height: 48)
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
public struct CustomNavBarButtonConfig {
|
||||
public enum ButtonType: String {
|
||||
case settingsGear
|
||||
|
||||
var imageName: String {
|
||||
self.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public init(type: ButtonType? = nil, action: (() -> Void)?) {
|
||||
self.type = type
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public let type: ButtonType?
|
||||
public let action: (() -> Void)?
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "arrow_right_24px.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="Icons/arrow_right_24px">
|
||||
<path id="icon" d="M10 17L15 12L10 7V17Z" fill="#CAC4D0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 199 B |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Leading element.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.3108 23.3098L12.9995 2.6875L2.68848 23.3098L23.3108 2.6875L12.9995 23.3098L2.68848 2.6875L23.3108 23.3098Z" stroke="#E6E1E5" stroke-width="0.5" stroke-linejoin="round"/>
|
||||
<path d="M12.9995 2.68894L2.68848 13L12.9995 23.3111L23.3106 13L12.9995 2.68894Z" stroke="#E6E1E5" stroke-width="0.5" stroke-linejoin="round"/>
|
||||
<path d="M13.0003 13.0007L7.84473 18.1562L13.0003 23.3118L18.1558 18.1562L13.0003 13.0007Z" stroke="#E6E1E5" stroke-width="0.5" stroke-linejoin="round"/>
|
||||
<path d="M2.68848 13H23.3108" stroke="#E6E1E5" stroke-width="0.5" stroke-linejoin="round"/>
|
||||
<path d="M23.3108 2.6875H2.68848" stroke="#E6E1E5" stroke-width="0.5" stroke-linejoin="round"/>
|
||||
<path d="M23.3108 23.3125H2.68848" stroke="#E6E1E5" stroke-width="0.5" stroke-linejoin="round"/>
|
||||
<path d="M2.68884 4.37769C3.62157 4.37769 4.37769 3.62157 4.37769 2.68884C4.37769 1.75612 3.62157 1 2.68884 1C1.75612 1 1 1.75612 1 2.68884C1 3.62157 1.75612 4.37769 2.68884 4.37769Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M23.3109 4.37769C24.2436 4.37769 24.9998 3.62157 24.9998 2.68884C24.9998 1.75612 24.2436 1 23.3109 1C22.3782 1 21.6221 1.75612 21.6221 2.68884C21.6221 3.62157 22.3782 4.37769 23.3109 4.37769Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M12.9994 4.37769C13.9321 4.37769 14.6882 3.62157 14.6882 2.68884C14.6882 1.75612 13.9321 1 12.9994 1C12.0667 1 11.3105 1.75612 11.3105 2.68884C11.3105 3.62157 12.0667 4.37769 12.9994 4.37769Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M2.68884 14.6902C3.62157 14.6902 4.37769 13.9341 4.37769 13.0013C4.37769 12.0686 3.62157 11.3125 2.68884 11.3125C1.75612 11.3125 1 12.0686 1 13.0013C1 13.9341 1.75612 14.6902 2.68884 14.6902Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M23.3109 14.6902C24.2436 14.6902 24.9998 13.9341 24.9998 13.0013C24.9998 12.0686 24.2436 11.3125 23.3109 11.3125C22.3782 11.3125 21.6221 12.0686 21.6221 13.0013C21.6221 13.9341 22.3782 14.6902 23.3109 14.6902Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M12.9994 14.6902C13.9321 14.6902 14.6882 13.9341 14.6882 13.0013C14.6882 12.0686 13.9321 11.3125 12.9994 11.3125C12.0667 11.3125 11.3105 12.0686 11.3105 13.0013C11.3105 13.9341 12.0667 14.6902 12.9994 14.6902Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M2.68884 25.0027C3.62157 25.0027 4.37769 24.2466 4.37769 23.3138C4.37769 22.3811 3.62157 21.625 2.68884 21.625C1.75612 21.625 1 22.3811 1 23.3138C1 24.2466 1.75612 25.0027 2.68884 25.0027Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M23.3109 25.0027C24.2436 25.0027 24.9998 24.2466 24.9998 23.3138C24.9998 22.3811 24.2436 21.625 23.3109 21.625C22.3782 21.625 21.6221 22.3811 21.6221 23.3138C21.6221 24.2466 22.3782 25.0027 23.3109 25.0027Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
<path d="M12.9994 25.0027C13.9321 25.0027 14.6882 24.2466 14.6882 23.3138C14.6882 22.3811 13.9321 21.625 12.9994 21.625C12.0667 21.625 11.3105 22.3811 11.3105 23.3138C11.3105 24.2466 12.0667 25.0027 12.9994 25.0027Z" fill="#1C1B1F" stroke="#E6E1E5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="icon">
|
||||
<path id="icon_2" d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.58 20 4 16.42 4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12C20 16.42 16.42 20 12 20Z" fill="#CAC4D0"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 347 B |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.58 20 4 16.42 4 12C4 7.58 7.58 4 12 4C16.42 4 20 7.58 20 12C20 16.42 16.42 20 12 20Z" fill="#FB6E4E"/>
|
||||
<path d="M12 17C14.7614 17 17 14.7614 17 12C17 9.23858 14.7614 7 12 7C9.23858 7 7 9.23858 7 12C7 14.7614 9.23858 17 12 17Z" fill="#FB6E4E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 457 B |
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "security_black_24dp 1.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="security_black_24dp 1" clip-path="url(#clip0_6362_539)">
|
||||
<path id="Vector" d="M5 12.5H11.5V20.93V21.6079L12.1477 21.4077C16.085 20.1905 18.9452 16.3346 19.4959 12.0538L19.5684 11.49H19H12.5V3.19V2.42073L11.797 2.73307L4.79699 5.84307L4.5 5.97502V6.3V12V12.5H5ZM3.5 11V5.32494L12 1.54716L20.5 5.32494V11C20.5 16.2973 16.8574 21.2335 12 22.4845C7.14261 21.2335 3.5 16.2973 3.5 11Z" fill="#1C1B1F" stroke="#1C1B1F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_6362_539">
|
||||
<rect width="24" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
@@ -17,7 +17,6 @@ public struct StatusButton: View {
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 16)
|
||||
.background(config.backgroundColor)
|
||||
|
||||
.cornerRadius(50)
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,8 @@ public enum StatusButtonConfig: String {
|
||||
case .connected:
|
||||
return NymColor.confirm
|
||||
case .connecting, .disconnecting:
|
||||
return NymColor.statusButtonTitleConnecting
|
||||
case .disconnected ,.error:
|
||||
return NymColor.sysOnSurfaceWhite
|
||||
case .disconnected, .error:
|
||||
return NymColor.sysOnSecondary
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import SwiftUI
|
||||
import Theme
|
||||
|
||||
public struct StatusInfoView: View {
|
||||
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
@State private var timeConnected = "00:00:00"
|
||||
@State private var startDate = Date.now
|
||||
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
// TODO: missing states
|
||||
Text("Initializing client...")
|
||||
.foregroundStyle(NymColor.statusInfoText)
|
||||
.textStyle(.Label.Large.primary)
|
||||
Spacer()
|
||||
.frame(height: 8)
|
||||
Text("\(timeConnected)")
|
||||
.foregroundStyle(NymColor.statusTimer)
|
||||
.textStyle(.Label.Large.primary)
|
||||
.onReceive(timer) { _ in
|
||||
timeConnected = differenceBetweenDates(startDate: startDate, currentDate: Date.now)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusInfoView {
|
||||
// TODO: move to separate date formatter service
|
||||
func differenceBetweenDates(startDate: Date, currentDate: Date) -> String {
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.allowedUnits = [.hour, .minute, .second]
|
||||
formatter.zeroFormattingBehavior = .pad
|
||||
return formatter.string(from: startDate, to: currentDate) ?? "00:00:00"
|
||||
}
|
||||
}
|
||||