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