// // NetworkServersView.swift // SimpleX (iOS) // // Created by Evgeny on 02/08/2022. // Copyright © 2022 SimpleX Chat. All rights reserved. // import SwiftUI import SimpleXChat private enum NetworkAlert: Identifiable { case updateOnionHosts(hosts: OnionHosts) case updateSessionMode(mode: TransportSessionMode) case error(err: String) var id: String { switch self { case let .updateOnionHosts(hosts): return "updateOnionHosts \(hosts)" case let .updateSessionMode(mode): return "updateSessionMode \(mode)" case let .error(err): return "error \(err)" } } } struct NetworkAndServers: View { @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @State private var cfgLoaded = false @State private var currentNetCfg = NetCfg.defaults @State private var netCfg = NetCfg.defaults @State private var onionHosts: OnionHosts = .no @State private var sessionMode: TransportSessionMode = .user @State private var alert: NetworkAlert? var body: some View { VStack { List { Section { NavigationLink { ProtocolServersView(serverProtocol: .smp) .navigationTitle("Your SMP servers") } label: { Text("SMP servers") } NavigationLink { ProtocolServersView(serverProtocol: .xftp) .navigationTitle("Your XFTP servers") } label: { Text("XFTP servers") } Picker("Use .onion hosts", selection: $onionHosts) { ForEach(OnionHosts.values, id: \.self) { Text($0.text) } } .frame(height: 36) if developerTools { Picker("Transport isolation", selection: $sessionMode) { ForEach(TransportSessionMode.values, id: \.self) { Text($0.text) } } .frame(height: 36) } NavigationLink { AdvancedNetworkSettings() .navigationTitle("Network settings") } label: { Text("Advanced network settings") } } header: { Text("Messages & files") } footer: { Text("Using .onion hosts requires compatible VPN provider.") } Section("Calls") { NavigationLink { RTCServers() .navigationTitle("Your ICE servers") } label: { Text("WebRTC ICE servers") } } } } .onAppear { if cfgLoaded { return } cfgLoaded = true currentNetCfg = getNetCfg() resetNetCfgView() } .onChange(of: onionHosts) { _ in if onionHosts != OnionHosts(netCfg: currentNetCfg) { alert = .updateOnionHosts(hosts: onionHosts) } } .onChange(of: sessionMode) { _ in if sessionMode != netCfg.sessionMode { alert = .updateSessionMode(mode: sessionMode) } } .alert(item: $alert) { a in switch a { case let .updateOnionHosts(hosts): return Alert( title: Text("Update .onion hosts setting?"), message: Text(onionHostsInfo(hosts)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."), primaryButton: .default(Text("Ok")) { let (hostMode, requiredHostMode) = hosts.hostMode netCfg.hostMode = hostMode netCfg.requiredHostMode = requiredHostMode saveNetCfg() }, secondaryButton: .cancel() { resetNetCfgView() } ) case let .updateSessionMode(mode): return Alert( title: Text("Update transport isolation mode?"), message: Text(sessionModeInfo(mode)) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."), primaryButton: .default(Text("Ok")) { netCfg.sessionMode = mode saveNetCfg() }, secondaryButton: .cancel() { resetNetCfgView() } ) case let .error(err): return Alert( title: Text("Error updating settings"), message: Text(err) ) } } } private func saveNetCfg() { do { let def = netCfg.hostMode == .onionHost ? NetCfg.proxyDefaults : NetCfg.defaults netCfg.tcpConnectTimeout = def.tcpConnectTimeout netCfg.tcpTimeout = def.tcpTimeout try setNetworkConfig(netCfg) currentNetCfg = netCfg setNetCfg(netCfg) } catch let error { let err = responseError(error) resetNetCfgView() alert = .error(err: err) logger.error("\(err)") } } private func resetNetCfgView() { netCfg = currentNetCfg onionHosts = OnionHosts(netCfg: netCfg) sessionMode = netCfg.sessionMode } private func onionHostsInfo(_ hosts: OnionHosts) -> LocalizedStringKey { switch hosts { case .no: return "Onion hosts will not be used." case .prefer: return "Onion hosts will be used when available. Requires enabling VPN." case .require: return "Onion hosts will be required for connection. Requires enabling VPN." } } private func sessionModeInfo(_ mode: TransportSessionMode) -> LocalizedStringKey { switch mode { case .user: return "A separate TCP connection will be used **for each chat profile you have in the app**." case .entity: return "A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." } } } struct NetworkServersView_Previews: PreviewProvider { static var previews: some View { NetworkAndServers() } }