/*
* 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()
}
}