diff --git a/nym-vpn/ios/Home/Package.swift b/nym-vpn/ios/Home/Package.swift
index 5c359e3207..1ca42e8bf2 100644
--- a/nym-vpn/ios/Home/Package.swift
+++ b/nym-vpn/ios/Home/Package.swift
@@ -21,7 +21,8 @@ let package = Package(
targets: [
.target(
name: "Home",
- dependencies: ["UIComponents"]
+ dependencies: ["UIComponents"],
+ path: "Sources"
),
.testTarget(
name: "HomeTests",
diff --git a/nym-vpn/ios/Home/Sources/Home/HomeView.swift b/nym-vpn/ios/Home/Sources/Home/HomeView.swift
index 2215a4f986..efc5134487 100644
--- a/nym-vpn/ios/Home/Sources/Home/HomeView.swift
+++ b/nym-vpn/ios/Home/Sources/Home/HomeView.swift
@@ -28,8 +28,7 @@ private extension HomeView {
func navbar() -> some View {
CustomNavBar(
title: "NymVPN".localizedString,
- leftButton: CustomNavBarButton(type: .empty, action: {}),
- rightButton: CustomNavBarButton(type: .settingsGear, action: {})
+ rightButton: CustomNavBarButton(type: .settings, action: {})
)
Spacer()
.frame(height: 50)
@@ -86,10 +85,10 @@ private extension HomeView {
.frame(height: 24)
VStack {
- HopButton(country: Country(name: "Germany", code: "de"))
+ HopButton(hopType: .first, country: Country(name: "Germany", code: "de"))
Spacer()
.frame(height: 24)
- HopButton(country: Country(name: "Switzerland", code: "ch"))
+ HopButton(hopType: .last, country: Country(name: "Switzerland", code: "ch"))
}
.padding(.horizontal, 16)
diff --git a/nym-vpn/ios/Home/Sources/Hop/HopListView.swift b/nym-vpn/ios/Home/Sources/Hop/HopListView.swift
new file mode 100644
index 0000000000..9d5aefaf58
--- /dev/null
+++ b/nym-vpn/ios/Home/Sources/Hop/HopListView.swift
@@ -0,0 +1,51 @@
+import SwiftUI
+import Theme
+import UIComponents
+
+public struct HopListView: View {
+ private let viewModel: HopListViewModel
+
+ public init(viewModel: HopListViewModel) {
+ self.viewModel = viewModel
+ }
+
+ public var body: some View {
+ VStack {
+ navbar()
+ Spacer()
+ .frame(height: 24)
+
+ searchView()
+ Spacer()
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background {
+ NymColor.background
+ .ignoresSafeArea()
+ }
+ }
+}
+
+private extension HopListView {
+ @ViewBuilder
+ func navbar() -> some View {
+ CustomNavBar(
+ title: viewModel.type.selectHopLocalizedTitle,
+ leftButton: CustomNavBarButton(type: .back, action: {})
+ )
+ }
+
+ @ViewBuilder
+ func searchView() -> some View {
+ SearchView(viewModel: SearchViewModel())
+ .padding(.horizontal, 16)
+ }
+}
+
+public struct HopListViewModel {
+ public let type: HopType
+
+ public init(type: HopType) {
+ self.type = type
+ }
+}
diff --git a/nym-vpn/ios/Home/Sources/Hop/HopType.swift b/nym-vpn/ios/Home/Sources/Hop/HopType.swift
new file mode 100644
index 0000000000..d7a4779daf
--- /dev/null
+++ b/nym-vpn/ios/Home/Sources/Hop/HopType.swift
@@ -0,0 +1,15 @@
+import Foundation
+
+public enum HopType {
+ case first
+ case last
+
+ var selectHopLocalizedTitle: String {
+ switch self {
+ case .first:
+ "firstHopSelection".localizedString
+ case .last:
+ "lastHopSelection".localizedString
+ }
+ }
+}
diff --git a/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/StatusArea/statusButtonBackground.colorset/Contents.json b/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/StatusArea/statusButtonBackground.colorset/Contents.json
index 37c68333ae..92ddf5fd58 100644
--- a/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/StatusArea/statusButtonBackground.colorset/Contents.json
+++ b/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/StatusArea/statusButtonBackground.colorset/Contents.json
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "0.080",
- "blue" : "0.443",
- "green" : "0.357",
- "red" : "0.384"
+ "blue" : "0x71",
+ "green" : "0x5B",
+ "red" : "0x61"
}
},
"idiom" : "universal"
@@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "0.160",
- "blue" : "0.200",
- "green" : "0.188",
- "red" : "0.192"
+ "blue" : "0x33",
+ "green" : "0x30",
+ "red" : "0x30"
}
},
"idiom" : "universal"
diff --git a/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/connectTitle.colorset/Contents.json b/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/connectTitle.colorset/Contents.json
index 98549862e7..6396fa8c84 100644
--- a/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/connectTitle.colorset/Contents.json
+++ b/nym-vpn/ios/Theme/Sources/Theme/Resources/Colors.xcassets/Colors/connectTitle.colorset/Contents.json
@@ -5,9 +5,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "31",
- "green" : "27",
- "red" : "28"
+ "blue" : "255",
+ "green" : "255",
+ "red" : "255"
}
},
"idiom" : "universal"
@@ -23,9 +23,9 @@
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
- "blue" : "255",
- "green" : "255",
- "red" : "255"
+ "blue" : "31",
+ "green" : "27",
+ "red" : "28"
}
},
"idiom" : "universal"
diff --git a/nym-vpn/ios/Theme/Sources/Theme/Resources/Localizable.xcstrings b/nym-vpn/ios/Theme/Sources/Theme/Resources/Localizable.xcstrings
index f71aba9f04..6905229714 100644
--- a/nym-vpn/ios/Theme/Sources/Theme/Resources/Localizable.xcstrings
+++ b/nym-vpn/ios/Theme/Sources/Theme/Resources/Localizable.xcstrings
@@ -122,6 +122,39 @@
}
}
},
+ "firstHopSelection" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "First hop selection"
+ }
+ }
+ }
+ },
+ "lastHop" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Last hop"
+ }
+ }
+ }
+ },
+ "lastHopSelection" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Last hop selection"
+ }
+ }
+ }
+ },
"NymVPN" : {
"extractionState" : "manual",
"localizations" : {
@@ -133,6 +166,28 @@
}
}
},
+ "search" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Search"
+ }
+ }
+ }
+ },
+ "searchCountry" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Search country"
+ }
+ }
+ }
+ },
"selectNetwork" : {
"extractionState" : "manual",
"localizations" : {
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/ConnectButton/ConnectButton.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/ConnectButton/ConnectButton.swift
index dfd650daa8..87ba5e4306 100644
--- a/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/ConnectButton/ConnectButton.swift
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/ConnectButton/ConnectButton.swift
@@ -6,7 +6,7 @@ public struct ConnectButton: View {
public var body: some View {
HStack {
- Text("Connect")
+ Text("connect".localizedString)
.foregroundStyle(NymColor.connectTitle)
.textStyle(.Label.Huge.primary)
}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/HopButton/HopButton.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/HopButton/HopButton.swift
index b896fb2386..d5c2189db0 100644
--- a/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/HopButton/HopButton.swift
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Buttons/HopButton/HopButton.swift
@@ -2,14 +2,16 @@ import SwiftUI
import Theme
public struct HopButton: View {
+ private let hopType: HopType
private let country: Country
- public init(country: Country) {
+ public init(hopType: HopType, country: Country) {
+ self.hopType = hopType
self.country = country
}
public var body: some View {
- VStack(alignment: .leading) {
+ StrokeBorderView(strokeTitle: hopType.localizedTitle) {
HStack {
Image(country.code)
.resizable()
@@ -27,22 +29,6 @@ public struct HopButton: View {
.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)
- }
}
}
@@ -55,3 +41,17 @@ public struct Country {
self.code = code
}
}
+
+public enum HopType {
+ case first
+ case last
+
+ var localizedTitle: String {
+ switch self {
+ case .first:
+ "firstHop".localizedString
+ case .last:
+ "lastHop".localizedString
+ }
+ }
+}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBar.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBar.swift
index ec94b50511..cbdd1b1242 100644
--- a/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBar.swift
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBar.swift
@@ -8,8 +8,8 @@ public struct CustomNavBar: View {
public init(
title: String,
- leftButton: CustomNavBarButton? = nil,
- rightButton: CustomNavBarButton? = nil
+ leftButton: CustomNavBarButton? = CustomNavBarButton(type: .empty, action: {}),
+ rightButton: CustomNavBarButton? = CustomNavBarButton(type: .empty, action: {})
) {
self.title = title
self.leftButton = leftButton
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBarButton.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBarButton.swift
index 37196dc629..6101426e6e 100644
--- a/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBarButton.swift
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/CustomNavigationBar/CustomNavBarButton.swift
@@ -3,13 +3,16 @@ import Theme
public struct CustomNavBarButton: View {
public enum ButtonType: String {
- case settingsGear
+ case back
+ case settings
case empty
var imageName: String? {
switch self {
- case .settingsGear:
- self.rawValue
+ case .back:
+ "arrowBack"
+ case .settings:
+ "settingsGear"
case .empty:
nil
}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/Contents.json b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/Contents.json
new file mode 100644
index 0000000000..c62d1cd015
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "arrow_back_24px.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/arrow_back_24px.svg b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/arrow_back_24px.svg
new file mode 100644
index 0000000000..2a35d2f37e
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/arrow_back_24px.svg
@@ -0,0 +1,5 @@
+
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/Contents.json b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/Contents.json
new file mode 100644
index 0000000000..0e629afc16
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "search_24px.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/search_24px.svg b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/search_24px.svg
new file mode 100644
index 0000000000..1516812170
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/search_24px.svg
@@ -0,0 +1,5 @@
+
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchView.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchView.swift
new file mode 100644
index 0000000000..1eb3cec440
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchView.swift
@@ -0,0 +1,45 @@
+import SwiftUI
+import Theme
+
+public struct SearchView: View {
+ @ObservedObject private var viewModel: SearchViewModel
+
+ public init(viewModel: SearchViewModel) {
+ self.viewModel = viewModel
+ }
+
+ public var body: some View {
+ StrokeBorderView(strokeTitle: viewModel.strokeTitle) {
+ HStack {
+ searchImage()
+ searchTextfield()
+ Spacer()
+ }
+ }
+ }
+}
+
+extension SearchView {
+ @ViewBuilder
+ func searchImage() -> some View {
+ Image(viewModel.searchImageName, bundle: .module)
+ .resizable()
+ .frame(width: 24, height: 24)
+ .cornerRadius(50)
+ .padding(16)
+ }
+
+ @ViewBuilder
+ func searchTextfield() -> some View {
+ ZStack(alignment: .leading) {
+ if viewModel.searchText.isEmpty {
+ Text(viewModel.searchCountryTitle)
+ .foregroundStyle(NymColor.sysOutline)
+ .textStyle(.Body.Large.primary)
+ }
+ TextField("", text: $viewModel.searchText)
+ .foregroundStyle(NymColor.sysOnSurface)
+ .textStyle(.Body.Large.primary)
+ }
+ }
+}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchViewModel.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchViewModel.swift
new file mode 100644
index 0000000000..4991292c1f
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchViewModel.swift
@@ -0,0 +1,11 @@
+import Foundation
+
+public final class SearchViewModel: ObservableObject {
+ let strokeTitle = "search".localizedString
+ let searchCountryTitle = "searchCountry".localizedString
+ let searchImageName = "searchIcon"
+
+ @Published var searchText = ""
+
+ public init() {}
+}
diff --git a/nym-vpn/ios/UIComponents/Sources/UIComponents/StrokeBorderView/StrokeBorderView.swift b/nym-vpn/ios/UIComponents/Sources/UIComponents/StrokeBorderView/StrokeBorderView.swift
new file mode 100644
index 0000000000..7546d1f039
--- /dev/null
+++ b/nym-vpn/ios/UIComponents/Sources/UIComponents/StrokeBorderView/StrokeBorderView.swift
@@ -0,0 +1,34 @@
+import SwiftUI
+import Theme
+
+public struct StrokeBorderView: View {
+ @ViewBuilder private let content: Content
+ private let strokeTitle: String
+
+ public init(strokeTitle: String, @ViewBuilder content: () -> Content) {
+ self.strokeTitle = strokeTitle
+ self.content = content()
+ }
+
+ public var body: some View {
+ VStack(alignment: .leading) {
+ content
+ }
+ .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(strokeTitle)
+ .foregroundStyle(NymColor.sysOnSurface)
+ .textStyle(.Body.Small.primary)
+ .padding(4)
+ .background(NymColor.background)
+ .position(x: 40, y: 0)
+ }
+ }
+}