diff options
author | Amit Kumar <amitkma@e.email> | 2021-05-12 17:55:06 +0530 |
---|---|---|
committer | Amit Kumar <amitkma@e.email> | 2021-05-12 17:55:06 +0530 |
commit | f27cbbb44baf1cd437c74285155af2b5cbfb7953 (patch) | |
tree | bb0681345aec45bd3c63659caf917a43d6dfe8ab /app/src/main/java/foundation/e | |
parent | 574233624a9261a800fd5ddd234208f2c6994766 (diff) |
Add DI container to inject dependencies in features and viewmodels
Other changes:
- Safely parse latitude and longitude.
- Hide marker when map state is set to disabled after being enabled.
Diffstat (limited to 'app/src/main/java/foundation/e')
8 files changed, 177 insertions, 54 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt new file mode 100644 index 0000000..364ae4a --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -0,0 +1,58 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.privacycentralapp + +import android.app.Application +import android.content.Context +import android.os.Process +import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory +import foundation.e.privacycentralapp.features.location.LocationApiDelegate +import foundation.e.privacymodules.location.FakeLocation +import foundation.e.privacymodules.location.IFakeLocation +import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacymodules.permissions.data.ApplicationDescription + +/** + * Simple container to hold application wide dependencies. + * + * TODO: Test if this implementation is leaky. + */ +class DependencyContainer constructor(val app: Application) { + + val context: Context by lazy { app.applicationContext } + + private val fakeLocationModule: IFakeLocation by lazy { FakeLocation(app.applicationContext) } + private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) } + + private val appDesc by lazy { + ApplicationDescription( + packageName = context.packageName, + uid = Process.myUid(), + label = context.resources.getString(R.string.app_name), + icon = null + ) + } + + private val locationApi by lazy { + LocationApiDelegate(fakeLocationModule, permissionsModule, appDesc) + } + + val fakeLocationViewModelFactory by lazy { + FakeLocationViewModelFactory(locationApi) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt index 87be346..9372a66 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt @@ -19,4 +19,8 @@ package foundation.e.privacycentralapp import android.app.Application -class PrivacyCentralApplication : Application() +class PrivacyCentralApplication : Application() { + + // Initialize the dependency container. + val dependencyContainer: DependencyContainer by lazy { DependencyContainer(this) } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt b/app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt new file mode 100644 index 0000000..4c7f436 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt @@ -0,0 +1,23 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.privacycentralapp.common + +// Definition of a Factory interface with a function to create objects of a type +interface Factory<T> { + fun create(): T +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/extensions/ViewModelExtension.kt b/app/src/main/java/foundation/e/privacycentralapp/extensions/ViewModelExtension.kt new file mode 100644 index 0000000..d256219 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/extensions/ViewModelExtension.kt @@ -0,0 +1,28 @@ +/* + * 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.privacycentralapp.extensions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +inline fun <VM : ViewModel> viewModelProviderFactoryOf( + crossinline f: () -> VM +): ViewModelProvider.Factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun <T : ViewModel?> create(modelClass: Class<T>): T = f() as T +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt index fe9359a..7e45049 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt @@ -36,7 +36,8 @@ class FakeLocationFeature( coroutineScope: CoroutineScope, reducer: Reducer<State, Effect>, actor: Actor<State, Action, Effect>, - singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent> + singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent>, + private val locationApi: LocationApiDelegate ) : BaseFeature<FakeLocationFeature.State, FakeLocationFeature.Action, FakeLocationFeature.Effect, FakeLocationFeature.SingleEvent>( initialState, actor, @@ -54,11 +55,12 @@ class FakeLocationFeature( data class ErrorEvent(val error: String) : SingleEvent() } - sealed class Action() { + sealed class Action { + + // Action which is triggered everytime the location is updated. data class UpdateLocationAction(val latLng: LatLng) : Action() - data class UseRealLocationAction(val locationApiDelegate: LocationApiDelegate) : Action() + object UseRealLocationAction : Action() data class UseRandomLocationAction( - val locationApiDelegate: LocationApiDelegate, val cities: Array<String> ) : Action() { override fun equals(other: Any?): Boolean { @@ -77,9 +79,8 @@ class FakeLocationFeature( } } - data class UseSpecificLocationAction(val locationApiDelegate: LocationApiDelegate) : Action() - data class SetFakeLocationAction( - val locationApiDelegate: LocationApiDelegate, + object UseSpecificLocationAction : Action() + data class SetCustomFakeLocationAction( val latitude: Double, val longitude: Double ) : Action() @@ -103,7 +104,8 @@ class FakeLocationFeature( 0.0 ) ), - coroutineScope: CoroutineScope + coroutineScope: CoroutineScope, + locationApi: LocationApiDelegate ) = FakeLocationFeature( initialState, coroutineScope, reducer = { state, effect -> @@ -140,13 +142,13 @@ class FakeLocationFeature( action.latLng.longitude ) ) - is Action.SetFakeLocationAction -> { + is Action.SetCustomFakeLocationAction -> { val location = Location( LocationMode.CUSTOM_LOCATION, action.latitude, action.longitude ) - action.locationApiDelegate.setFakeLocation(action.latitude, action.longitude) + locationApi.setFakeLocation(action.latitude, action.longitude) val success = DummyDataSource.setLocationMode( LocationMode.CUSTOM_LOCATION, location @@ -163,7 +165,7 @@ class FakeLocationFeature( } is Action.UseRandomLocationAction -> { val randomCity = CityDataSource.getRandomCity(action.cities) - action.locationApiDelegate.setFakeLocation(randomCity.latitude, randomCity.longitude) + locationApi.setFakeLocation(randomCity.latitude, randomCity.longitude) val success = DummyDataSource.setLocationMode( LocationMode.RANDOM_LOCATION, randomCity.toRandomLocation() @@ -179,7 +181,7 @@ class FakeLocationFeature( } } is Action.UseRealLocationAction -> { - action.locationApiDelegate.startRealLocation() + locationApi.startRealLocation() val success = DummyDataSource.setLocationMode(LocationMode.REAL_LOCATION) if (success) { flowOf( @@ -204,7 +206,8 @@ class FakeLocationFeature( is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) else -> null } - } + }, + locationApi = locationApi ) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt index c11a7ea..d569e2f 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt @@ -21,7 +21,6 @@ import android.annotation.SuppressLint import android.content.Context import android.os.Bundle import android.os.Looper -import android.os.Process import android.text.Editable import android.util.Log import android.view.Gravity @@ -56,19 +55,17 @@ import com.mapbox.mapboxsdk.maps.MapboxMap import com.mapbox.mapboxsdk.maps.OnMapReadyCallback import com.mapbox.mapboxsdk.maps.Style import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.DependencyContainer +import foundation.e.privacycentralapp.PrivacyCentralApplication import foundation.e.privacycentralapp.R import foundation.e.privacycentralapp.dummy.LocationMode -import foundation.e.privacymodules.location.FakeLocation -import foundation.e.privacymodules.location.IFakeLocation -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule -import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch -import java.lang.Exception class FakeLocationFragment : Fragment(R.layout.fragment_fake_location), @@ -77,7 +74,14 @@ class FakeLocationFragment : private var isCameraMoved: Boolean = false private lateinit var permissionsManager: PermissionsManager - private val viewModel: FakeLocationViewModel by viewModels() + + private val dependencyContainer: DependencyContainer by lazy { + (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer + } + + private val viewModel: FakeLocationViewModel by viewModels { + viewModelProviderFactoryOf { dependencyContainer.fakeLocationViewModelFactory.create() } + } private lateinit var mapView: FakeLocationMapView private lateinit var mapboxMap: MapboxMap @@ -96,9 +100,8 @@ class FakeLocationFragment : object : LocationEngineCallback<LocationEngineResult> { override fun onSuccess(result: LocationEngineResult?) { result?.lastLocation?.let { - Log.d(TAG, "Last location: ${it.latitude}, ${it.longitude}") mapboxMap.locationComponent.forceLocationUpdate( - LocationUpdate.Builder().location(it).animationDuration(200) + LocationUpdate.Builder().location(it).animationDuration(100) .build() ) if (!isCameraMoved) { @@ -111,7 +114,8 @@ class FakeLocationFragment : ) ) } - // Only update location when location mode is set to real location + // Only update location when location mode is set to real location or random location. + // It basically triggers a UI update. if (viewModel.fakeLocationFeature.state.value.location.mode != LocationMode.CUSTOM_LOCATION) { viewModel.submitAction( FakeLocationFeature.Action.UpdateLocationAction( @@ -138,22 +142,6 @@ class FakeLocationFragment : private const val DROPPED_MARKER_LAYER_ID = "DROPPED_MARKER_LAYER_ID" } - private val fakeLocationModule: IFakeLocation by lazy { FakeLocation(this.requireContext()) } - private val permissionsModule by lazy { PermissionsPrivacyModule(this.requireContext()) } - - private val appDesc by lazy { - ApplicationDescription( - packageName = this.requireContext().packageName, - uid = Process.myUid(), - label = getString(R.string.app_name), - icon = null - ) - } - - private val locationApiDelegate by lazy { - LocationApiDelegate(fakeLocationModule, permissionsModule, appDesc) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launchWhenStarted { @@ -185,7 +173,6 @@ class FakeLocationFragment : } } } - locationApiDelegate.startRealLocation() } override fun onAttach(context: Context) { @@ -222,8 +209,10 @@ class FakeLocationFragment : mapboxMap.addOnCameraMoveStartedListener { // Show marker when user starts to move across the map. - if (mapView.isEnabled) { - hoveringMarker?.visibility = View.VISIBLE + hoveringMarker?.visibility = if (mapView.isEnabled) { + View.VISIBLE + } else { + View.GONE } isCameraMoved = true } @@ -273,7 +262,22 @@ class FakeLocationFragment : inputJob = lifecycleScope.launch { delay(DEBOUNCE_PERIOD) ensureActive() - Log.d("FakeLocation", "Call save location here") + try { + val lat = latEditText.text.toString().toDouble() + val long = longEditText.text.toString().toDouble() + viewModel.submitAction( + FakeLocationFeature.Action.SetCustomFakeLocationAction( + lat, + long + ) + ) + } catch (e: NumberFormatException) { + Toast.makeText( + requireContext(), + getString(R.string.please_enter_valid_lat_long), + Toast.LENGTH_SHORT + ).show() + } } } } @@ -288,25 +292,21 @@ class FakeLocationFragment : R.id.radio_use_real_location -> if (checked) { viewModel.submitAction( - FakeLocationFeature.Action.UseRealLocationAction( - locationApiDelegate - ) + FakeLocationFeature.Action.UseRealLocationAction ) } R.id.radio_use_random_location -> if (checked) { viewModel.submitAction( FakeLocationFeature.Action.UseRandomLocationAction( - locationApiDelegate, resources.getStringArray(R.array.cities) + resources.getStringArray(R.array.cities) ) ) } R.id.radio_use_specific_location -> if (checked) { viewModel.submitAction( - FakeLocationFeature.Action.UseSpecificLocationAction( - locationApiDelegate - ) + FakeLocationFeature.Action.UseSpecificLocationAction ) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt index eb55fba..b73c228 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt @@ -19,17 +19,18 @@ package foundation.e.privacycentralapp.features.location import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import foundation.e.privacycentralapp.common.Factory import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch -class FakeLocationViewModel : ViewModel() { +class FakeLocationViewModel(private val locationApi: LocationApiDelegate) : ViewModel() { private val _actions = MutableSharedFlow<FakeLocationFeature.Action>() val actions = _actions.asSharedFlow() val fakeLocationFeature: FakeLocationFeature by lazy { - FakeLocationFeature.create(coroutineScope = viewModelScope) + FakeLocationFeature.create(coroutineScope = viewModelScope, locationApi = locationApi) } fun submitAction(action: FakeLocationFeature.Action) { @@ -38,3 +39,9 @@ class FakeLocationViewModel : ViewModel() { } } } + +class FakeLocationViewModelFactory(private val locationApi: LocationApiDelegate) : Factory<FakeLocationViewModel> { + override fun create(): FakeLocationViewModel { + return FakeLocationViewModel((locationApi)) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt index dd2e5c1..88fef3e 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt @@ -23,7 +23,6 @@ import foundation.e.privacymodules.location.IFakeLocation import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.AppOpModes import foundation.e.privacymodules.permissions.data.ApplicationDescription -import java.lang.Exception class LocationApiDelegate( private val fakeLocationModule: IFakeLocation, @@ -63,6 +62,7 @@ class LocationApiDelegate( Log.e(TAG, "Can't stop FakeLocation", e) } } + fun startRealLocation() { stopFakeLocation() try { |