summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAmit Kumar <amitkma@e.email>2021-05-12 17:55:06 +0530
committerAmit Kumar <amitkma@e.email>2021-05-12 17:55:06 +0530
commitf27cbbb44baf1cd437c74285155af2b5cbfb7953 (patch)
treebb0681345aec45bd3c63659caf917a43d6dfe8ab /app
parent574233624a9261a800fd5ddd234208f2c6994766 (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')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt58
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt6
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/Factory.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/extensions/ViewModelExtension.kt28
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt29
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt74
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt11
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/LocationApiDelegate.kt2
-rw-r--r--app/src/main/res/values/strings.xml1
9 files changed, 178 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 {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c0b8348..8fc7c92 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -37,4 +37,5 @@
<string name="fake_location_mode">Fake location mode</string>
<string name="user_location_permission_explanation">This app needs location permissions in order to show its functionality.</string>
<string name="user_location_permission_not_granted">You didn\'t grant location permission</string>
+ <string name="please_enter_valid_lat_long">Please enter valid latitude and longitude value</string>
</resources> \ No newline at end of file