2024-11-25 21:54:22 +00:00
//
// V P N P r o v i d e r S e r v e r V i e w + C o n t a i n e r . 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 1 / 2 5 / 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 / > .
//
import CommonLibrary
import CommonUtils
import PassepartoutKit
import SwiftUI
extension VPNProviderServerView {
struct ContainerView : View {
@ EnvironmentObject
private var providerManager : ProviderManager
let vpnManager : VPNProviderManager < Configuration >
let apis : [ APIMapper ]
let moduleId : UUID
let providerId : ProviderID
let selectedServer : VPNServer ?
// 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
let filtersViewModel : VPNFiltersView . Model
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 ContentView (
2024-11-26 00:04:58 +00:00
apis : apis ,
providerId : providerId ,
2024-11-25 21:54:22 +00:00
servers : filteredServers ,
selectedServer : selectedServer ,
isFiltering : isFiltering ,
filtersViewModel : filtersViewModel ,
favoritesManager : favoritesManager ,
selectTitle : selectTitle ,
onSelect : onSelectServer
)
. task {
await loadInitialServers ( )
}
. onReceive ( filtersViewModel . $ filters . dropFirst ( ) , perform : onNewFilters )
. onReceive ( filtersViewModel . $ onlyShowsFavorites , perform : onToggleFavorites )
. onDisappear ( perform : onDisappear )
. navigationTitle ( title )
}
}
}
private extension VPNProviderServerView . ContainerView {
var title : String {
providerManager . provider ( withId : providerId ) ? . description ? ? Strings . Global . Nouns . servers
}
var filteredServers : [ VPNServer ] {
if onlyShowsFavorites {
return servers . filter {
favoritesManager . serverIds . contains ( $0 . serverId )
}
}
return servers
}
func compatiblePreset ( with server : VPNServer ) -> VPNPreset < Configuration > ? {
vpnManager
. presets
. first {
if let supportedIds = server . provider . supportedPresetIds {
return supportedIds . contains ( $0 . presetId )
}
return true
}
}
}
private extension VPNProviderServerView . ContainerView {
func loadInitialServers ( ) async {
do {
favoritesManager . moduleId = moduleId
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 {
pp_log ( . app , . error , " Unable to load VPN repository: \( error ) " )
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 ( ) {
favoritesManager . save ( )
}
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 ? ? [ ] ) " )
assertionFailure ( " No compatible presets for server \( server . serverId ) (provider= \( vpnManager . providerId ) , configuration= \( Configuration . providerConfigurationIdentifier ) , supported= \( server . provider . supportedPresetIds ? ? [ ] ) ) " )
return
}
onSelect ( server , preset )
}
}