/*
* Copyright (C) 2024 Leonard Kugis
* 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.location
import android.location.Location
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import foundation.e.advancedprivacy.domain.usecases.FakeLocationStateUseCase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
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
import kotlin.time.Duration.Companion.milliseconds
import foundation.e.advancedprivacy.domain.entities.FakeLocationCoordinate
import android.util.Log
class FakeLocationViewModel(
private val fakeLocationStateUseCase: FakeLocationStateUseCase
) : ViewModel() {
companion object {
private val SET_SPECIFIC_LOCATION_DELAY = 200.milliseconds
private val SET_MOCK_LOCATION_PARAMETERS_DELAY = 1000.milliseconds
private val SET_ROUTE_LOOP_ENABLED_DELAY = 1000.milliseconds
private val SET_ROUTE_DELAY = 1000.milliseconds
}
private val _state = MutableStateFlow(FakeLocationState())
val state = _state.asStateFlow()
val currentLocation: StateFlow = fakeLocationStateUseCase.currentLocation
private val _singleEvents = MutableSharedFlow()
val singleEvents = _singleEvents.asSharedFlow()
private val specificLocationInputFlow = MutableSharedFlow()
private val mockLocationParametersInputFlow = MutableSharedFlow()
private val routeLoopEnabledInputFlow = MutableSharedFlow()
private val routeInputFlow = MutableSharedFlow()
@OptIn(FlowPreview::class)
suspend fun doOnStartedState() = withContext(Dispatchers.Main) {
launch {
merge(
fakeLocationStateUseCase.configuredLocationMode.map { ss ->
_state.update { s ->
s.copy(
mode = ss.mode,
altitude = ss.altitude,
speed = ss.speed,
bearing = ss.bearing,
jitter = ss.jitter,
jitterAltitude = ss.jitterAltitude,
jitterSpeed = ss.jitterSpeed,
jitterBearing = ss.jitterBearing,
specificLatitude = ss.specificLatitude,
specificLongitude = ss.specificLongitude,
route = ss.route,
loopRoute = ss.loopRoute
)
}
},
specificLocationInputFlow
.debounce(SET_SPECIFIC_LOCATION_DELAY).map { action ->
fakeLocationStateUseCase.useFakeLocation(Pair(action.latitude, action.longitude))
},
mockLocationParametersInputFlow
.debounce(SET_MOCK_LOCATION_PARAMETERS_DELAY).map { action ->
fakeLocationStateUseCase.setFakeLocationParameters(action.altitude, action.speed, action.bearing, action.jitter, action.jitterAltitude, action.jitterSpeed, action.jitterBearing)
},
routeInputFlow
.debounce(SET_ROUTE_DELAY).map { action ->
fakeLocationStateUseCase.setRoute(action.route)
},
routeLoopEnabledInputFlow
.debounce(SET_ROUTE_LOOP_ENABLED_DELAY).map { action ->
fakeLocationStateUseCase.setRouteLoopEnabled(action.isEnabled)
},
).collect {}
}
}
fun submitAction(action: Action) = viewModelScope.launch {
when (action) {
is Action.StartListeningLocation -> actionStartListeningLocation()
is Action.StopListeningLocation -> fakeLocationStateUseCase.stopListeningLocation()
is Action.SetSpecificLocationAction -> setSpecificLocation(action)
is Action.UseRandomLocationAction -> fakeLocationStateUseCase.useRandomLocation()
is Action.UseRealLocationAction -> fakeLocationStateUseCase.useRealLocation()
is Action.UseRoute -> fakeLocationStateUseCase.useRoute()
is Action.UpdateMockLocationParameters -> updateMockLocationParameters(action)
is Action.SetRoute -> setRoute(action)
is Action.SetRouteLoopEnabledAction -> setRouteLoopEnabled(action)
is Action.RouteStartAction -> fakeLocationStateUseCase.routeStart()
is Action.RouteStopAction -> fakeLocationStateUseCase.routeStop()
}
}
private suspend fun actionStartListeningLocation() {
val started = fakeLocationStateUseCase.startListeningLocation()
if (!started) {
_singleEvents.emit(SingleEvent.RequestLocationPermission)
}
}
private suspend fun setSpecificLocation(action: Action.SetSpecificLocationAction) {
specificLocationInputFlow.emit(action)
}
private suspend fun updateMockLocationParameters(action: Action.UpdateMockLocationParameters) {
mockLocationParametersInputFlow.emit(action)
}
private suspend fun setRoute(action: Action.SetRoute) {
fakeLocationStateUseCase.setRoute(action.route)
routeInputFlow.emit(action)
}
private suspend fun setRouteLoopEnabled(action: Action.SetRouteLoopEnabledAction) {
routeLoopEnabledInputFlow.emit(action)
}
sealed class SingleEvent {
object RequestLocationPermission : SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
sealed class Action {
object StartListeningLocation : Action()
object StopListeningLocation : Action()
object UseRealLocationAction : Action()
object UseRandomLocationAction : Action()
object UseRoute : Action()
data class UpdateMockLocationParameters(
val altitude: Float,
val speed: Float,
val bearing: Float,
val jitter: Float,
val jitterAltitude: Float,
val jitterSpeed: Float,
val jitterBearing: Float
) : Action()
data class SetSpecificLocationAction(
val latitude: Float,
val longitude: Float
) : Action()
data class SetRoute(
val route: List
) : Action()
data class SetRouteLoopEnabledAction(
val isEnabled: Boolean
) : Action()
object RouteStartAction : Action()
object RouteStopAction : Action()
}
}