@@ -18,6 +18,7 @@ import org.monogram.core.ScopeProvider
1818import org.monogram.data.datasource.remote.ChatRemoteSource
1919import org.monogram.data.datasource.remote.ProxyRemoteDataSource
2020import org.monogram.data.gateway.UpdateDispatcher
21+ import org.monogram.domain.infra.VpnDetector
2122import org.monogram.domain.repository.AppPreferencesProvider
2223import org.monogram.domain.repository.ConnectionStatus
2324import kotlin.random.Random
@@ -29,6 +30,7 @@ class ConnectionManager(
2930 private val appPreferences : AppPreferencesProvider ,
3031 private val dispatchers : DispatcherProvider ,
3132 private val connectivityManager : ConnectivityManager ,
33+ private val vpnDetector : VpnDetector ,
3234 scopeProvider : ScopeProvider
3335) {
3436 private val TAG = " ConnectionManager"
@@ -67,13 +69,64 @@ class ConnectionManager(
6769 registerNetworkCallback()
6870 startWatchdog()
6971 startProxyManagement()
72+ startVpnMonitoring()
7073
7174 scope.launch(dispatchers.default) {
7275 runReconnectAttempt(" bootstrap" , force = true )
7376 syncConnectionStateFromTdlib(" bootstrap" )
7477 }
7578 }
7679
80+ private fun startVpnMonitoring () {
81+ vpnDetector.startMonitoring()
82+
83+ scope.launch {
84+ combine(
85+ appPreferences.isVpnAutoDisableEnabled,
86+ vpnDetector.isVpnActive
87+ ) { enabled, vpnActive -> enabled to vpnActive }
88+ .distinctUntilChanged()
89+ .collect { (enabled, vpnActive) ->
90+ if (! enabled) return @collect
91+
92+ if (vpnActive) {
93+ val currentProxyId = appPreferences.enabledProxyId.value
94+ if (currentProxyId != null ) {
95+ appPreferences.setSavedProxyBeforeVpn(currentProxyId)
96+ Log .d(TAG , " VPN detected active, disabling in-app proxy (saved proxy: $currentProxyId )" )
97+ coRunCatching {
98+ proxyRemoteSource.disableProxy()
99+ appPreferences.setEnabledProxyId(null )
100+ }.onFailure { Log .e(TAG , " Failed to disable proxy for VPN" , it) }
101+ }
102+ autoBestJob?.cancel()
103+ telegaSwitchJob?.cancel()
104+ } else {
105+ val savedProxyId = appPreferences.savedProxyBeforeVpn.value
106+ if (savedProxyId != null ) {
107+ Log .d(TAG , " VPN disconnected, restoring proxy: $savedProxyId " )
108+ coRunCatching {
109+ if (proxyRemoteSource.enableProxy(savedProxyId)) {
110+ appPreferences.setEnabledProxyId(savedProxyId)
111+ appPreferences.setSavedProxyBeforeVpn(null )
112+ } else {
113+ Log .w(TAG , " enableProxy returned false for saved proxy $savedProxyId , keeping it for retry" )
114+ }
115+ }.onFailure { Log .e(TAG , " Failed to restore proxy after VPN" , it) }
116+ }
117+
118+ if (appPreferences.isTelegaProxyEnabled.value) {
119+ telegaSwitchJob?.cancel()
120+ telegaSwitchJob = launchTelegaSwitchLoop()
121+ } else if (appPreferences.isAutoBestProxyEnabled.value) {
122+ autoBestJob?.cancel()
123+ autoBestJob = launchAutoBestLoop()
124+ }
125+ }
126+ }
127+ }
128+ }
129+
77130 private fun handleConnectionState (state : TdApi .ConnectionState , source : String ) {
78131 val status = when (state) {
79132 is TdApi .ConnectionStateReady -> ConnectionStatus .Connected
@@ -248,6 +301,11 @@ class ConnectionManager(
248301 }
249302
250303 private suspend fun selectBestProxy (telegaOnly : Boolean = false) {
304+ if (appPreferences.isVpnAutoDisableEnabled.value && vpnDetector.isVpnActive.value) {
305+ Log .d(TAG , " Skipping proxy selection — VPN is active" )
306+ return
307+ }
308+
251309 val allProxies = proxyRemoteSource.getProxies()
252310 val proxies = if (telegaOnly) {
253311 val telegaIds = getTelegaIdentifiers()
@@ -370,4 +428,17 @@ class ConnectionManager(
370428 connectivityManager.activeNetworkInfo?.isConnected == true
371429 }
372430 }
431+
432+ fun stop () {
433+ vpnDetector.stopMonitoring()
434+ retryJob?.cancel()
435+ proxyModeWatcherJob?.cancel()
436+ autoBestJob?.cancel()
437+ telegaSwitchJob?.cancel()
438+ watchdogJob?.cancel()
439+ networkCallback?.let {
440+ runCatching { connectivityManager.unregisterNetworkCallback(it) }
441+ networkCallback = null
442+ }
443+ }
373444}
0 commit comments