2024-10-10 22:24:06 +00:00
//
// V P N P r o v i d e r S e r v e r V i e w . s w i f t
// P a s s e p a r t o u t
//
// C r e a t e d b y D a v i d e D e R o s a o n 1 0 / 7 / 2 4 .
// C o p y r i g h t ( c ) 2 0 2 4 D a v i d e D e R o s a . A l l r i g h t s r e s e r v e d .
//
// h t t p s : / / g i t h u b . c o m / p a s s e p a r t o u t v p n
//
// T h i s f i l e i s p a r t o f P a s s e p a r t o u t .
//
// P a s s e p a r t o u t i s f r e e s o f t w a r e : y o u c a n r e d i s t r i b u t e i t a n d / o r m o d i f y
// i t u n d e r t h e t e r m s o f t h e G N U G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d b y
// t h e F r e e S o f t w a r e F o u n d a t i o n , e i t h e r v e r s i o n 3 o f t h e L i c e n s e , o r
// ( a t y o u r o p t i o n ) a n y l a t e r v e r s i o n .
//
// P a s s e p a r t o u t i s d i s t r i b u t e d i n t h e h o p e t h a t i t w i l l b e u s e f u l ,
// b u t W I T H O U T A N Y W A R R A N T Y ; w i t h o u t e v e n t h e i m p l i e d w a r r a n t y o f
// M E R C H A N T A B I L I T Y o r F I T N E S S F O R A P A R T I C U L A R P U R P O S E . S e e t h e
// G N U G e n e r a l P u b l i c L i c e n s e f o r m o r e d e t a i l s .
//
// Y o u s h o u l d h a v e r e c e i v e d a c o p y o f t h e G N U G e n e r a l P u b l i c L i c e n s e
// a l o n g w i t h P a s s e p a r t o u t . I f n o t , s e e < h t t p : / / w w w . g n u . o r g / l i c e n s e s / > .
//
2024-11-02 09:11:59 +00:00
import CommonAPI
2024-10-26 11:29:26 +00:00
import CommonLibrary
2024-11-02 09:11:59 +00:00
import CommonUtils
2024-10-10 22:24:06 +00:00
import PassepartoutKit
import SwiftUI
2024-10-15 19:34:02 +00:00
struct VPNProviderServerView < Configuration > : View where Configuration : ProviderConfigurationIdentifiable & Codable {
2024-10-23 13:42:54 +00:00
var apis : [ APIMapper ] = API . shared
2024-10-18 16:12:28 +00:00
2024-10-26 11:29:26 +00:00
let moduleId : UUID
2024-10-18 16:12:28 +00:00
let providerId : ProviderID
let configurationType : Configuration . Type
2024-10-23 15:58:04 +00:00
let selectedEntity : VPNEntity < Configuration > ?
2024-10-10 22:24:06 +00:00
2024-10-23 20:57:30 +00:00
let filtersWithSelection : Bool
2024-11-03 15:57:40 +00:00
var selectTitle = Strings . Providers . selectEntity
2024-10-25 09:38:27 +00:00
2024-10-26 18:28:02 +00:00
let onSelect : ( VPNServer , VPNPreset < Configuration > ) -> Void
2024-10-10 22:24:06 +00:00
2024-10-18 16:12:28 +00:00
@ StateObject
2024-10-26 18:28:02 +00:00
private var vpnManager = VPNProviderManager < Configuration > ( sorting : [
2024-10-18 16:12:28 +00:00
. localizedCountry ,
. area ,
2024-11-14 23:24:22 +00:00
. serverId
2024-10-18 16:12:28 +00:00
] )
2024-10-26 18:28:02 +00:00
@ StateObject
private var filtersViewModel = VPNFiltersView . Model ( )
2024-10-26 11:29:26 +00:00
2024-10-18 16:12:28 +00:00
@ StateObject
private var errorHandler : ErrorHandler = . default ( )
2024-10-10 22:24:06 +00:00
var body : some View {
2024-10-18 16:12:28 +00:00
debugChanges ( )
2024-10-26 18:28:02 +00:00
return contentView
. themeNavigationDetail ( )
. withErrorHandler ( errorHandler )
}
}
extension VPNProviderServerView {
var serversView : ServersView {
ServersView (
vpnManager : vpnManager ,
filtersViewModel : filtersViewModel ,
apis : apis ,
moduleId : moduleId ,
providerId : providerId ,
2024-11-11 19:21:02 +00:00
selectedServer : selectedEntity ? . server ,
2024-10-26 18:28:02 +00:00
initialFilters : {
guard let selectedEntity , filtersWithSelection else {
return nil
}
2024-10-28 23:15:56 +00:00
var filters = VPNFilters ( )
filters . categoryName = selectedEntity . server . provider . categoryName
#if os ( macOS )
filters . countryCode = selectedEntity . server . provider . countryCode
#endif
return filters
2024-10-26 18:28:02 +00:00
} ( ) ,
2024-10-25 09:38:27 +00:00
selectTitle : selectTitle ,
2024-10-26 18:28:02 +00:00
onSelect : onSelect ,
errorHandler : errorHandler
2024-10-18 16:12:28 +00:00
)
2024-10-26 18:28:02 +00:00
}
var filtersView : some View {
VPNFiltersView ( model : filtersViewModel )
}
}
// MARK: - S u b v i e w s
extension VPNProviderServerView {
struct ServersView : View {
@ EnvironmentObject
private var providerManager : ProviderManager
let vpnManager : VPNProviderManager < Configuration >
2024-10-28 22:00:41 +00:00
// B E W A R E : n o t o b s e r v e d ! u s e . o n R e c e i v e ( ) + @ S t a t e
2024-10-26 18:28:02 +00:00
let filtersViewModel : VPNFiltersView . Model
let apis : [ APIMapper ]
let moduleId : UUID
let providerId : ProviderID
2024-11-11 19:21:02 +00:00
let selectedServer : VPNServer ?
2024-10-26 18:28:02 +00:00
let initialFilters : VPNFilters ?
let selectTitle : String
let onSelect : ( VPNServer , VPNPreset < Configuration > ) -> Void
@ ObservedObject
var errorHandler : ErrorHandler
@ State
private var servers : [ VPNServer ] = [ ]
@ State
private var isFiltering = false
@ State
private var onlyShowsFavorites = false
@ StateObject
private var favoritesManager = ProviderFavoritesManager ( )
var body : some View {
debugChanges ( )
return ServersSubview (
servers : filteredServers ,
2024-11-11 19:21:02 +00:00
selectedServer : selectedServer ,
2024-10-26 18:28:02 +00:00
isFiltering : isFiltering ,
filtersViewModel : filtersViewModel ,
favoritesManager : favoritesManager ,
selectTitle : selectTitle ,
onSelect : onSelectServer
)
. task {
2024-10-18 16:12:28 +00:00
do {
2024-10-26 18:28:02 +00:00
favoritesManager . moduleId = moduleId
2024-10-28 15:57:23 +00:00
let repository = try await providerManager . vpnServerRepository (
2024-10-18 16:12:28 +00:00
from : apis ,
2024-10-25 08:54:28 +00:00
for : providerId
2024-10-18 16:12:28 +00:00
)
2024-10-28 15:57:23 +00:00
try await vpnManager . setRepository ( repository )
filtersViewModel . load ( options : vpnManager . options , initialFilters : initialFilters )
2024-10-26 18:28:02 +00:00
await reloadServers ( filters : filtersViewModel . filters )
2024-10-18 16:12:28 +00:00
} catch {
pp_log ( . app , . error , " Unable to load VPN repository: \( error ) " )
errorHandler . handle ( error , title : Strings . Global . servers )
}
}
2024-11-11 15:11:09 +00:00
. onReceive ( filtersViewModel . $ filters . dropFirst ( ) ) { newValue in
2024-10-26 18:28:02 +00:00
Task {
await reloadServers ( filters : newValue )
}
}
2024-11-05 10:28:27 +00:00
. onReceive ( filtersViewModel . $ onlyShowsFavorites ) { newValue in
2024-10-26 18:28:02 +00:00
onlyShowsFavorites = newValue
}
. onDisappear {
favoritesManager . save ( )
}
2024-10-28 15:57:23 +00:00
. navigationTitle ( title )
2024-10-18 16:12:28 +00:00
}
2024-10-10 22:24:06 +00:00
}
}
2024-10-26 18:28:02 +00:00
private extension VPNProviderServerView . ServersView {
2024-10-28 15:57:23 +00:00
var title : String {
providerManager . provider ( withId : providerId ) ? . description ? ? Strings . Global . servers
}
2024-10-26 18:28:02 +00:00
var filteredServers : [ VPNServer ] {
if onlyShowsFavorites {
return servers . filter {
favoritesManager . serverIds . contains ( $0 . serverId )
}
}
return servers
}
func reloadServers ( filters : VPNFilters ) async {
isFiltering = true
2024-10-28 15:57:23 +00:00
do {
try await Task {
servers = try await vpnManager . filteredServers ( with : filters )
filtersViewModel . update ( with : servers )
isFiltering = false
} . value
} catch {
pp_log ( . app , . error , " Unable to fetch filtered servers: \( error ) " )
}
2024-10-26 18:28:02 +00:00
}
2024-10-10 22:24:06 +00:00
func compatiblePreset ( with server : VPNServer ) -> VPNPreset < Configuration > ? {
2024-10-26 18:28:02 +00:00
vpnManager
2024-10-18 16:12:28 +00:00
. presets
2024-10-10 22:24:06 +00:00
. first {
if let supportedIds = server . provider . supportedPresetIds {
return supportedIds . contains ( $0 . presetId )
}
return true
}
}
2024-10-18 16:12:28 +00:00
2024-10-26 18:28:02 +00:00
func onSelectServer ( _ server : VPNServer ) {
2024-10-18 16:12:28 +00:00
guard let preset = compatiblePreset ( with : server ) else {
pp_log ( . app , . error , " Unable to find a compatible preset. Supported IDs: \( server . provider . supportedPresetIds ? ? [ ] ) " )
2024-10-26 18:28:02 +00:00
assertionFailure ( " No compatible presets for server \( server . serverId ) (provider= \( vpnManager . providerId ) , configuration= \( Configuration . providerConfigurationIdentifier ) , supported= \( server . provider . supportedPresetIds ? ? [ ] ) ) " )
2024-10-18 16:12:28 +00:00
return
}
onSelect ( server , preset )
}
2024-10-10 22:24:06 +00:00
}
// MARK: - P r e v i e w
# Preview {
NavigationStack {
2024-10-23 15:17:20 +00:00
VPNProviderServerView (
2024-10-18 16:12:28 +00:00
apis : [ API . bundled ] ,
2024-10-26 11:29:26 +00:00
moduleId : UUID ( ) ,
2024-10-18 16:12:28 +00:00
providerId : . protonvpn ,
2024-10-23 15:58:04 +00:00
configurationType : OpenVPN . Configuration . self ,
selectedEntity : nil ,
2024-10-23 20:57:30 +00:00
filtersWithSelection : false ,
2024-10-25 09:38:27 +00:00
selectTitle : " Select " ,
2024-10-23 15:58:04 +00:00
onSelect : { _ , _ in }
)
2024-10-10 22:24:06 +00:00
}
. withMockEnvironment ( )
}