From 85ccd835aba1c60fee9436bf60df440b00715277 Mon Sep 17 00:00:00 2001 From: Rokas Ambrazevicius Date: Fri, 12 Jan 2024 16:36:16 +0200 Subject: [PATCH] Add hop list view --- nym-vpn/ios/Home/Package.swift | 3 +- nym-vpn/ios/Home/Sources/Home/HomeView.swift | 7 +-- .../ios/Home/Sources/Hop/HopListView.swift | 51 +++++++++++++++++ nym-vpn/ios/Home/Sources/Hop/HopType.swift | 15 +++++ .../Contents.json | 12 ++-- .../connectTitle.colorset/Contents.json | 12 ++-- .../Theme/Resources/Localizable.xcstrings | 55 +++++++++++++++++++ .../Buttons/ConnectButton/ConnectButton.swift | 2 +- .../Buttons/HopButton/HopButton.swift | 36 ++++++------ .../CustomNavigationBar/CustomNavBar.swift | 4 +- .../CustomNavBarButton.swift | 9 ++- .../Icons/arrowBack.imageset/Contents.json | 16 ++++++ .../arrowBack.imageset/arrow_back_24px.svg | 5 ++ .../Icons/searchIcon.imageset/Contents.json | 16 ++++++ .../Icons/searchIcon.imageset/search_24px.svg | 5 ++ .../UIComponents/Search/SearchView.swift | 45 +++++++++++++++ .../UIComponents/Search/SearchViewModel.swift | 11 ++++ .../StrokeBorderView/StrokeBorderView.swift | 34 ++++++++++++ 18 files changed, 297 insertions(+), 41 deletions(-) create mode 100644 nym-vpn/ios/Home/Sources/Hop/HopListView.swift create mode 100644 nym-vpn/ios/Home/Sources/Hop/HopType.swift create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/Contents.json create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/arrowBack.imageset/arrow_back_24px.svg create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/Contents.json create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/Resources/Assets.xcassets/Icons/searchIcon.imageset/search_24px.svg create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchView.swift create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/Search/SearchViewModel.swift create mode 100644 nym-vpn/ios/UIComponents/Sources/UIComponents/StrokeBorderView/StrokeBorderView.swift 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) + } + } +}