/* * Copyright (C) 2023 MURENA SAS * Copyright (C) 2021 E FOUNDATION * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package foundation.e.advancedprivacy.features.internetprivacy import androidx.annotation.StringRes import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import foundation.e.advancedprivacy.R import foundation.e.advancedprivacy.domain.entities.FeatureState import foundation.e.advancedprivacy.domain.usecases.AppListUseCase import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.advancedprivacy.domain.usecases.IpScramblingStateUseCase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class InternetPrivacyViewModel( private val getQuickPrivacyStateUseCase: GetQuickPrivacyStateUseCase, private val ipScramblingStateUseCase: IpScramblingStateUseCase, private val appListUseCase: AppListUseCase ) : ViewModel() { companion object { private const val WARNING_LOADING_LONG_DELAY = 5 * 1000L } private val _state = MutableStateFlow(InternetPrivacyState()) val state = _state.asStateFlow() private val _singleEvents = MutableSharedFlow() val singleEvents = _singleEvents.asSharedFlow() val availablesLocationsIds = listOf("", *ipScramblingStateUseCase.availablesLocations.toTypedArray()) init { viewModelScope.launch(Dispatchers.IO) { _state.update { it.copy( mode = ipScramblingStateUseCase.internetPrivacyMode.value, availableLocationIds = availablesLocationsIds, selectedLocation = ipScramblingStateUseCase.exitCountry ) } } } @OptIn(FlowPreview::class) suspend fun doOnStartedState() = withContext(Dispatchers.IO) { launch { merge( appListUseCase.getAppsUsingInternet().map { apps -> _state.update { s -> s.copy( availableApps = apps, bypassTorApps = ipScramblingStateUseCase.bypassTorApps ) } }, ipScramblingStateUseCase.internetPrivacyMode.map { _state.update { s -> s.copy(mode = it) } } ).collect {} } launch { ipScramblingStateUseCase.internetPrivacyMode .map { it == FeatureState.STARTING } .debounce(WARNING_LOADING_LONG_DELAY) .collect { if (it) _singleEvents.emit( SingleEvent.ErrorEvent(R.string.ipscrambling_warning_starting_long) ) } } launch { getQuickPrivacyStateUseCase.otherVpnRunning.collect { _singleEvents.emit( SingleEvent.ErrorEvent( R.string.ipscrambling_error_always_on_vpn_already_running, listOf(it.label ?: "") ) ) _state.update { it.copy(forceRedraw = !it.forceRedraw) } } } } fun submitAction(action: Action) = viewModelScope.launch { when (action) { is Action.UseRealIPAction -> actionUseRealIP() is Action.UseHiddenIPAction -> actionUseHiddenIP() is Action.ToggleAppIpScrambled -> actionToggleAppIpScrambled(action) is Action.SelectLocationAction -> actionSelectLocation(action) } } private suspend fun actionUseRealIP() { ipScramblingStateUseCase.toggle(hideIp = false) } private suspend fun actionUseHiddenIP() { ipScramblingStateUseCase.toggle(hideIp = true) } private suspend fun actionToggleAppIpScrambled(action: Action.ToggleAppIpScrambled) = withContext(Dispatchers.IO) { ipScramblingStateUseCase.toggleBypassTor(action.packageName) _state.update { it.copy(bypassTorApps = ipScramblingStateUseCase.bypassTorApps) } } private suspend fun actionSelectLocation(action: Action.SelectLocationAction) = withContext(Dispatchers.IO) { val locationId = _state.value.availableLocationIds[action.position] ipScramblingStateUseCase.setExitCountry(locationId) _state.update { it.copy(selectedLocation = locationId) } } sealed class SingleEvent { data class ErrorEvent( @StringRes val errorResId: Int, val args: List = emptyList() ) : SingleEvent() } sealed class Action { object UseRealIPAction : Action() object UseHiddenIPAction : Action() data class ToggleAppIpScrambled(val packageName: String) : Action() data class SelectLocationAction(val position: Int) : Action() } }