Add Home View

This commit is contained in:
Rokas Ambrazevicius
2024-01-08 13:39:07 +02:00
parent 59845174e7
commit b609331b8e
54 changed files with 922 additions and 85 deletions
+83 -9
View File
@@ -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

+2 -1
View File
@@ -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"
}
}