Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PIA-1001: Regions search #67

Merged
merged 3 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions PIA VPN-tvOS/Navigation/Destinations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ enum DashboardDestinations: Destinations {
case home
}

enum RegionSelectionDestinations: Destinations {
case search
}

enum RegionsDestinations: Destinations {
case serversList
case selectServer(_: ServerType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ import Foundation
import PIALibrary

class RegionsSelectionFactory {

static func makeRegionsContainerView() -> RegionsContainerView {
return RegionsContainerView(viewModel: makeRegionsContainerViewModel())
}

static func makeRegionsContainerViewModel() -> RegionsContainerViewModel {
return RegionsContainerViewModel(onSearchSelectedAction: .navigate(router: AppRouterFactory.makeAppRouter(), destination: RegionSelectionDestinations.search))
}

static func makeRegionsListViewModel() -> RegionsListViewModel {
return RegionsListViewModel(useCase: makeRegionsListUseCase(), onServerSelectedRouterAction: .pop(router: AppRouterFactory.makeAppRouter()))
return RegionsListViewModel(useCase: makeRegionsListUseCase(), onServerSelectedRouterAction: .goBackToRoot(router: AppRouterFactory.makeAppRouter()))
}

static func makeRegionsListView() -> RegionsListView {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// RegionsContainerViewModel.swift
// PIA VPN-tvOS
//
// Created by Laura S on 1/19/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import Foundation
import SwiftUI

class RegionsContainerViewModel: ObservableObject {
enum RegionSelectionSideMenuItems: CaseIterable, Identifiable {
case all
case search
case favourites

var id: Self {
return self
}

var text: String {
switch self {
case .all:
return L10n.Localizable.RegionsView.SplitMenu.AllItem.title
case .search:
return L10n.Localizable.RegionsView.SplitMenu.SearchItem.title
case .favourites:
return L10n.Localizable.RegionsView.SplitMenu.FavoritesItem.title
}
}
}

@Published var sideMenuItems: [RegionSelectionSideMenuItems] = RegionSelectionSideMenuItems.allCases

@Published private(set) var selectedSideMenuItem: RegionSelectionSideMenuItems = .all

private let onSearchSelectedAction: AppRouter.Actions

init(onSearchSelectedAction: AppRouter.Actions) {
self.onSearchSelectedAction = onSearchSelectedAction
}

func navigate(to route: RegionSelectionSideMenuItems) {
if route == .search {
onSearchSelectedAction.execute()
} else {
selectedSideMenuItem = route
}

}


}
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,35 @@

import Foundation
import PIALibrary
import Combine

class RegionsListViewModel: ObservableObject {

private let useCase: RegionsListUseCaseType
private let onServerSelectedRouterAction: AppRouter.Actions
@Published var servers: [ServerType] = []
@Published var search = ""
private var cancellables = Set<AnyCancellable>()

init(useCase: RegionsListUseCaseType, onServerSelectedRouterAction: AppRouter.Actions) {
self.useCase = useCase
self.onServerSelectedRouterAction = onServerSelectedRouterAction
refreshRegionsList()
subscribeToSearchUpdates()
}

func subscribeToSearchUpdates() {
$search.sink { [weak self] searchTerm in
guard let self = self else { return }
guard !searchTerm.isEmpty else {
self.refreshRegionsList()
return
}
self.servers = self.useCase.getCurrentServers().filter({ server in
return server.name.lowercased().contains(searchTerm.lowercased()) ||
searchTerm.lowercased().contains(server.country.lowercased())
})
}.store(in: &cancellables)
}

func refreshRegionsList() {
Expand Down
67 changes: 67 additions & 0 deletions PIA VPN-tvOS/RegionsSelection/UI/RegionsContainerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// RegionsContainerView.swift
// PIA VPN-tvOS
//
// Created by Laura S on 1/19/24.
// Copyright © 2024 Private Internet Access Inc. All rights reserved.
//

import SwiftUI

struct RegionsContainerView: View {
let viewWidth = UIScreen.main.bounds.width
let viewHeight = UIScreen.main.bounds.height

@ObservedObject var viewModel: RegionsContainerViewModel

var sideMenuButtons: some View {
VStack(alignment: .leading) {
ForEach(viewModel.sideMenuItems) { menuItem in
Button {
viewModel.navigate(to: menuItem)
} label: {
Text(menuItem.text)
}
}
}
}


var body: some View {
VStack(alignment: .leading) {
switch viewModel.selectedSideMenuItem {
case .all:
HStack(alignment: .top) {
sideMenuButtons
VStack {
RegionsSelectionFactory.makeRegionsListView()
}
}
case .favourites:
HStack(alignment: .top) {
sideMenuButtons
VStack {
RegionsSelectionFactory.makeRegionsListView()
}
}
case .search:
EmptyView()
}

}.navigationDestination(for: RegionSelectionDestinations.self) { route in
switch route {
case .search:
let regionsView = RegionsSelectionFactory.makeRegionsListView()

regionsView
.searchable(text: regionsView.$viewModel.search, placement: SearchFieldPlacement.automatic)
}
}


}
}

#Preview {
RegionsContainerView(viewModel: RegionsSelectionFactory.makeRegionsContainerViewModel())
}
1 change: 0 additions & 1 deletion PIA VPN-tvOS/RegionsSelection/UI/RegionsListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ struct RegionsListView: View {
}
}
}
.frame(width: viewWidth - 50, height: viewHeight - 100)

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct UserActivatedContainerView: View {
.navigationDestination(for: RegionsDestinations.self) { destination in
switch destination {
case .serversList:
RegionsSelectionFactory.makeRegionsListView()
RegionsSelectionFactory.makeRegionsContainerView()
case .selectServer(let selectedServer):
VStack {
Text("Selected server: \(selectedServer.name)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import Foundation
import PIALibrary

class VPNConfigurationInstallingFactory {
private static var isSimulator: Bool {
#if targetEnvironment(simulator)
return true
#else
return false
#endif
}

static func makeVPNConfigurationInstallingView() -> VPNConfigurationInstallingView {
VPNConfigurationInstallingView(viewModel: makeVPNConfigurationInstallingViewModel())
}
Expand All @@ -23,11 +31,17 @@ class VPNConfigurationInstallingFactory {
}

private static func makeInstallVPNConfigurationUseCase() -> InstallVPNConfigurationUseCaseType {
InstallVpnConfigurationProvider(vpnProvider: makeVpnConfigurationProvider(),

guard !isSimulator else {
return InstallVPNConfigurationUseCaseMock(error: nil)
}

return InstallVpnConfigurationProvider(vpnProvider: makeVpnConfigurationProvider(),
vpnConfigurationAvailability: VPNConfigurationAvailability())
}

private static func makeVpnConfigurationProvider() -> VpnConfigurationProviderType {
VpnConfigurationProvider(vpnProvider: Client.providers.vpnProvider)
return VpnConfigurationProvider(vpnProvider: Client.providers.vpnProvider)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class InstallVpnConfigurationProvider: InstallVPNConfigurationUseCaseType {
self.vpnConfigurationAvailability = vpnConfigurationAvailability
}


func callAsFunction() async throws {
return try await withCheckedThrowingContinuation { continuation in
vpnProvider.install(force: true) { [self] error in
Expand All @@ -28,6 +29,7 @@ class InstallVpnConfigurationProvider: InstallVPNConfigurationUseCaseType {

continuation.resume()
vpnConfigurationAvailability.set(value: true)

}
}
}
Expand Down
23 changes: 23 additions & 0 deletions PIA VPN-tvOSTests/RegionsList/RegionsListViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ class RegionsListViewModelTests: XCTestCase {
class Fixture {
let regionsListUseCaseMock = RegionsListUseCaseMock()
let appRouterSpy = AppRouterSpy()

var allServers: [ServerMock] = [
ServerMock(name: "Toronto", identifier: "ca-server", regionIdentifier: "ca-region", country: "CA", geo: false),
ServerMock(name: "Montreal", identifier: "ca-server2", regionIdentifier: "ca-region2", country: "CA", geo: false),
ServerMock(name: "Barcelona-1", identifier: "es-server", regionIdentifier: "es-region", country: "ES", geo: false),
ServerMock(name: "Madrid", identifier: "es-server2", regionIdentifier: "es-region2", country: "ES", geo: false)

]
}

var fixture: Fixture!
Expand Down Expand Up @@ -61,4 +69,19 @@ class RegionsListViewModelTests: XCTestCase {

}

func test_regionsDidSearch() {
// GIVEN THAT we have 4 servers available (2 in CA and 2 in ES)
fixture.regionsListUseCaseMock.getCurrentServersResult = fixture.allServers
instantiateSut(with: .pop(router: fixture.appRouterSpy))
XCTAssertEqual(sut.servers.count, 4)

// WHEN we search for 'Canada'
sut.search = "Canada"

// THEN the displayed servers are only 2 (The ones in 'CA')
XCTAssertEqual(sut.servers.count, 2)
XCTAssertEqual(sut.servers.first!.country, "CA")
XCTAssertEqual(sut.servers.last!.country, "CA")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

import Foundation
@testable import PIA_VPN_tvOS

class InstallVPNConfigurationUseCaseMock: InstallVPNConfigurationUseCaseType {
private let error: InstallVPNConfigurationError?
Expand Down
14 changes: 12 additions & 2 deletions PIA VPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,14 @@
696E8F0D2B31A8760080BB31 /* NotificationCenterMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 696E8F0C2B31A8760080BB31 /* NotificationCenterMock.swift */; };
696E8F102B31AC690080BB31 /* RootContainerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 696E8F0F2B31AC690080BB31 /* RootContainerViewModelTests.swift */; };
69793DA12B59B96B000CB845 /* RegionsListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69793DA02B59B96B000CB845 /* RegionsListViewModelTests.swift */; };
69793DA42B5A9B27000CB845 /* RegionsContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69793DA32B5A9B27000CB845 /* RegionsContainerView.swift */; };
69793DA62B5A9E1E000CB845 /* RegionsContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69793DA52B5A9E1E000CB845 /* RegionsContainerViewModel.swift */; };
697A5F452B514B5700661977 /* AppRouterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 697A5F442B514B5700661977 /* AppRouterFactory.swift */; };
697A5F4D2B514D9600661977 /* UserActivatedContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 697A5F4C2B514D9600661977 /* UserActivatedContainerView.swift */; };
697A5F512B514DC500661977 /* UserActivatedContainerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 697A5F502B514DC500661977 /* UserActivatedContainerFactory.swift */; };
69816C3D2B4EBB3C00E3C86B /* Destinations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69816C3C2B4EBB3C00E3C86B /* Destinations.swift */; };
698615A72B62EA7C00A1EA54 /* InstallVPNConfigurationUseCaseMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28DF2B4C107F00744E5F /* InstallVPNConfigurationUseCaseMock.swift */; };
698615A82B62EA7F00A1EA54 /* InstallVPNConfigurationUseCaseMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28DF2B4C107F00744E5F /* InstallVPNConfigurationUseCaseMock.swift */; };
698C3B492B2B33650012D527 /* VpnConnectionUseCaseMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698C3B482B2B33650012D527 /* VpnConnectionUseCaseMock.swift */; };
698C3B4B2B2B34760012D527 /* PIAConnectionButtonViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698C3B4A2B2B34760012D527 /* PIAConnectionButtonViewModelTests.swift */; };
698C3B4E2B2B3CBE0012D527 /* LoginIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28742B279BB600744E5F /* LoginIntegrationTests.swift */; };
Expand Down Expand Up @@ -636,7 +640,6 @@
E5AB28D12B4B39F000744E5F /* VPNConfigurationInstallingViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28CF2B4B39F000744E5F /* VPNConfigurationInstallingViewModelTests.swift */; };
E5AB28E02B4C107F00744E5F /* VPNConfigurationAvailabilityMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28DD2B4C107F00744E5F /* VPNConfigurationAvailabilityMock.swift */; };
E5AB28E12B4C107F00744E5F /* VpnConfigurationProviderTypeMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28DE2B4C107F00744E5F /* VpnConfigurationProviderTypeMock.swift */; };
E5AB28E22B4C107F00744E5F /* InstallVPNConfigurationUseCaseMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AB28DF2B4C107F00744E5F /* InstallVPNConfigurationUseCaseMock.swift */; };
E5C507682B0D609300200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C507672B0D609300200A6A /* PIALibrary */; };
E5C5076A2B0D60A500200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C507692B0D60A500200A6A /* PIALibrary */; };
E5C5076C2B0D60AD00200A6A /* PIALibrary in Frameworks */ = {isa = PBXBuildFile; productRef = E5C5076B2B0D60AD00200A6A /* PIALibrary */; };
Expand Down Expand Up @@ -991,6 +994,8 @@
696E8F0C2B31A8760080BB31 /* NotificationCenterMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCenterMock.swift; sourceTree = "<group>"; };
696E8F0F2B31AC690080BB31 /* RootContainerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootContainerViewModelTests.swift; sourceTree = "<group>"; };
69793DA02B59B96B000CB845 /* RegionsListViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsListViewModelTests.swift; sourceTree = "<group>"; };
69793DA32B5A9B27000CB845 /* RegionsContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsContainerView.swift; sourceTree = "<group>"; };
69793DA52B5A9E1E000CB845 /* RegionsContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsContainerViewModel.swift; sourceTree = "<group>"; };
697A5F442B514B5700661977 /* AppRouterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouterFactory.swift; sourceTree = "<group>"; };
697A5F4C2B514D9600661977 /* UserActivatedContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivatedContainerView.swift; sourceTree = "<group>"; };
697A5F502B514DC500661977 /* UserActivatedContainerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivatedContainerFactory.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2139,6 +2144,7 @@
isa = PBXGroup;
children = (
69E61DE72B55643500085648 /* RegionsListView.swift */,
69793DA32B5A9B27000CB845 /* RegionsContainerView.swift */,
);
path = UI;
sourceTree = "<group>";
Expand All @@ -2147,6 +2153,7 @@
isa = PBXGroup;
children = (
69E61DE92B55644900085648 /* RegionsListViewModel.swift */,
69793DA52B5A9E1E000CB845 /* RegionsContainerViewModel.swift */,
);
path = Presentation;
sourceTree = "<group>";
Expand Down Expand Up @@ -4004,7 +4011,9 @@
69A226B82B3079EA0065EDDB /* PIA+String.swift in Sources */,
69F1C29C2B2B299300E924AE /* VpnConnectionUseCase.swift in Sources */,
69A226B32B3074D30065EDDB /* AppRouter.swift in Sources */,
698615A72B62EA7C00A1EA54 /* InstallVPNConfigurationUseCaseMock.swift in Sources */,
693CA5AB2B3ED67A00D38378 /* AppPreferences.swift in Sources */,
69793DA62B5A9E1E000CB845 /* RegionsContainerViewModel.swift in Sources */,
E5AB288A2B29BDED00744E5F /* Foundation+PIA.swift in Sources */,
E5C507A22B0FE40000200A6A /* LoginView.swift in Sources */,
693CA5AD2B3ED74100D38378 /* AppConfiguration.swift in Sources */,
Expand Down Expand Up @@ -4041,6 +4050,7 @@
694AC74E2B17AB9C007E7B56 /* DashboardView.swift in Sources */,
69F1C2932B2B10D400E924AE /* DashboardFactory.swift in Sources */,
E5AB28B62B361E6C00744E5F /* VPNConfigurationInstallingErrorMapper.swift in Sources */,
69793DA42B5A9B27000CB845 /* RegionsContainerView.swift in Sources */,
E5C5077A2B0E144E00200A6A /* PIA_VPN_tvOSApp.swift in Sources */,
69F1C2952B2B216100E924AE /* PIA+Animation.swift in Sources */,
69A226B52B3075AB0065EDDB /* RootContainerViewModel.swift in Sources */,
Expand All @@ -4067,7 +4077,6 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E5AB28E22B4C107F00744E5F /* InstallVPNConfigurationUseCaseMock.swift in Sources */,
E5AB28D02B4B39F000744E5F /* InstallVpnConfigurationProviderTests.swift in Sources */,
698C3B492B2B33650012D527 /* VpnConnectionUseCaseMock.swift in Sources */,
69CA26B22B59668700E78894 /* RegionsListUseCaseMock.swift in Sources */,
Expand All @@ -4078,6 +4087,7 @@
E5AB286C2B2796E700744E5F /* LoginProviderMock.swift in Sources */,
E5AB28872B2911C900744E5F /* AccountProviderMock.swift in Sources */,
696E8F102B31AC690080BB31 /* RootContainerViewModelTests.swift in Sources */,
698615A82B62EA7F00A1EA54 /* InstallVPNConfigurationUseCaseMock.swift in Sources */,
E52E691F2B5DCEA500471913 /* UserAuthenticationStatusMonitorMock.swift in Sources */,
E52E690F2B5D695A00471913 /* VPNStatusMonitorTests.swift in Sources */,
698C3B4E2B2B3CBE0012D527 /* LoginIntegrationTests.swift in Sources */,
Expand Down
Loading
Loading