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-12-04 12:43:50 +00:00
struct VPNProviderServerView < Configuration > : View where Configuration : IdentifiableConfiguration {
2024-11-26 08:20:00 +00:00
@ EnvironmentObject
private var providerManager : ProviderManager
2024-12-06 10:24:51 +00:00
@ EnvironmentObject
private var preferencesManager : PreferencesManager
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
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-23 19:31:22 +00:00
var selectTitle = Strings . Views . 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-11-26 08:20:00 +00:00
@ State
private var servers : [ VPNServer ] = [ ]
@ State
private var isFiltering = false
@ State
private var onlyShowsFavorites = false
2024-10-26 18:28:02 +00:00
@ StateObject
2024-12-08 15:05:23 +00:00
private var providerPreferences = ProviderPreferences ( )
2024-10-26 11:29:26 +00:00
2024-11-26 08:20:00 +00:00
@ StateObject
2024-12-06 10:24:51 +00:00
private var filtersViewModel = VPNFiltersView . Model ( )
2024-11-26 08:20:00 +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-11-26 08:20:00 +00:00
return ContainerView (
content : contentView ,
filters : filtersView
)
. navigationTitle ( title )
. themeNavigationDetail ( )
. withErrorHandler ( errorHandler )
2024-10-26 18:28:02 +00:00
}
}
extension VPNProviderServerView {
2024-11-26 08:20:00 +00:00
func contentView ( ) -> some View {
ContentView (
2024-10-26 18:28:02 +00:00
apis : apis ,
providerId : providerId ,
2024-11-26 08:20:00 +00:00
servers : filteredServers ,
2024-11-11 19:21:02 +00:00
selectedServer : selectedEntity ? . server ,
2024-11-26 08:20:00 +00:00
isFiltering : isFiltering ,
2024-11-25 21:54:22 +00:00
filtersViewModel : filtersViewModel ,
2024-12-06 10:24:51 +00:00
providerPreferences : providerPreferences ,
2024-10-25 09:38:27 +00:00
selectTitle : selectTitle ,
2024-11-26 08:20:00 +00:00
onSelect : onSelectServer
2024-10-18 16:12:28 +00:00
)
2024-11-26 08:20:00 +00:00
. task {
await loadInitialServers ( )
}
. onReceive ( filtersViewModel . $ filters . dropFirst ( ) , perform : onNewFilters )
. onReceive ( filtersViewModel . $ onlyShowsFavorites , perform : onToggleFavorites )
. onDisappear ( perform : onDisappear )
2024-10-26 18:28:02 +00:00
}
2024-11-26 08:20:00 +00:00
func filtersView ( ) -> some View {
2024-11-26 00:04:58 +00:00
VPNFiltersView (
apis : apis ,
providerId : providerId ,
model : filtersViewModel
)
2024-10-26 18:28:02 +00:00
}
2024-11-26 08:20:00 +00:00
}
private extension VPNProviderServerView {
var title : String {
providerManager . provider ( withId : providerId ) ? . description ? ? Strings . Global . Nouns . servers
}
var filteredServers : [ VPNServer ] {
if onlyShowsFavorites {
return servers . filter {
2024-12-10 14:19:07 +00:00
providerPreferences . isFavoriteServer ( $0 . serverId )
2024-11-26 08:20:00 +00:00
}
}
return servers
}
2024-10-26 18:28:02 +00:00
2024-12-10 13:45:07 +00:00
var initialFilters : VPNFilters {
2024-11-25 21:54:22 +00:00
var filters = VPNFilters ( )
2024-12-10 13:45:07 +00:00
// f o r c e i n i t i a l p r e s e t f i l t e r
filters . presetId = vpnManager . options . presets . first ? . presetId
if let selectedEntity {
filters . presetId = selectedEntity . preset . presetId
if filtersWithSelection {
filters . categoryName = selectedEntity . server . provider . categoryName
2024-11-25 21:54:22 +00:00
#if os ( macOS )
2024-12-10 13:45:07 +00:00
filters . countryCode = selectedEntity . server . provider . countryCode
2024-11-25 21:54:22 +00:00
#endif
2024-12-10 13:45:07 +00:00
}
}
2024-11-25 21:54:22 +00:00
return filters
2024-10-18 16:12:28 +00:00
}
2024-11-26 08:20:00 +00:00
func compatiblePreset ( with server : VPNServer ) -> VPNPreset < Configuration > ? {
vpnManager
. presets
2024-12-10 13:45:07 +00:00
. filter {
$0 . presetId = = filtersViewModel . filters . presetId
2024-12-08 23:25:09 +00:00
}
2024-11-26 08:20:00 +00:00
. first {
if let supportedIds = server . provider . supportedPresetIds {
return supportedIds . contains ( $0 . presetId )
}
return true
}
}
}
private extension VPNProviderServerView {
func loadInitialServers ( ) async {
do {
2024-12-10 13:13:10 +00:00
let repository = try preferencesManager . preferencesRepository ( forProviderWithId : providerId )
providerPreferences . setRepository ( repository )
2024-12-06 10:24:51 +00:00
} catch {
pp_log ( . app , . error , " Unable to load preferences for provider \( providerId ) : \( error ) " )
}
do {
2024-11-26 08:20:00 +00:00
let repository = try await providerManager . vpnServerRepository (
from : apis ,
for : providerId
)
try await vpnManager . setRepository ( repository )
filtersViewModel . load ( options : vpnManager . options , initialFilters : initialFilters )
await reloadServers ( filters : filtersViewModel . filters )
} catch {
2024-12-06 10:24:51 +00:00
pp_log ( . app , . error , " Unable to load VPN servers for provider \( providerId ) : \( error ) " )
2024-11-26 08:20:00 +00:00
errorHandler . handle ( error , title : Strings . Global . Nouns . servers )
}
}
func reloadServers ( filters : VPNFilters ) async {
isFiltering = true
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 ) " )
}
}
func onNewFilters ( _ filters : VPNFilters ) {
Task {
await reloadServers ( filters : filters )
}
}
func onToggleFavorites ( _ only : Bool ) {
onlyShowsFavorites = only
}
func onDisappear ( ) {
2024-12-06 10:24:51 +00:00
do {
try providerPreferences . save ( )
} catch {
pp_log ( . app , . error , " Unable to save preferences: \( error ) " )
}
2024-11-26 08:20:00 +00:00
}
func onSelectServer ( _ server : VPNServer ) {
guard let preset = compatiblePreset ( with : server ) else {
pp_log ( . app , . error , " Unable to find a compatible preset. Supported IDs: \( server . provider . supportedPresetIds ? ? [ ] ) " )
2024-12-04 11:40:47 +00:00
assertionFailure ( " No compatible presets for server \( server . serverId ) (provider= \( vpnManager . providerId ) , configuration= \( Configuration . configurationIdentifier ) , supported= \( server . provider . supportedPresetIds ? ? [ ] ) ) " )
2024-11-26 08:20:00 +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-12-04 11:40:47 +00:00
selectedEntity : nil as VPNEntity < OpenVPN . Configuration > ? ,
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 ( )
}