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-886: Dashboard selected region and Quick Connect sections #54

Merged
merged 2 commits into from
Jan 4, 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
52 changes: 47 additions & 5 deletions PIA VPN-tvOS/Dashboard/CompositionRoot/DashboardFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import PIALibrary
class DashboardFactory {

static func makeDashboardView() -> DashboardView {
return DashboardView(
viewModel: makeDashboardViewModel(),
connectionButton: makePIAConnectionButton()
)
return DashboardView(viewModel: makeDashboardViewModel())
}

static func makeDashboardViewModel() -> DashboardViewModel {
Expand Down Expand Up @@ -36,7 +33,52 @@ extension DashboardFactory {
}

private static func makeVpnConnectionUseCase() -> VpnConnectionUseCaseType {
return VpnConnectionUseCase()
return VpnConnectionUseCase(serverProvider: makeServerProvider())
}

private static func makeSelectedServerUserCase() -> SelectedServerUseCaseType {
return SelectedServerUseCase(serverProvider: makeServerProvider())
}

private static func makeSelectedServerViewModel() -> SelectedServerViewModel {
return SelectedServerViewModel(useCase: makeSelectedServerUserCase())
}

internal static func makeSelectedServerView() -> SelectedServerView {
SelectedServerView(viewModel: makeSelectedServerViewModel())
}

}


// MARK: QuickConnect section

extension DashboardFactory {

static func makeServerProvider() -> ServerProviderType {
guard let defaultServerProvider: DefaultServerProvider =
Client.providers.serverProvider as? DefaultServerProvider else {
fatalError("Incorrect server provider type")
}

return defaultServerProvider

}

static internal func makeQuickConnectButtonViewModel(for server: ServerType, delegate: QuickConnectButtonViewModelDelegate?) -> QuickConnectButtonViewModel {
QuickConnectButtonViewModel(server: server, delegate: delegate)
}

static func makeQuickConnectButton(for server: ServerType, delegate: QuickConnectButtonViewModelDelegate?) -> QuickConnectButton {
QuickConnectButton(viewModel: makeQuickConnectButtonViewModel(for: server, delegate: delegate))
}

static internal func makeQuickConnectViewModel() -> QuickConnectViewModel {
QuickConnectViewModel(connectUseCase: makeVpnConnectionUseCase(), selectedServerUseCase: makeSelectedServerUserCase())
}

static func makeQuickConnectView() -> QuickConnectView {
QuickConnectView(viewModel: makeQuickConnectViewModel())
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

import Foundation

protocol QuickConnectButtonViewModelDelegate: AnyObject {
func quickConnectButtonViewModel(didSelect server: ServerType)
}

class QuickConnectButtonViewModel: ObservableObject {

private let server: ServerType

@Published var flagName = ""

weak var delegate: QuickConnectButtonViewModelDelegate?

init(server: ServerType, delegate: QuickConnectButtonViewModelDelegate?) {
self.server = server
self.delegate = delegate
updateStatus()
}

func connectButtonDidTap() {
delegate?.quickConnectButtonViewModel(didSelect: server)
}

private func updateStatus() {
flagName = "flag-\(server.country.lowercased())"
}


}
33 changes: 33 additions & 0 deletions PIA VPN-tvOS/Dashboard/Presentation/QuickConnectViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

import Foundation

class QuickConnectViewModel: ObservableObject {

@Published private (set) var servers: [ServerType] = []

private let connectUseCase: VpnConnectionUseCaseType
private let selectedServerUseCase: SelectedServerUseCaseType


init(connectUseCase: VpnConnectionUseCaseType,
selectedServerUseCase: SelectedServerUseCaseType) {
self.connectUseCase = connectUseCase
self.selectedServerUseCase = selectedServerUseCase

updateStatus()
}

func updateStatus() {
servers = selectedServerUseCase.getHistoricalServers()
}

}

extension QuickConnectViewModel: QuickConnectButtonViewModelDelegate {

func quickConnectButtonViewModel(didSelect server: ServerType) {
connectUseCase.connect(to: server)
}

}

20 changes: 20 additions & 0 deletions PIA VPN-tvOS/Dashboard/Presentation/SelectedServerViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

import Foundation


class SelectedServerViewModel: ObservableObject {
let selectedSeverSectionTitle = L10n.Localizable.Tiles.Region.title.uppercased()
@Published var serverName: String = ""

let useCase: SelectedServerUseCaseType

init(useCase: SelectedServerUseCaseType) {
self.useCase = useCase
updateState()
}

private func updateState() {
serverName = useCase.getSelectedServer().name
}

}
53 changes: 47 additions & 6 deletions PIA VPN-tvOS/Dashboard/UI/DashboardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,32 @@ struct DashboardView: View {
let viewHeight = UIScreen.main.bounds.height

let viewModel: DashboardViewModel
let connectionButton: PIAConnectionButton

var body: some View {
VStack {
VStack(spacing: 20) {
connectionButton
VStack(alignment: .leading) {
DashboardConnectionButtonSection()
.padding()

Divider()

SelectedServerSection()
.padding()

Divider()

QuickConnectSection()
.frame(width: (viewWidth/2))

Divider()

// TODO: Remove logout button from the Dashboard
// when we have it in the settings screen
Button {
viewModel.logOut()
} label: {
Text("LogOut")
}
.padding()

}
.frame(maxWidth: (viewWidth/2))
Expand All @@ -33,9 +45,38 @@ struct DashboardView: View {
}
}

// MARK: Dashboard sections

fileprivate struct DashboardConnectionButtonSection: View {
var body: some View {
HStack {
Spacer()
DashboardFactory.makePIAConnectionButton()
Spacer()
}
}
}

fileprivate struct SelectedServerSection: View {
var body: some View {
Button {
// TODO: Navigate to the regions list
} label: {
DashboardFactory.makeSelectedServerView()
}
.buttonStyle(.plain)
.buttonBorderShape(.roundedRectangle(radius: 4))
}
}

fileprivate struct QuickConnectSection: View {
var body: some View {
DashboardFactory.makeQuickConnectView()
}
}

#Preview {
DashboardView(
viewModel: DashboardFactory.makeDashboardViewModel(),
connectionButton: DashboardFactory.makePIAConnectionButton()
viewModel: DashboardFactory.makeDashboardViewModel()
)
}
29 changes: 29 additions & 0 deletions PIA VPN-tvOS/Dashboard/UI/QuickConnectButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// QuickConnectButton.swift
// PIA VPN-tvOS
//
// Created by Laura S on 12/26/23.
// Copyright © 2023 Private Internet Access Inc. All rights reserved.
//

import SwiftUI

struct QuickConnectButton: View {
@ObservedObject var viewModel: QuickConnectButtonViewModel

var size: CGFloat = 65

var body: some View {
Button {
viewModel.connectButtonDidTap()
} label: {
Image(viewModel.flagName)
.resizable()
.frame(width: size, height: size*0.75)
}
.buttonStyle(.card)
.buttonBorderShape(.roundedRectangle(radius: 2))

}
}

22 changes: 22 additions & 0 deletions PIA VPN-tvOS/Dashboard/UI/QuickConnectView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import SwiftUI

struct QuickConnectView: View {
@ObservedObject var viewModel: QuickConnectViewModel

let rows = [ GridItem(.adaptive(minimum: 80)) ]
var body: some View {
ScrollView(.horizontal) {
LazyHGrid(rows: rows) {
ForEach(viewModel.servers, id: \.regionIdentifier) { item in
DashboardFactory.makeQuickConnectButton(for: item, delegate: viewModel)
}
}
}

}
}

#Preview {
QuickConnectView(viewModel: DashboardFactory.makeQuickConnectViewModel())
}
38 changes: 38 additions & 0 deletions PIA VPN-tvOS/Dashboard/UI/SelectedServerView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import SwiftUI

struct SelectedServerView: View {
@Environment(\.colorScheme) var colorScheme

@ObservedObject var viewModel: SelectedServerViewModel

var body: some View {

HStack(alignment: .top) {
VStack(alignment: .leading) {
Text(viewModel.selectedSeverSectionTitle)
.font(.callout)
Text(viewModel.serverName)
.font(.caption)
}

Spacer()

HStack(alignment: .center) {
if colorScheme == .light {
Image.map
} else {
Image.map
.blendMode(.hardLight)
}

Image(systemName: "chevron.forward")
.foregroundStyle(Color.gray)
.frame(width: 50)

}

}
}
}

67 changes: 67 additions & 0 deletions PIA VPN-tvOS/Dashboard/UseCases/SelectedServerUseCase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

import Foundation
import PIALibrary

protocol SelectedServerUseCaseType {
func getSelectedServer() -> ServerType
func getHistoricalServers() -> [ServerType]
}

class SelectedServerUseCase: SelectedServerUseCaseType {

let serverProvider: ServerProviderType

init(serverProvider: ServerProviderType) {
self.serverProvider = serverProvider
}

func getSelectedServer() -> ServerType {
// TODO: get real selected server
return automaticServer()
}

func getHistoricalServers() -> [ServerType] {
// TODO: Remove this `guard` statement when we get
// the real historical servers
guard !serverProvider.historicalServers.isEmpty else {
return Self.generateDemoServers()
}
return serverProvider.historicalServers
}

private func automaticServer() -> ServerType {
Server(
serial: "",
name: L10n.Localizable.Global.automatic,
country: "universal",
hostname: "auto.bogus.domain",
pingAddress: nil,
regionIdentifier: "auto"
)
}
}

// TODO: Remove this extension when we have implemented
// getting the real historical servers (for QuickConnect)
extension SelectedServerUseCase {
private static func generateDemoServers() -> [ServerType] {
var servers: [ServerType] = []
func createDemoServer(name: String, country: String, regionIdentifier: String) -> ServerType {
Server(serial: "", name: name, country: country, hostname: "auto.bogus.domain", pingAddress: nil, regionIdentifier: regionIdentifier)
}

servers.append(createDemoServer(name: "Spain", country: "ES", regionIdentifier: "Spain"))
servers.append(createDemoServer(name: "France", country: "FR", regionIdentifier: "France"))
servers.append(createDemoServer(name: "Germany", country: "DE", regionIdentifier: "Germany"))
servers.append(createDemoServer(name: "Netherlands", country: "NL", regionIdentifier: "Netherlands"))
servers.append(createDemoServer(name: "Australia", country: "AU", regionIdentifier: "Australia"))
servers.append(createDemoServer(name: "Portugal", country: "PT", regionIdentifier: "Portugal"))
servers.append(createDemoServer(name: "Ireland", country: "IR", regionIdentifier: "Ireland"))
servers.append(createDemoServer(name: "Italy", country: "IT", regionIdentifier: "Italy"))
servers.append(createDemoServer(name: "Canada", country: "CA", regionIdentifier: "Canada"))


return servers

}
}
Loading
Loading