diff options
Diffstat (limited to 'app')
49 files changed, 3156 insertions, 60 deletions
diff --git a/app/build.gradle b/app/build.gradle index 35e1c73..15fa88c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,14 +4,14 @@ plugins { } android { - compileSdkVersion 29 + compileSdkVersion buildConfig.compileSdk defaultConfig { applicationId "foundation.e.privacycentralapp" - minSdkVersion 24 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" + minSdkVersion buildConfig.minSdk + targetSdkVersion buildConfig.targetSdk + versionCode buildConfig.version.code + versionName buildConfig.version.fullName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -26,17 +26,19 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - kotlinOptions { - jvmTarget = '1.8' - } } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32" - implementation 'androidx.core:core-ktx:1.3.2' + implementation project(":flow-mvi") + implementation Libs.Kotlin.stdlib + implementation Libs.AndroidX.coreKtx + implementation Libs.AndroidX.Fragment.fragmentKtx implementation 'androidx.appcompat:appcompat:1.2.0' + implementation Libs.AndroidX.Lifecycle.runtime + implementation Libs.AndroidX.Lifecycle.viewmodel + implementation 'com.google.android.material:material:1.3.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a28e77e..2c3b055 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,13 +3,14 @@ package="foundation.e.privacycentralapp"> <application + android:name=".PrivacyCentralApplication" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PrivacyCentralApp"> - <activity android:name=".MainActivity"> + <activity android:name=".main.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> diff --git a/app/src/main/java/foundation/e/privacycentralapp/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt index 3dd2145..87be346 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/MainActivity.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt @@ -17,12 +17,6 @@ package foundation.e.privacycentralapp -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import android.app.Application -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} +class PrivacyCentralApplication : Application() diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt b/app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt new file mode 100644 index 0000000..bbc108b --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/common/RightRadioButton.kt @@ -0,0 +1,43 @@ +/* + * 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 + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.widget.RadioButton + +/** + * A custom [RadioButton] which displays the radio drawable on the right side. + */ +@SuppressLint("AppCompatCustomView") +class RightRadioButton : RadioButton { + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + // Returns layout direction as right-to-left to draw the compound button on right side. + override fun getLayoutDirection(): Int { + return LAYOUT_DIRECTION_RTL + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt new file mode 100644 index 0000000..aef994b --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/DummyDataSource.kt @@ -0,0 +1,227 @@ +/* + * 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.dummy + +import foundation.e.privacycentralapp.R +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlin.random.Random + +// ======================================================// +// +// ================ ==== ==== =============== +// ================ ====== ====== ================ +// ==== ======== ======== ==== ==== +// ==== ========= ========= ==== ==== +// ==== ==================== ================ +// ==== ==== ======== ==== =============== +// ==== ==== ==== ==== ==== +// ================ ==== == ==== ==== +// ================ ==== ==== ==== +// +// ======================================================// + +/** + * This whole file acts as a dummy source. All data classes and method implementations + * are not proper ones and are subject to change anytime. + */ + +/** + * Dummmy permission data class. + */ +data class Permission( + val id: Int, + val name: String, + val iconId: Int, + val packagesRequested: Set<String> = emptySet(), + val packagesAllowed: Set<String> = emptySet() +) + +enum class LocationMode { + REAL_LOCATION, RANDOM_LOCATION, CUSTOM_LOCATION +} + +enum class InternetPrivacyMode { + REAL_IP, HIDE_IP +} + +data class Location(val mode: LocationMode, val latitude: Double, val longitude: Double) + +object DummyDataSource { + + const val trackersCount = 77 + private val _activeTrackersCount = MutableStateFlow(10) + val activeTrackersCount = _activeTrackersCount.asStateFlow() + + private val _location = MutableStateFlow(Location(LocationMode.REAL_LOCATION, 0.0, 0.0)) + val location = _location.asStateFlow() + + private val _internetActivityMode = MutableStateFlow(InternetPrivacyMode.REAL_IP) + val internetActivityMode = _internetActivityMode.asStateFlow() + + /** + * Declare dummy permissions with following ids + * + * [0] -> Body sensor + * [1] -> Calendar + * [2] -> Call Logs + * [3] -> Location + */ + val permissions = arrayOf("Body Sensor", "Calendar", "Call Logs", "Location") + + private val permissionIcons = arrayOf( + R.drawable.ic_body_monitor, + R.drawable.ic_calendar, + R.drawable.ic_call, + R.drawable.ic_location + ) + + val packages = arrayOf( + "facebook", + "uber", + "instagram", + "openstreetmap", + "truecaller", + "netflix", + "firefox", + "pubg", + "amazon", + "calendar", + "zohomail", + "privacycentral" + ) + + val _populatedPermissions = MutableStateFlow(fetchPermissions()) + val populatedPermission = _populatedPermissions.asStateFlow() + + private val _appsUsingLocationPerm = + MutableStateFlow(_populatedPermissions.value[3].packagesAllowed) + val appsUsingLocationPerm = _appsUsingLocationPerm.asStateFlow() + + private fun fetchPermissions(): List<Permission> { + val result = mutableListOf<Permission>() + permissions.forEachIndexed { index, permission -> + when (index) { + 0 -> result.add(Permission(index, permission, permissionIcons[index])) + 1 -> { + val randomPackages = getRandomItems(packages, 8) + val grantedPackages = getRandomItems(randomPackages, 3) + result.add( + Permission( + index, + permission, + permissionIcons[index], + randomPackages, + grantedPackages + ) + ) + } + 2 -> { + val randomPackages = getRandomItems(packages, 10) + val grantedPackages = getRandomItems(randomPackages, 9) + result.add( + Permission( + index, + permission, + permissionIcons[index], + randomPackages, + grantedPackages + ) + ) + } + 3 -> { + val randomPackages = getRandomItems(packages, 5) + val grantedPackages = getRandomItems(randomPackages, 3) + result.add( + Permission( + index, + permission, + permissionIcons[index], + randomPackages, + grantedPackages + ) + ) + } + } + } + return result + } + + private fun <T> getRandomItems(data: Array<T>, limit: Int): Set<T> = + getRandomItems(data.toSet(), limit) + + private fun <T> getRandomItems(data: Set<T>, limit: Int): Set<T> { + val randomItems = mutableSetOf<T>() + val localData = data.toMutableList() + repeat(limit) { + val generated = localData.random() + randomItems.add(generated) + localData.remove(generated) + } + return randomItems + } + + fun getPermission(permissionId: Int): Permission = populatedPermission.value[permissionId] + + fun getLocationPermissionApps(): Permission = getPermission(3) + + fun setLocationMode(locationMode: LocationMode, location: Location? = null): Boolean { + when (locationMode) { + LocationMode.REAL_LOCATION -> + _location.value = + Location(LocationMode.REAL_LOCATION, 24.39, 71.80) + LocationMode.RANDOM_LOCATION -> _location.value = randomLocation() + LocationMode.CUSTOM_LOCATION -> { + requireNotNull(location) { "Custom location should be null" } + _location.value = location.copy(mode = LocationMode.CUSTOM_LOCATION) + } + } + return true + } + + private fun randomLocation(): Location = Location( + LocationMode.RANDOM_LOCATION, + Random.nextDouble(-90.0, 90.0), + Random.nextDouble(-180.0, 180.0) + ) + + fun setInternetPrivacyMode(mode: InternetPrivacyMode): Boolean { + _internetActivityMode.value = mode + return true + } + + fun togglePermission(permissionId: Int, packageName: String, grant: Boolean) { + val allPermissions = _populatedPermissions.value.toMutableList() + val permission: Permission = allPermissions[permissionId].let { permission -> + + val packagesAllowed = permission.packagesAllowed.toMutableSet() + + if (grant) packagesAllowed.add(packageName) + else packagesAllowed.remove(packageName) + + permission.copy(packagesAllowed = packagesAllowed) + } + allPermissions[permissionId] = permission + _populatedPermissions.value = allPermissions + + // Update when permission is toggled for Location + if (permissionId == 3) { + _appsUsingLocationPerm.value = _populatedPermissions.value[permissionId].packagesAllowed + } + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt new file mode 100644 index 0000000..133ad84 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/Extensions.kt @@ -0,0 +1,32 @@ +/* + * 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.dummy + +import android.content.Context +import foundation.e.privacycentralapp.R + +fun LocationMode.mapToString(context: Context): String = when (this) { + LocationMode.REAL_LOCATION -> context.getString(R.string.real_location_mode) + LocationMode.RANDOM_LOCATION -> context.getString(R.string.random_location_mode) + LocationMode.CUSTOM_LOCATION -> context.getString(R.string.fake_location_mode) +} + +fun InternetPrivacyMode.mapToString(context: Context): String = when (this) { + InternetPrivacyMode.REAL_IP -> context.getString(R.string.i_am_exposing) + InternetPrivacyMode.HIDE_IP -> context.getString(R.string.i_am_anonymous) +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt new file mode 100644 index 0000000..dd4f0ff --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt @@ -0,0 +1,205 @@ +/* + * 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.features.dashboard + +import android.util.Log +import foundation.e.flowmvi.Actor +import foundation.e.flowmvi.Reducer +import foundation.e.flowmvi.SingleEventProducer +import foundation.e.flowmvi.feature.BaseFeature +import foundation.e.privacycentralapp.dummy.DummyDataSource +import foundation.e.privacycentralapp.dummy.InternetPrivacyMode +import foundation.e.privacycentralapp.dummy.LocationMode +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +// Define a state machine for Dashboard Feature +class DashboardFeature( + initialState: State, + coroutineScope: CoroutineScope, + reducer: Reducer<State, Effect>, + actor: Actor<State, Action, Effect>, + singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent> +) : BaseFeature<DashboardFeature.State, + DashboardFeature.Action, + DashboardFeature.Effect, + DashboardFeature.SingleEvent>( + initialState, actor, reducer, coroutineScope, { message -> Log.d("DashboardFeature", message) }, + singleEventProducer +) { + sealed class State { + object InitialState : State() + object LoadingDashboardState : State() + data class DashboardState( + val trackersCount: Int, + val activeTrackersCount: Int, + val totalApps: Int, + val permissionCount: Int, + val appsUsingLocationPerm: Int, + val locationMode: LocationMode, + val internetPrivacyMode: InternetPrivacyMode + ) : State() + + object QuickProtectionState : State() + } + + sealed class SingleEvent { + object NavigateToQuickProtectionSingleEvent : SingleEvent() + object NavigateToTrackersSingleEvent : SingleEvent() + object NavigateToInternetActivityPrivacySingleEvent : SingleEvent() + object NavigateToLocationSingleEvent : SingleEvent() + object NavigateToPermissionsSingleEvent : SingleEvent() + } + + sealed class Action { + object ShowQuickPrivacyProtectionInfoAction : Action() + object ObserveDashboardAction : Action() + object ShowDashboardAction : Action() + object ShowFakeMyLocationAction : Action() + object ShowInternetActivityPrivacyAction : Action() + object ShowAppsPermissions : Action() + } + + sealed class Effect { + object OpenQuickPrivacyProtectionEffect : Effect() + data class OpenDashboardEffect( + val trackersCount: Int, + val activeTrackersCount: Int, + val totalApps: Int, + val permissionCount: Int, + val appsUsingLocationPerm: Int, + val locationMode: LocationMode, + val internetPrivacyMode: InternetPrivacyMode + ) : Effect() + + object LoadingDashboardEffect : Effect() + data class UpdateActiveTrackersCountEffect(val count: Int) : Effect() + data class UpdateLocationModeEffect(val mode: LocationMode) : Effect() + data class UpdateInternetActivityModeEffect(val mode: InternetPrivacyMode) : Effect() + data class UpdateAppsUsingLocationPermEffect(val apps: Int) : Effect() + object OpenFakeMyLocationEffect : Effect() + object OpenInternetActivityPrivacyEffect : Effect() + object OpenAppsPermissionsEffect : Effect() + } + + companion object { + fun create(initialState: State, coroutineScope: CoroutineScope): DashboardFeature = + DashboardFeature( + initialState, + coroutineScope, + reducer = { state, effect -> + when (effect) { + Effect.OpenQuickPrivacyProtectionEffect -> State.QuickProtectionState + is Effect.OpenDashboardEffect -> State.DashboardState( + effect.trackersCount, + effect.activeTrackersCount, + effect.totalApps, + effect.permissionCount, + effect.appsUsingLocationPerm, + effect.locationMode, + effect.internetPrivacyMode + ) + Effect.LoadingDashboardEffect -> { + if (state is State.InitialState) { + State.LoadingDashboardState + } else state + } + is Effect.UpdateActiveTrackersCountEffect -> { + if (state is State.DashboardState) { + state.copy(activeTrackersCount = effect.count) + } else state + } + is Effect.UpdateInternetActivityModeEffect -> { + if (state is State.DashboardState) { + state.copy(internetPrivacyMode = effect.mode) + } else state + } + is Effect.UpdateLocationModeEffect -> { + if (state is State.DashboardState) { + state.copy(locationMode = effect.mode) + } else state + } + is Effect.UpdateAppsUsingLocationPermEffect -> if (state is State.DashboardState) { + state.copy(appsUsingLocationPerm = effect.apps) + } else state + + Effect.OpenFakeMyLocationEffect -> state + Effect.OpenAppsPermissionsEffect -> state + Effect.OpenInternetActivityPrivacyEffect -> state + } + }, + actor = { _: State, action: Action -> + Log.d("Feature", "action: $action") + when (action) { + Action.ObserveDashboardAction -> merge( + DummyDataSource.activeTrackersCount.map { + Effect.UpdateActiveTrackersCountEffect(it) + }, + DummyDataSource.appsUsingLocationPerm.map { + Effect.UpdateAppsUsingLocationPermEffect(it.size) + }, + DummyDataSource.location.map { + Effect.UpdateLocationModeEffect(it.mode) + }, + DummyDataSource.internetActivityMode.map { + Effect.UpdateInternetActivityModeEffect(it) + } + ) + Action.ShowQuickPrivacyProtectionInfoAction -> flowOf( + Effect.OpenQuickPrivacyProtectionEffect + ) + Action.ShowDashboardAction -> flow { + emit(Effect.LoadingDashboardEffect) + kotlinx.coroutines.delay(2000) + emit( + Effect.OpenDashboardEffect( + DummyDataSource.trackersCount, + DummyDataSource.activeTrackersCount.value, + DummyDataSource.packages.size, + DummyDataSource.permissions.size, + DummyDataSource.appsUsingLocationPerm.value.size, + DummyDataSource.location.value.mode, + DummyDataSource.internetActivityMode.value + ) + ) + } + Action.ShowFakeMyLocationAction -> flowOf(Effect.OpenFakeMyLocationEffect) + Action.ShowAppsPermissions -> flowOf(Effect.OpenAppsPermissionsEffect) + Action.ShowInternetActivityPrivacyAction -> flowOf( + Effect.OpenInternetActivityPrivacyEffect + ) + } + }, + singleEventProducer = { state, _, effect -> + Log.d("DashboardFeature", "$state, $effect") + if (state is State.DashboardState && effect is Effect.OpenFakeMyLocationEffect) + SingleEvent.NavigateToLocationSingleEvent + else if (state is State.QuickProtectionState && effect is Effect.OpenQuickPrivacyProtectionEffect) + SingleEvent.NavigateToQuickProtectionSingleEvent + else if (state is State.DashboardState && effect is Effect.OpenInternetActivityPrivacyEffect) + SingleEvent.NavigateToInternetActivityPrivacySingleEvent + else if (state is State.DashboardState && effect is Effect.OpenAppsPermissionsEffect) + SingleEvent.NavigateToPermissionsSingleEvent + else null + } + ) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt new file mode 100644 index 0000000..f0a7397 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt @@ -0,0 +1,199 @@ +/* + * 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.features.dashboard + +import android.graphics.Color +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.view.View +import android.widget.ProgressBar +import android.widget.RelativeLayout +import android.widget.TextView +import android.widget.Toolbar +import androidx.core.widget.NestedScrollView +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.add +import androidx.fragment.app.commit +import androidx.lifecycle.lifecycleScope +import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.dummy.mapToString +import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyFragment +import foundation.e.privacycentralapp.features.location.FakeLocationFragment +import foundation.e.privacycentralapp.features.permissions.PermissionsFragment +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect + +class DashboardFragment : + Fragment(R.layout.fragment_dashboard), + MVIView<DashboardFeature.State, DashboardFeature.Action> { + + private val viewModel: DashboardViewModel by activityViewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launchWhenStarted { + viewModel.dashboardFeature.takeView(this, this@DashboardFragment) + } + lifecycleScope.launchWhenStarted { + viewModel.dashboardFeature.singleEvents.collect { event -> + when (event) { + is DashboardFeature.SingleEvent.NavigateToLocationSingleEvent -> { + requireActivity().supportFragmentManager.commit { + add<FakeLocationFragment>(R.id.container) + setReorderingAllowed(true) + addToBackStack("dashboard") + } + } + is DashboardFeature.SingleEvent.NavigateToQuickProtectionSingleEvent -> { + requireActivity().supportFragmentManager.commit { + add<QuickProtectionFragment>(R.id.container) + setReorderingAllowed(true) + addToBackStack("dashboard") + } + } + is DashboardFeature.SingleEvent.NavigateToInternetActivityPrivacySingleEvent -> { + requireActivity().supportFragmentManager.commit { + add<InternetPrivacyFragment>(R.id.container) + setReorderingAllowed(true) + addToBackStack("dashboard") + } + } + is DashboardFeature.SingleEvent.NavigateToPermissionsSingleEvent -> { + requireActivity().supportFragmentManager.commit { + add<PermissionsFragment>(R.id.container) + setReorderingAllowed(true) + addToBackStack("dashboard") + } + } + DashboardFeature.SingleEvent.NavigateToTrackersSingleEvent -> { + } + } + } + } + lifecycleScope.launchWhenStarted { + viewModel.submitAction(DashboardFeature.Action.ShowDashboardAction) + viewModel.submitAction(DashboardFeature.Action.ObserveDashboardAction) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val toolbar = view.findViewById<Toolbar>(R.id.toolbar) + setupToolbar(toolbar) + addClickToMore(view.findViewById(R.id.personal_leakag_info)) + view.let { + it.findViewById<TextView>(R.id.tap_to_enable_quick_protection).setOnClickListener { + viewModel.submitAction(DashboardFeature.Action.ShowQuickPrivacyProtectionInfoAction) + } + it.findViewById<RelativeLayout>(R.id.my_location).setOnClickListener { + viewModel.submitAction(DashboardFeature.Action.ShowFakeMyLocationAction) + } + it.findViewById<RelativeLayout>(R.id.internet_activity_privacy).setOnClickListener { + viewModel.submitAction(DashboardFeature.Action.ShowInternetActivityPrivacyAction) + } + it.findViewById<RelativeLayout>(R.id.apps_permissions).setOnClickListener { + viewModel.submitAction(DashboardFeature.Action.ShowAppsPermissions) + } + } + } + + private fun setupToolbar(toolbar: Toolbar) { + val activity = requireActivity() + activity.setActionBar(toolbar) + activity.title = "My Privacy Dashboard" + } + + private fun addClickToMore(textView: TextView) { + val clickToMore = SpannableString("Click to learn more") + clickToMore.setSpan( + ForegroundColorSpan(Color.parseColor("#007fff")), + 0, + clickToMore.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + textView.append(clickToMore) + } + + override fun render(state: DashboardFeature.State) { + when (state) { + is DashboardFeature.State.InitialState, is DashboardFeature.State.LoadingDashboardState -> { + view?.let { + it.findViewById<ProgressBar>(R.id.loadingSpinner).visibility = View.VISIBLE + it.findViewById<NestedScrollView>(R.id.scrollContainer).visibility = View.GONE + } + } + is DashboardFeature.State.DashboardState -> { + view?.let { view -> + view.findViewById<ProgressBar>(R.id.loadingSpinner).visibility = View.GONE + view.findViewById<NestedScrollView>(R.id.scrollContainer).visibility = + View.VISIBLE + view.findViewById<TextView>(R.id.am_i_tracked_subtitle).text = getString( + R.string.am_i_tracked_subtitle, + state.trackersCount, + state.activeTrackersCount + ) + view.findViewById<TextView>(R.id.apps_permissions_subtitle).text = getString( + R.string.apps_permissions_subtitle, + state.totalApps, + state.permissionCount + ) + view.findViewById<TextView>(R.id.my_location_subtitle).let { textView -> + textView.text = getString( + R.string.my_location_subtitle, + state.appsUsingLocationPerm, + ) + textView.append( + SpannableString(state.locationMode.mapToString(requireContext())) + .also { + it.setSpan( + ForegroundColorSpan(Color.parseColor("#007fff")), + 0, + it.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + ) + } + view.findViewById<TextView>(R.id.internet_activity_privacy_subtitle) + .let { textView -> + textView.text = getString(R.string.internet_activity_privacy_subtitle) + textView.append( + SpannableString(state.internetPrivacyMode.mapToString(requireContext())) + .also { + it.setSpan( + ForegroundColorSpan(Color.parseColor("#007fff")), + 0, + it.length, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + ) + } + } + } + DashboardFeature.State.QuickProtectionState -> { + } + } + } + + override fun actions(): Flow<DashboardFeature.Action> = viewModel.actions +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt new file mode 100644 index 0000000..1a3b3df --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardViewModel.kt @@ -0,0 +1,40 @@ +/* + * 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.features.dashboard + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +class DashboardViewModel : ViewModel() { + + private val _actions = MutableSharedFlow<DashboardFeature.Action>() + val actions = _actions.asSharedFlow() + + val dashboardFeature: DashboardFeature by lazy { + DashboardFeature.create(DashboardFeature.State.InitialState, coroutineScope = viewModelScope) + } + + fun submitAction(action: DashboardFeature.Action) { + viewModelScope.launch { + _actions.emit(action) + } + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt new file mode 100644 index 0000000..c120b49 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/QuickProtectionFragment.kt @@ -0,0 +1,53 @@ +/* + * 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.features.dashboard + +import android.content.Context +import android.os.Bundle +import android.view.View +import android.widget.Toolbar +import androidx.activity.addCallback +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import foundation.e.privacycentralapp.R + +class QuickProtectionFragment : Fragment(R.layout.fragment_quick_protection) { + + private val viewModel: DashboardViewModel by activityViewModels() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val toolbar = view.findViewById<Toolbar>(R.id.toolbar) + setupToolbar(toolbar) + } + + override fun onAttach(context: Context) { + super.onAttach(context) + requireActivity().onBackPressedDispatcher.addCallback(this, true) { + viewModel.submitAction(DashboardFeature.Action.ShowDashboardAction) + this.isEnabled = false + requireActivity().onBackPressed() + } + } + + private fun setupToolbar(toolbar: Toolbar) { + val activity = requireActivity() + activity.setActionBar(toolbar) + activity.title = "Quick protection" + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt new file mode 100644 index 0000000..b34024e --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFeature.kt @@ -0,0 +1,110 @@ +/* + * 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.features.internetprivacy + +import android.util.Log +import foundation.e.flowmvi.Actor +import foundation.e.flowmvi.Reducer +import foundation.e.flowmvi.SingleEventProducer +import foundation.e.flowmvi.feature.BaseFeature +import foundation.e.privacycentralapp.dummy.DummyDataSource +import foundation.e.privacycentralapp.dummy.InternetPrivacyMode +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf + +// Define a state machine for Internet privacy feature +class InternetPrivacyFeature( + initialState: State, + coroutineScope: CoroutineScope, + reducer: Reducer<State, Effect>, + actor: Actor<State, Action, Effect>, + singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent> +) : BaseFeature<InternetPrivacyFeature.State, InternetPrivacyFeature.Action, InternetPrivacyFeature.Effect, InternetPrivacyFeature.SingleEvent>( + initialState, + actor, + reducer, + coroutineScope, + { message -> Log.d("InternetPrivacyFeature", message) }, + singleEventProducer +) { + data class State(val mode: InternetPrivacyMode) + + sealed class SingleEvent { + object RealIPSelectedEvent : SingleEvent() + object HiddenIPSelectedEvent : SingleEvent() + data class ErrorEvent(val error: String) : SingleEvent() + } + + sealed class Action { + object LoadInternetModeAction : Action() + object UseRealIPAction : Action() + object UseHiddenIPAction : Action() + } + + sealed class Effect { + data class ModeUpdatedEffect(val mode: InternetPrivacyMode) : Effect() + data class ErrorEffect(val message: String) : Effect() + } + + companion object { + fun create( + initialState: State = State(InternetPrivacyMode.REAL_IP), + coroutineScope: CoroutineScope + ) = InternetPrivacyFeature( + initialState, coroutineScope, + reducer = { state, effect -> + when (effect) { + is Effect.ModeUpdatedEffect -> state.copy(mode = effect.mode) + is Effect.ErrorEffect -> state + } + }, + actor = { _, action -> + when (action) { + Action.LoadInternetModeAction -> flowOf(Effect.ModeUpdatedEffect(DummyDataSource.internetActivityMode.value)) + Action.UseHiddenIPAction, Action.UseRealIPAction -> flow { + val success = + DummyDataSource.setInternetPrivacyMode(if (action is Action.UseHiddenIPAction) InternetPrivacyMode.HIDE_IP else InternetPrivacyMode.REAL_IP) + emit( + if (success) Effect.ModeUpdatedEffect(DummyDataSource.internetActivityMode.value) else Effect.ErrorEffect( + "Couldn't update internet mode" + ) + ) + } + } + }, + singleEventProducer = { _, action, effect -> + when (action) { + Action.UseRealIPAction, Action.UseHiddenIPAction -> when (effect) { + is Effect.ModeUpdatedEffect -> { + if (effect.mode == InternetPrivacyMode.REAL_IP) { + SingleEvent.RealIPSelectedEvent + } else { + SingleEvent.HiddenIPSelectedEvent + } + } + is Effect.ErrorEffect -> { + SingleEvent.ErrorEvent(effect.message) + } + } + else -> null + } + } + ) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt new file mode 100644 index 0000000..a8c1671 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyFragment.kt @@ -0,0 +1,116 @@ +/* + * 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.features.internetprivacy + +import android.os.Bundle +import android.view.View +import android.widget.RadioButton +import android.widget.Toast +import android.widget.Toolbar +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.dummy.InternetPrivacyMode +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect + +class InternetPrivacyFragment : + Fragment(R.layout.fragment_internet_activity_policy), + MVIView<InternetPrivacyFeature.State, InternetPrivacyFeature.Action> { + + private val viewModel: InternetPrivacyViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launchWhenStarted { + viewModel.internetPrivacyFeature.takeView(this, this@InternetPrivacyFragment) + } + lifecycleScope.launchWhenStarted { + viewModel.internetPrivacyFeature.singleEvents.collect { event -> + when (event) { + is InternetPrivacyFeature.SingleEvent.ErrorEvent -> displayToast(event.error) + InternetPrivacyFeature.SingleEvent.HiddenIPSelectedEvent -> displayToast("Your IP is hidden") + InternetPrivacyFeature.SingleEvent.RealIPSelectedEvent -> displayToast("Your IP is visible to internet") + } + } + } + lifecycleScope.launchWhenStarted { + viewModel.submitAction(InternetPrivacyFeature.Action.LoadInternetModeAction) + } + } + + private fun displayToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) + .show() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val toolbar = view.findViewById<Toolbar>(R.id.toolbar) + setupToolbar(toolbar) + bindClickListeners(view) + } + + private fun setupToolbar(toolbar: Toolbar) { + val activity = requireActivity() + activity.setActionBar(toolbar) + activity.title = "My Internet Activity Privacy" + } + + private fun bindClickListeners(fragmentView: View) { + fragmentView.let { + it.findViewById<RadioButton>(R.id.radio_use_real_ip) + .setOnClickListener { radioButton -> + toggleIP(radioButton) + } + it.findViewById<RadioButton>(R.id.radio_use_hidden_ip) + .setOnClickListener { radioButton -> + toggleIP(radioButton) + } + } + } + + private fun toggleIP(radioButton: View?) { + if (radioButton is RadioButton) { + val checked = radioButton.isChecked + when (radioButton.id) { + R.id.radio_use_real_ip -> + if (checked) { + viewModel.submitAction(InternetPrivacyFeature.Action.UseRealIPAction) + } + R.id.radio_use_hidden_ip -> + if (checked) { + viewModel.submitAction(InternetPrivacyFeature.Action.UseHiddenIPAction) + } + } + } + } + + override fun render(state: InternetPrivacyFeature.State) { + view?.let { + it.findViewById<RadioButton>(R.id.radio_use_hidden_ip).isChecked = + state.mode == InternetPrivacyMode.HIDE_IP + it.findViewById<RadioButton>(R.id.radio_use_real_ip).isChecked = + state.mode == InternetPrivacyMode.REAL_IP + } + } + + override fun actions(): Flow<InternetPrivacyFeature.Action> = viewModel.actions +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt new file mode 100644 index 0000000..b66b611 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/internetprivacy/InternetPrivacyViewModel.kt @@ -0,0 +1,40 @@ +/* + * 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.features.internetprivacy + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +class InternetPrivacyViewModel : ViewModel() { + + private val _actions = MutableSharedFlow<InternetPrivacyFeature.Action>() + val actions = _actions.asSharedFlow() + + val internetPrivacyFeature: InternetPrivacyFeature by lazy { + InternetPrivacyFeature.create(coroutineScope = viewModelScope) + } + + fun submitAction(action: InternetPrivacyFeature.Action) { + viewModelScope.launch { + _actions.emit(action) + } + } +} 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 new file mode 100644 index 0000000..6b00490 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFeature.kt @@ -0,0 +1,152 @@ +/* + * 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.features.location + +import android.util.Log +import foundation.e.flowmvi.Actor +import foundation.e.flowmvi.Reducer +import foundation.e.flowmvi.SingleEventProducer +import foundation.e.flowmvi.feature.BaseFeature +import foundation.e.privacycentralapp.dummy.DummyDataSource +import foundation.e.privacycentralapp.dummy.Location +import foundation.e.privacycentralapp.dummy.LocationMode +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +// Define a state machine for Fake location feature +class FakeLocationFeature( + initialState: State, + coroutineScope: CoroutineScope, + reducer: Reducer<State, Effect>, + actor: Actor<State, Action, Effect>, + singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent> +) : BaseFeature<FakeLocationFeature.State, FakeLocationFeature.Action, FakeLocationFeature.Effect, FakeLocationFeature.SingleEvent>( + initialState, actor, reducer, coroutineScope, { message -> Log.d("FakeLocationFeature", message) }, + singleEventProducer +) { + sealed class State { + object InitialState : State() + data class LocationState(val location: Location) : State() + } + + sealed class SingleEvent { + object RandomLocationSelectedEvent : SingleEvent() + object RealLocationSelectedEvent : SingleEvent() + object SpecificLocationSavedEvent : SingleEvent() + data class ErrorEvent(val error: String) : SingleEvent() + } + + sealed class Action { + object ObserveLocationAction : Action() + object UseRealLocationAction : Action() + object UseRandomLocationAction : Action() + object UseSpecificLocationAction : Action() + data class AddSpecificLocationAction(val latitude: Double, val longitude: Double) : Action() + } + + sealed class Effect { + data class LocationUpdatedEffect(val location: Location) : Effect() + object RealLocationSelectedEffect : Effect() + object RandomLocationSelectedEffect : Effect() + data class SpecificLocationSelectedEffect(val location: Location) : Effect() + object SpecificLocationSavedEffect : Effect() + data class ErrorEffect(val message: String) : Effect() + } + + companion object { + fun create( + initialState: State = State.InitialState, + coroutineScope: CoroutineScope + ) = FakeLocationFeature( + initialState, coroutineScope, + reducer = { state, effect -> + when (effect) { + Effect.RandomLocationSelectedEffect, + Effect.RealLocationSelectedEffect, is Effect.ErrorEffect, Effect.SpecificLocationSavedEffect -> state + is Effect.LocationUpdatedEffect -> State.LocationState(effect.location) + is Effect.SpecificLocationSelectedEffect -> State.LocationState(effect.location) + } + }, + actor = { _, action -> + when (action) { + is Action.ObserveLocationAction -> DummyDataSource.location.map { + Effect.LocationUpdatedEffect(it) + } + is Action.AddSpecificLocationAction -> { + val location = Location( + LocationMode.CUSTOM_LOCATION, + action.latitude, + action.longitude + ) + val success = DummyDataSource.setLocationMode( + LocationMode.CUSTOM_LOCATION, + location + ) + if (success) { + flowOf( + Effect.SpecificLocationSavedEffect + ) + } else { + flowOf( + Effect.ErrorEffect("Couldn't select location") + ) + } + } + Action.UseRandomLocationAction -> { + val success = DummyDataSource.setLocationMode(LocationMode.RANDOM_LOCATION) + if (success) { + flowOf( + Effect.RandomLocationSelectedEffect + ) + } else { + flowOf( + Effect.ErrorEffect("Couldn't select location") + ) + } + } + Action.UseRealLocationAction -> { + val success = DummyDataSource.setLocationMode(LocationMode.REAL_LOCATION) + if (success) { + flowOf( + Effect.RealLocationSelectedEffect + ) + } else { + flowOf( + Effect.ErrorEffect("Couldn't select location") + ) + } + } + Action.UseSpecificLocationAction -> { + val location = DummyDataSource.location.value + flowOf(Effect.SpecificLocationSelectedEffect(location.copy(mode = LocationMode.CUSTOM_LOCATION))) + } + } + }, + singleEventProducer = { _, _, effect -> + when (effect) { + Effect.RandomLocationSelectedEffect -> SingleEvent.RandomLocationSelectedEvent + Effect.SpecificLocationSavedEffect -> SingleEvent.SpecificLocationSavedEvent + Effect.RealLocationSelectedEffect -> SingleEvent.RealLocationSelectedEvent + is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) + else -> null + } + } + ) + } +} 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 new file mode 100644 index 0000000..5b58293 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt @@ -0,0 +1,178 @@ +/* + * 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.features.location + +import android.os.Bundle +import android.text.Editable +import android.util.Log +import android.view.View +import android.widget.Button +import android.widget.ImageView +import android.widget.RadioButton +import android.widget.Toast +import android.widget.Toolbar +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.google.android.material.textfield.TextInputLayout +import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.dummy.LocationMode +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect + +class FakeLocationFragment : + Fragment(R.layout.fragment_fake_location), + MVIView<FakeLocationFeature.State, FakeLocationFeature.Action> { + + private val viewModel: FakeLocationViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launchWhenStarted { + viewModel.fakeLocationFeature.takeView(this, this@FakeLocationFragment) + } + lifecycleScope.launchWhenStarted { + viewModel.fakeLocationFeature.singleEvents.collect { event -> + when (event) { + is FakeLocationFeature.SingleEvent.RandomLocationSelectedEvent -> displayToast("Random location selected") + is FakeLocationFeature.SingleEvent.SpecificLocationSavedEvent -> displayToast("Specific location selected") + is FakeLocationFeature.SingleEvent.ErrorEvent -> displayToast(event.error) + FakeLocationFeature.SingleEvent.RealLocationSelectedEvent -> displayToast("Real location selected") + } + } + } + lifecycleScope.launchWhenStarted { + viewModel.submitAction(FakeLocationFeature.Action.ObserveLocationAction) + } + } + + private fun displayToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) + .show() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val toolbar = view.findViewById<Toolbar>(R.id.toolbar) + setupToolbar(toolbar) + bindClickListeners(view) + } + + private fun bindClickListeners(fragmentView: View) { + fragmentView.let { + it.findViewById<RadioButton>(R.id.radio_use_real_location) + .setOnClickListener { radioButton -> + toggleLocationType(radioButton) + } + it.findViewById<RadioButton>(R.id.radio_use_random_location) + .setOnClickListener { radioButton -> + toggleLocationType(radioButton) + } + it.findViewById<RadioButton>(R.id.radio_use_specific_location) + .setOnClickListener { radioButton -> + toggleLocationType(radioButton) + } + it.findViewById<Button>(R.id.button_add_location) + .setOnClickListener { + val latitude = + fragmentView.findViewById<TextInputLayout>(R.id.edittext_latitude).editText?.text.toString() + .toDouble() + val longitude = + fragmentView.findViewById<TextInputLayout>(R.id.edittext_longitude).editText?.text.toString() + .toDouble() + saveSpecificLocation(latitude, longitude) + } + } + } + + private fun saveSpecificLocation(latitude: Double, longitude: Double) { + viewModel.submitAction( + FakeLocationFeature.Action.AddSpecificLocationAction(latitude, longitude) + ) + } + + private fun toggleLocationType(radioButton: View?) { + if (radioButton is RadioButton) { + val checked = radioButton.isChecked + when (radioButton.id) { + R.id.radio_use_real_location -> + if (checked) { + viewModel.submitAction(FakeLocationFeature.Action.UseRealLocationAction) + } + R.id.radio_use_random_location -> + if (checked) { + viewModel.submitAction(FakeLocationFeature.Action.UseRandomLocationAction) + } + R.id.radio_use_specific_location -> + if (checked) { + viewModel.submitAction(FakeLocationFeature.Action.UseSpecificLocationAction) + } + } + } + } + + private fun setupToolbar(toolbar: Toolbar) { + val activity = requireActivity() + activity.setActionBar(toolbar) + activity.title = "Fake My Location" + } + + override fun render(state: FakeLocationFeature.State) { + when (state) { + is FakeLocationFeature.State.LocationState -> { + Log.d("FakeMyLocation", "State: $state") + when (state.location.mode) { + LocationMode.REAL_LOCATION, LocationMode.RANDOM_LOCATION -> + view?.let { + it.findViewById<RadioButton>(R.id.radio_use_random_location).isChecked = + (state.location.mode == LocationMode.RANDOM_LOCATION) + it.findViewById<RadioButton>(R.id.radio_use_real_location).isChecked = + (state.location.mode == LocationMode.REAL_LOCATION) + it.findViewById<ImageView>(R.id.dummy_img_map).visibility = View.GONE + it.findViewById<TextInputLayout>(R.id.edittext_latitude).visibility = + View.GONE + it.findViewById<TextInputLayout>(R.id.edittext_longitude).visibility = + View.GONE + it.findViewById<Button>(R.id.button_add_location).visibility = View.GONE + } + LocationMode.CUSTOM_LOCATION -> view?.let { + it.findViewById<RadioButton>(R.id.radio_use_specific_location).isChecked = + true + it.findViewById<ImageView>(R.id.dummy_img_map).visibility = View.VISIBLE + it.findViewById<TextInputLayout>(R.id.edittext_latitude).apply { + visibility = View.VISIBLE + editText?.text = Editable.Factory.getInstance() + .newEditable(state.location.latitude.toString()) + } + it.findViewById<TextInputLayout>(R.id.edittext_longitude).apply { + visibility = View.VISIBLE + editText?.text = Editable.Factory.getInstance() + .newEditable(state.location.longitude.toString()) + } + it.findViewById<Button>(R.id.button_add_location).visibility = View.VISIBLE + } + } + } + FakeLocationFeature.State.InitialState -> { + } + } + } + + override fun actions(): Flow<FakeLocationFeature.Action> = viewModel.actions +} 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 new file mode 100644 index 0000000..eb55fba --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt @@ -0,0 +1,40 @@ +/* + * 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.features.location + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +class FakeLocationViewModel : ViewModel() { + + private val _actions = MutableSharedFlow<FakeLocationFeature.Action>() + val actions = _actions.asSharedFlow() + + val fakeLocationFeature: FakeLocationFeature by lazy { + FakeLocationFeature.create(coroutineScope = viewModelScope) + } + + fun submitAction(action: FakeLocationFeature.Action) { + viewModelScope.launch { + _actions.emit(action) + } + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt new file mode 100644 index 0000000..4f9b602 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsAdapter.kt @@ -0,0 +1,60 @@ +/* + * 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.features.permissions + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Switch +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import foundation.e.privacycentralapp.R + +class PermissionAppsAdapter( + private val dataSet: List<Pair<String, Boolean>>, + private val listener: (String, Boolean) -> Unit +) : + RecyclerView.Adapter<PermissionAppsAdapter.PermissionViewHolder>() { + + class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val appName: TextView = view.findViewById(R.id.app_title) + + @SuppressLint("UseSwitchCompatOrMaterialCode") + val togglePermission: Switch = view.findViewById(R.id.togglePermission) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_permission_apps, parent, false) + val holder = PermissionViewHolder(view) + holder.togglePermission.setOnCheckedChangeListener { _, isChecked -> + listener(dataSet[holder.adapterPosition].first, isChecked) + } + view.findViewById<Switch>(R.id.togglePermission) + return holder + } + + override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) { + val permission = dataSet[position] + holder.appName.text = permission.first + holder.togglePermission.isChecked = permission.second + } + + override fun getItemCount(): Int = dataSet.size +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt new file mode 100644 index 0000000..224d1be --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionAppsFragment.kt @@ -0,0 +1,105 @@ +/* + * 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.features.permissions + +import android.os.Bundle +import android.view.View +import android.widget.TextView +import android.widget.Toast +import android.widget.Toolbar +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.R +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect + +class PermissionAppsFragment : + Fragment(R.layout.fragment_permission_apps), + MVIView<PermissionsFeature.State, PermissionsFeature.Action> { + + private val viewModel: PermissionsViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launchWhenStarted { + viewModel.permissionsFeature.takeView(this, this@PermissionAppsFragment) + } + lifecycleScope.launchWhenStarted { + viewModel.permissionsFeature.singleEvents.collect { event -> + when (event) { + is PermissionsFeature.SingleEvent.ErrorEvent -> displayToast(event.error) + } + } + } + lifecycleScope.launchWhenStarted { + viewModel.submitAction( + PermissionsFeature.Action.LoadPermissionApps( + requireArguments().getInt( + "PERMISSION_ID" + ) + ) + ) + } + } + + private fun displayToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) + .show() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val toolbar = view.findViewById<Toolbar>(R.id.toolbar) + setupToolbar(toolbar) + } + + private fun setupToolbar(toolbar: Toolbar) { + val activity = requireActivity() + activity.setActionBar(toolbar) + activity.title = "My Apps Permission" + } + + override fun render(state: PermissionsFeature.State) { + state.currentPermission?.let { permission -> + view?.findViewById<RecyclerView>(R.id.recylcer_view_permission_apps)?.apply { + val listOfPackages = mutableListOf<Pair<String, Boolean>>() + permission.packagesRequested.forEach { + listOfPackages.add(it to permission.packagesAllowed.contains(it)) + } + layoutManager = LinearLayoutManager(requireContext()) + setHasFixedSize(true) + adapter = PermissionAppsAdapter(listOfPackages) { packageName, grant -> + viewModel.submitAction( + PermissionsFeature.Action.TogglePermissionAction( + packageName, + grant + ) + ) + } + } + view?.findViewById<TextView>(R.id.permission_control)?.text = + getString(R.string.apps_access_to_permission, permission.name) + } + } + + override fun actions(): Flow<PermissionsFeature.Action> = viewModel.actions +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsAdapter.kt new file mode 100644 index 0000000..330a1ac --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsAdapter.kt @@ -0,0 +1,63 @@ +/* + * 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.features.permissions + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.dummy.Permission + +class PermissionsAdapter( + private val context: Context, + private val dataSet: List<Permission>, + private val listener: (Int) -> Unit +) : + RecyclerView.Adapter<PermissionsAdapter.PermissionViewHolder>() { + + class PermissionViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val nameView: TextView = view.findViewById(R.id.permission_title) + val permissionCountView: TextView = view.findViewById(R.id.permission_count) + val permissionIcon: ImageView = view.findViewById(R.id.permission_icon) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PermissionViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_permission, parent, false) + val holder = PermissionViewHolder(view) + view.setOnClickListener { listener(holder.adapterPosition) } + return holder + } + + override fun onBindViewHolder(holder: PermissionViewHolder, position: Int) { + val permission = dataSet[position] + holder.nameView.text = permission.name + holder.permissionCountView.text = context.getString( + R.string.apps_allowed, + permission.packagesAllowed.size, + permission.packagesRequested.size + ) + holder.permissionIcon.setImageResource(permission.iconId) + } + + override fun getItemCount(): Int = dataSet.size +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt new file mode 100644 index 0000000..d095f00 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFeature.kt @@ -0,0 +1,118 @@ +/* + * 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.features.permissions + +import android.util.Log +import foundation.e.flowmvi.Actor +import foundation.e.flowmvi.Reducer +import foundation.e.flowmvi.SingleEventProducer +import foundation.e.flowmvi.feature.BaseFeature +import foundation.e.privacycentralapp.dummy.DummyDataSource +import foundation.e.privacycentralapp.dummy.Permission +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +// Define a state machine for Internet privacy feature +class PermissionsFeature( + initialState: State, + coroutineScope: CoroutineScope, + reducer: Reducer<State, Effect>, + actor: Actor<State, Action, Effect>, + singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent> +) : BaseFeature<PermissionsFeature.State, PermissionsFeature.Action, PermissionsFeature.Effect, PermissionsFeature.SingleEvent>( + initialState, + actor, + reducer, + coroutineScope, + { message -> Log.d("PermissionsFeature", message) }, + singleEventProducer +) { + data class State( + val permissions: List<Permission> = emptyList(), + val currentPermission: Permission? = null + ) + + sealed class SingleEvent { + data class ErrorEvent(val error: String) : SingleEvent() + } + + sealed class Action { + object ObservePermissions : Action() + data class LoadPermissionApps(val id: Int) : Action() + data class TogglePermissionAction( + val packageName: String, + val grant: Boolean + ) : Action() + } + + sealed class Effect { + data class PermissionsLoadedEffect(val permissions: List<Permission>) : Effect() + data class PermissionLoadedEffect(val permission: Permission) : Effect() + object PermissionToggledEffect : Effect() + data class ErrorEffect(val message: String) : Effect() + } + + companion object { + fun create( + initialState: State = State(), + coroutineScope: CoroutineScope + ) = PermissionsFeature( + initialState, coroutineScope, + reducer = { state, effect -> + when (effect) { + is Effect.PermissionsLoadedEffect -> State(effect.permissions) + is Effect.PermissionLoadedEffect -> state.copy(currentPermission = effect.permission) + is Effect.ErrorEffect -> state + Effect.PermissionToggledEffect -> state + } + }, + actor = { state, action -> + when (action) { + Action.ObservePermissions -> DummyDataSource.populatedPermission.map { + Effect.PermissionsLoadedEffect(it) + } + is Action.LoadPermissionApps -> flowOf( + Effect.PermissionLoadedEffect( + DummyDataSource.getPermission(action.id) + ) + ) + + is Action.TogglePermissionAction -> { + if (state.currentPermission != null) { + DummyDataSource.togglePermission( + state.currentPermission.id, + action.packageName, + action.grant + ) + flowOf(Effect.PermissionToggledEffect) + } else { + flowOf(Effect.ErrorEffect("Can't update permission")) + } + } + } + }, + singleEventProducer = { _, _, effect -> + when (effect) { + is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) + else -> null + } + } + ) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt new file mode 100644 index 0000000..864a355 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsFragment.kt @@ -0,0 +1,79 @@ +/* + * 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.features.permissions + +import android.os.Bundle +import android.view.View +import android.widget.Toolbar +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import androidx.fragment.app.add +import androidx.fragment.app.commit +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.R +import kotlinx.coroutines.flow.Flow + +class PermissionsFragment : + Fragment(R.layout.fragment_permissions), + MVIView<PermissionsFeature.State, PermissionsFeature.Action> { + + private val viewModel: PermissionsViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launchWhenStarted { + viewModel.permissionsFeature.takeView(this, this@PermissionsFragment) + } + lifecycleScope.launchWhenStarted { + viewModel.submitAction(PermissionsFeature.Action.ObservePermissions) + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val toolbar = view.findViewById<Toolbar>(R.id.toolbar) + setupToolbar(toolbar) + } + + private fun setupToolbar(toolbar: Toolbar) { + val activity = requireActivity() + activity.setActionBar(toolbar) + activity.title = "My Apps Permission" + } + + override fun render(state: PermissionsFeature.State) { + view?.findViewById<RecyclerView>(R.id.recylcer_view_permissions)?.apply { + layoutManager = LinearLayoutManager(requireContext()) + setHasFixedSize(true) + adapter = PermissionsAdapter(requireContext(), state.permissions) { permissionId -> + requireActivity().supportFragmentManager.commit { + val bundle = bundleOf("PERMISSION_ID" to permissionId) + add<PermissionAppsFragment>(R.id.container, args = bundle) + setReorderingAllowed(true) + addToBackStack("permissions") + } + } + } + } + + override fun actions(): Flow<PermissionsFeature.Action> = viewModel.actions +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt new file mode 100644 index 0000000..fc50c39 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/permissions/PermissionsViewModel.kt @@ -0,0 +1,40 @@ +/* + * 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.features.permissions + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +class PermissionsViewModel : ViewModel() { + + private val _actions = MutableSharedFlow<PermissionsFeature.Action>() + val actions = _actions.asSharedFlow() + + val permissionsFeature: PermissionsFeature by lazy { + PermissionsFeature.create(coroutineScope = viewModelScope) + } + + fun submitAction(action: PermissionsFeature.Action) { + viewModelScope.launch { + _actions.emit(action) + } + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt new file mode 100644 index 0000000..1b92cb2 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/main/MainActivity.kt @@ -0,0 +1,62 @@ +/* + * 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.main + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.add +import androidx.fragment.app.commit +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.features.dashboard.DashboardFragment + +open class MainActivity : FragmentActivity(R.layout.activity_main) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (savedInstanceState == null) { + supportFragmentManager.commit { + setReorderingAllowed(true) + add<DashboardFragment>(R.id.container) + } + } + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + handleIntent(intent) + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + handleIntent(intent) + } + + open fun handleIntent(intent: Intent) {} + + override fun finishAfterTransition() { + val resultData = Intent() + val result = onPopulateResultIntent(resultData) + setResult(result, resultData) + + super.finishAfterTransition() + } + + open fun onPopulateResultIntent(intent: Intent): Int = Activity.RESULT_OK +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/main/MainViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/main/MainViewModel.kt new file mode 100644 index 0000000..7e758b7 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/main/MainViewModel.kt @@ -0,0 +1,22 @@ +/* + * 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.main + +import androidx.lifecycle.ViewModel + +class MainViewModel : ViewModel() diff --git a/app/src/main/res/drawable/dummy_img_map_picker.png b/app/src/main/res/drawable/dummy_img_map_picker.png Binary files differnew file mode 100644 index 0000000..c1cf32b --- /dev/null +++ b/app/src/main/res/drawable/dummy_img_map_picker.png diff --git a/app/src/main/res/drawable/dummy_leakage_analytics.png b/app/src/main/res/drawable/dummy_leakage_analytics.png Binary files differnew file mode 100644 index 0000000..5379cd4 --- /dev/null +++ b/app/src/main/res/drawable/dummy_leakage_analytics.png diff --git a/app/src/main/res/drawable/ic_apps_permissions.xml b/app/src/main/res/drawable/ic_apps_permissions.xml new file mode 100644 index 0000000..b7eb1ab --- /dev/null +++ b/app/src/main/res/drawable/ic_apps_permissions.xml @@ -0,0 +1,22 @@ +<!-- + ~ 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/>. + --> + +<vector android:height="23.99944dp" android:viewportHeight="42.738" + android:viewportWidth="42.739" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#fb3846" android:pathData="M42.74,5L42.74,37.738A5,5 0,0 1,37.74 42.738L5.001,42.738A5,5 0,0 1,0.001 37.738L0.001,5A5,5 0,0 1,5.001 0L37.74,0A5,5 0,0 1,42.74 5z"/> + <path android:fillColor="#fff" android:pathData="M37.541,20.967l-6.692,-2.579v-7.529a2.638,2.638 0,0 0,-1.673 -2.37l-6.972,-2.579a2.5,2.5 0,0 0,-1.743 0l-6.972,2.579a2.638,2.638 0,0 0,-1.673 2.37v7.529l-6.693,2.579a2.41,2.41 0,0 0,-1.6 2.3v7.668a2.529,2.529 0,0 0,1.324 2.3l6.972,3.486a2.66,2.66 0,0 0,2.3 0l7.25,-3.625 7.181,3.625a2.66,2.66 0,0 0,2.3 0l6.971,-3.486a2.589,2.589 0,0 0,1.394 -2.3v-7.668a2.474,2.474 0,0 0,-1.674 -2.3zM28.478,18.457l-5.926,2.231v-4.741l5.926,-2.579zM14.256,10.789l7.111,-2.719 7.111,2.719 -7.111,2.928zM20.112,31.076l-5.926,2.928v-5.507l5.926,-2.719zM20.112,23.268l-7.111,2.858 -7.111,-2.858v-0.07l7.111,-2.649 7.111,2.649zM36.844,31.076l-5.926,2.928v-5.507l5.926,-2.719zM36.844,23.268l-7.111,2.858 -7.111,-2.858v-0.07l7.111,-2.649 7.111,2.649z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_body_monitor.xml b/app/src/main/res/drawable/ic_body_monitor.xml new file mode 100644 index 0000000..cbf0d27 --- /dev/null +++ b/app/src/main/res/drawable/ic_body_monitor.xml @@ -0,0 +1,19 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@color/black"> + <path + android:fillColor="@android:color/white" + android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM12,12c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3s3,1.34 3,3S13.66,12 12,12z"/> + <path + android:fillColor="@android:color/white" + android:pathData="M10,8.5h1v1h-1z"/> + <path + android:fillColor="@android:color/white" + android:pathData="M11.5,8.5h1v1h-1z"/> + <path + android:fillColor="@android:color/white" + android:pathData="M13,8.5h1v1h-1z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_calendar.xml b/app/src/main/res/drawable/ic_calendar.xml new file mode 100644 index 0000000..376c5a4 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@color/black"> + <path + android:fillColor="@android:color/white" + android:pathData="M9,11L7,11v2h2v-2zM13,11h-2v2h2v-2zM17,11h-2v2h2v-2zM19,4h-1L18,2h-2v2L8,4L8,2L6,2v2L5,4c-1.11,0 -1.99,0.9 -1.99,2L3,20c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,6c0,-1.1 -0.9,-2 -2,-2zM19,20L5,20L5,9h14v11z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_call.xml b/app/src/main/res/drawable/ic_call.xml new file mode 100644 index 0000000..41d8b40 --- /dev/null +++ b/app/src/main/res/drawable/ic_call.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@color/black"> + <path + android:fillColor="@android:color/white" + android:pathData="M13,9h-2v2h2L13,9zM17,9h-2v2h2L17,9zM20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57 -0.35,-0.11 -0.74,-0.03 -1.02,0.24l-2.2,2.2c-2.83,-1.44 -5.15,-3.75 -6.59,-6.58l2.2,-2.21c0.28,-0.27 0.36,-0.66 0.25,-1.01C8.7,6.45 8.5,5.25 8.5,4c0,-0.55 -0.45,-1 -1,-1L4,3c-0.55,0 -1,0.45 -1,1 0,9.39 7.61,17 17,17 0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1zM19,9v2h2L21,9h-2z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_chevron_right_24dp.xml b/app/src/main/res/drawable/ic_chevron_right_24dp.xml new file mode 100644 index 0000000..c0a38c1 --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_right_24dp.xml @@ -0,0 +1,27 @@ +<!-- + Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:autoMirrored="true" + android:height="24dp" + android:width="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0" + android:tint="#000"> + <path android:fillColor="#FF000000" + android:pathData="M9.71,18.71l-1.42,-1.42l5.3,-5.29l-5.3,-5.29l1.42,-1.42l6.7,6.71z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml new file mode 100644 index 0000000..49597b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_facebook.xml @@ -0,0 +1,31 @@ +<!-- + ~ 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/>. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="512dp" + android:height="512dp" + android:viewportWidth="512" + android:viewportHeight="512"> + <path + android:pathData="M512,256c0,-141.385 -114.615,-256 -256,-256c-141.385,0 -256,114.615 -256,256c0,127.777 93.616,233.685 216,252.89l0,-178.89l-65,0l0,-74l65,0l0,-56.4c0,-64.16 38.219,-99.6 96.695,-99.6c28.009,0 57.305,5 57.305,5l0,63l-32.281,0c-31.801,0 -41.719,19.733 -41.719,39.978l0,48.022l71,0l-11.35,74l-59.65,0l0,178.89c122.385,-19.205 216,-125.113 216,-252.89Z" + android:fillColor="#1877f2" + android:fillType="nonZero"/> + <path + android:pathData="M355.65,330l11.35,-74l-71,0l0,-48.022c0,-20.245 9.917,-39.978 41.719,-39.978l32.281,0l0,-63c0,0 -29.297,-5 -57.305,-5c-58.476,0 -96.695,35.44 -96.695,99.6l0,56.4l-65,0l0,74l65,0l0,178.89c13.033,2.045 26.392,3.11 40,3.11c13.608,0 26.966,-1.065 40,-3.11l0,-178.89l59.65,0Z" + android:fillColor="#fff" + android:fillType="nonZero"/> +</vector> diff --git a/app/src/main/res/drawable/ic_internet_activity.xml b/app/src/main/res/drawable/ic_internet_activity.xml new file mode 100644 index 0000000..ef34960 --- /dev/null +++ b/app/src/main/res/drawable/ic_internet_activity.xml @@ -0,0 +1,22 @@ +<!-- + ~ 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/>. + --> + +<vector android:height="23.99944dp" android:viewportHeight="42.738" + android:viewportWidth="42.739" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#38d874" android:pathData="M42.74,5L42.74,37.738A5,5 0,0 1,37.74 42.738L5.001,42.738A5,5 0,0 1,0.001 37.738L0.001,5A5,5 0,0 1,5.001 0L37.74,0A5,5 0,0 1,42.74 5z"/> + <path android:fillColor="#fff" android:pathData="M36.109,7.096h-29.3a3.052,3.052 0,0 0,-3.052 3.052v22.379a3.013,3.013 0,0 0,3.052 3.052h29.3a3.052,3.052 0,0 0,3.052 -3.052v-22.374a3.094,3.094 0,0 0,-3.052 -3.057zM11.896,14.471a0.773,0.773 0,0 1,-0.763 0.763h-2.543a0.735,0.735 0,0 1,-0.763 -0.763v-2.543a0.773,0.773 0,0 1,0.763 -0.763h2.543a0.82,0.82 0,0 1,0.763 0.763zM35.096,14.471a0.773,0.773 0,0 1,-0.763 0.763h-15.768a0.735,0.735 0,0 1,-0.763 -0.763v-2.543a0.773,0.773 0,0 1,0.763 -0.763h15.768a0.82,0.82 0,0 1,0.763 0.763z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_location.xml b/app/src/main/res/drawable/ic_location.xml new file mode 100644 index 0000000..e117e22 --- /dev/null +++ b/app/src/main/res/drawable/ic_location.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24" + android:tint="@color/black"> + <path + android:fillColor="@android:color/white" + android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_my_location.xml b/app/src/main/res/drawable/ic_my_location.xml new file mode 100644 index 0000000..3b04dc4 --- /dev/null +++ b/app/src/main/res/drawable/ic_my_location.xml @@ -0,0 +1,22 @@ +<!-- + ~ 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/>. + --> + +<vector android:height="23.99944dp" android:viewportHeight="42.738" + android:viewportWidth="42.739" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#fc7222" android:pathData="M42.74,5L42.74,37.738A5,5 0,0 1,37.74 42.738L5.001,42.738A5,5 0,0 1,0.001 37.738L0.001,5A5,5 0,0 1,5.001 0L37.74,0A5,5 0,0 1,42.74 5z"/> + <path android:fillColor="#fff" android:pathData="M21.369,3.651a13.318,13.318 0,0 0,-13.271 13.27c0,5.322 1.728,6.912 11.888,21.5a1.685,1.685 0,0 0,2.7 0c10.16,-14.584 11.957,-16.173 11.957,-21.5a13.364,13.364 0,0 0,-13.271 -13.27zM21.369,27.98a2.183,2.183 0,0 1,-2.212 -2.212,2.227 2.227,0 0,1 2.212,-2.212 2.274,2.274 0,0 1,2.212 2.212,2.227 2.227,0 0,1 -2.212,2.212zM23.169,20.17v0.138a1.079,1.079 0,0 1,-1.106 1.037h-1.106a1.079,1.079 0,0 1,-1.106 -1.037v-1.175a1.562,1.562 0,0 1,0.968 -1.451c2.074,-1.037 3.456,-1.866 3.456,-2.972a2.722,2.722 0,0 0,-2.773 -2.765,2.737 2.737,0 0,0 -2.626,2 1.038,1.038 0,0 1,-1.037 0.76h-1.175a1.112,1.112 0,0 1,-1.106 -1.313,6.146 6.146,0 0,1 5.944,-4.764 6.038,6.038 0,0 1,6.082 6.082c0,2.834 -2.281,4.354 -4.424,5.46z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_privacy_toggle.xml b/app/src/main/res/drawable/ic_privacy_toggle.xml new file mode 100644 index 0000000..6a0f647 --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy_toggle.xml @@ -0,0 +1,22 @@ +<!-- + ~ 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/>. + --> + +<vector android:height="84dp" android:viewportHeight="84.406" + android:viewportWidth="84.406" android:width="84dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#38d874" android:pathData="M84.406,42L84.406,42.406A42,42 0,0 1,42.406 84.406L42,84.406A42,42 0,0 1,0 42.406L0,42A42,42 0,0 1,42 0L42.406,0A42,42 0,0 1,84.406 42z"/> + <path android:fillColor="#fff" android:pathData="M42.118,13.695a6.476,6.476 0,0 0,-2.114 0.334l-21.358,8.9a5.324,5.324 0,0 0,-3.225 4.9c0,22.136 12.68,37.376 24.583,42.382a5.284,5.284 0,0 0,4.115 0c9.566,-4 24.7,-17.687 24.7,-42.382a5.432,5.432 0,0 0,-3.338 -4.9l-21.358,-8.9a6.3,6.3 0,0 0,-2.005 -0.334zM41.1739,27.31h1.888a1.432,1.432 0,0 1,1.413 1.413v14.142a1.4,1.4 0,0 1,-1.413 1.416h-1.888a1.362,1.362 0,0 1,-1.413 -1.416L39.7608,28.72a1.394,1.394 0,0 1,1.411 -1.41zM34.1319,30.292a1.4,1.4 0,0 1,1.5 0.67l0.943,1.65a1.432,1.432 0,0 1,-0.351 1.827,9.9 9.9,0 0,0 5.89,17.854 9.933,9.933 0,0 0,9.9 -9.958,10.115 10.115,0 0,0 -4.065,-7.9 1.437,1.437 0,0 1,-0.354 -1.827l0.944,-1.65a1.4,1.4 0,0 1,2.059 -0.412,14.585 14.585,0 1,1 -17.032,0 1.432,1.432 0,0 1,0.562 -0.258z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_tracked.xml b/app/src/main/res/drawable/ic_tracked.xml new file mode 100644 index 0000000..9aa4736 --- /dev/null +++ b/app/src/main/res/drawable/ic_tracked.xml @@ -0,0 +1,22 @@ +<!-- + ~ 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/>. + --> + +<vector android:height="23.99944dp" android:viewportHeight="42.738" + android:viewportWidth="42.739" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#007fff" android:pathData="M42.74,5L42.74,37.738A5,5 0,0 1,37.74 42.738L5.001,42.738A5,5 0,0 1,0.001 37.738L0.001,5A5,5 0,0 1,5.001 0L37.74,0A5,5 0,0 1,42.74 5z"/> + <path android:fillColor="#fff" android:pathData="M27.929,25.118h-3.749a1.887,1.887 0,0 0,-1.875 1.874v7.5a1.85,1.85 0,0 0,1.875 1.874h3.749a1.887,1.887 0,0 0,1.874 -1.874v-7.5a1.927,1.927 0,0 0,-1.874 -1.874zM26.992,33.553h-1.874v-5.623h1.874zM37.302,17.621h-3.749a1.887,1.887 0,0 0,-1.875 1.874v15a1.85,1.85 0,0 0,1.875 1.874h3.749a1.888,1.888 0,0 0,1.875 -1.874v-15a1.927,1.927 0,0 0,-1.875 -1.874zM36.365,33.553h-1.875v-13.121h1.875zM18.5579,17.621h-3.749a1.887,1.887 0,0 0,-1.875 1.874v15a1.85,1.85 0,0 0,1.875 1.874h3.749a1.888,1.888 0,0 0,1.875 -1.874v-15a1.927,1.927 0,0 0,-1.875 -1.874zM17.6209,33.553h-1.874v-13.121h1.874zM9.1859,26.993h-3.751a1.887,1.887 0,0 0,-1.875 1.874v5.623a1.851,1.851 0,0 0,1.875 1.874h3.749a1.887,1.887 0,0 0,1.874 -1.874v-5.623a1.927,1.927 0,0 0,-1.874 -1.875zM8.2489,33.553h-1.875v-3.749h1.875zM7.3119,21.369a2.812,2.812 0,0 0,2.812 -2.812,2.941 2.941,0 0,0 -0.117,-0.7l5.916,-5.916a3.094,3.094 0,0 0,0.762 0.059,2.974 2.974,0 0,0 1,-0.176l5.565,4.452v0.41a2.776,2.776 0,0 0,2.812 2.812,2.812 2.812,0 0,0 2.812,-2.812c0,-0.117 -0.059,-0.234 -0.059,-0.41l5.565,-4.452a3.064,3.064 0,0 0,1.054 0.176,2.812 2.812,0 0,0 2.812,-2.812 2.851,2.851 0,0 0,-2.812 -2.812,2.812 2.812,0 0,0 -2.812,2.812v0.469l-5.565,4.452a3.081,3.081 0,0 0,-1.054 -0.234,3.077 3.077,0 0,0 -1,0.234l-5.565,-4.452c0,-0.176 0.059,-0.293 0.059,-0.469a2.851,2.851 0,0 0,-2.812 -2.812,2.812 2.812,0 0,0 -2.812,2.812 3.092,3.092 0,0 0,0.059 0.761l-5.916,5.916a2.943,2.943 0,0 0,-0.7 -0.117,2.812 2.812,0 0,0 -2.812,2.812 2.776,2.776 0,0 0,2.806 2.809z"/> +</vector> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4fc2444..7013496 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,18 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" +<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Hello World!" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toLeftOf="parent" - app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - -</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file + />
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml new file mode 100644 index 0000000..663c270 --- /dev/null +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -0,0 +1,350 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + > + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top|center" + android:background="@color/white" + tools:layout_height="56dp" + /> + + <ProgressBar + android:id="@+id/loadingSpinner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:indeterminate="true" + /> + + <androidx.core.widget.NestedScrollView + android:id="@+id/scrollContainer" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="?android:attr/actionBarSize" + android:visibility="gone" + > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + android:gravity="center_horizontal" + android:orientation="vertical" + tools:context=".main.MainActivity" + > + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center" + android:paddingLeft="32dp" + android:paddingTop="16dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + android:text="@string/privacy_dashboard_info" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <ImageView + android:id="@+id/togglePrivacyCentral" + android:layout_width="96dp" + android:layout_height="96dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + android:src="@drawable/ic_privacy_toggle" + /> + + <TextView + android:id="@+id/tap_to_enable_quick_protection" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:drawableEnd="@drawable/ic_chevron_right_24dp" + android:fontFamily="sans-serif-medium" + android:gravity="center" + android:paddingLeft="32dp" + android:paddingTop="16dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + android:text="@string/tap_to_enable_quick_protection" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <ImageView + android:layout_width="match_parent" + android:layout_height="160dp" + android:src="@drawable/dummy_leakage_analytics" + /> + + <TextView + android:id="@+id/personal_leakag_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:gravity="center" + android:paddingLeft="32dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + android:text="@string/personal_leakage_info" + android:textColor="@color/black" + android:textSize="12sp" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#f9f9f9" + android:orientation="vertical" + > + + <RelativeLayout + android:id="@+id/am_i_tracked" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="32dp" + android:paddingTop="16dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + > + + <ImageView + android:id="@+id/am_i_tracked_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_tracked" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toStartOf="@+id/am_i_tracked_chevron" + android:layout_toEndOf="@+id/am_i_tracked_icon" + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingEnd="32dp" + > + + <TextView + android:id="@+id/am_i_tracked_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:text="@string/am_i_tracked_title" + android:textColor="@color/black" + android:textSize="16sp" + /> + + <TextView + android:id="@+id/am_i_tracked_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/am_i_tracked_subtitle" + android:textColor="@color/black" + android:textSize="14sp" + /> + </LinearLayout> + + <ImageView + android:id="@+id/am_i_tracked_chevron" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_chevron_right_24dp" + /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/apps_permissions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="32dp" + android:paddingTop="16dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + > + + <ImageView + android:id="@+id/apps_permissions_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_apps_permissions" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toStartOf="@+id/apps_permissions_chevron" + android:layout_toEndOf="@+id/apps_permissions_icon" + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingEnd="32dp" + > + + <TextView + android:id="@+id/apps_permissions_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:text="@string/apps_permissions_title" + android:textColor="@color/black" + android:textSize="16sp" + /> + + <TextView + android:id="@+id/apps_permissions_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/apps_permissions_subtitle" + android:textColor="@color/black" + android:textSize="14sp" + /> + </LinearLayout> + + <ImageView + android:id="@+id/apps_permissions_chevron" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_chevron_right_24dp" + /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/my_location" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="32dp" + android:paddingTop="16dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + > + + <ImageView + android:id="@+id/my_location_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_my_location" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toStartOf="@+id/my_location_chevron" + android:layout_toEndOf="@+id/my_location_icon" + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingEnd="32dp" + > + + <TextView + android:id="@+id/my_location_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:text="@string/my_location_title" + android:textColor="@color/black" + android:textSize="16sp" + /> + + <TextView + android:id="@+id/my_location_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/my_location_subtitle" + android:textColor="@color/black" + android:textSize="14sp" + /> + </LinearLayout> + + <ImageView + android:id="@+id/my_location_chevron" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_chevron_right_24dp" + /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/internet_activity_privacy" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="32dp" + android:paddingTop="16dp" + android:paddingRight="32dp" + android:paddingBottom="16dp" + > + + <ImageView + android:id="@+id/internet_activity_privacy_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_internet_activity" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toStartOf="@+id/internet_activity_privacy_chevron" + android:layout_toEndOf="@+id/internet_activity_privacy_icon" + android:orientation="vertical" + android:paddingStart="16dp" + android:paddingEnd="32dp" + > + + <TextView + android:id="@+id/internet_activity_privacy_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:text="@string/internet_activity_privacy_title" + android:textColor="@color/black" + android:textSize="16sp" + /> + + <TextView + android:id="@+id/internet_activity_privacy_subtitle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/internet_activity_privacy_subtitle" + android:textColor="@color/black" + android:textSize="14sp" + /> + </LinearLayout> + + <ImageView + android:id="@+id/internet_activity_privacy_chevron" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_chevron_right_24dp" + /> + </RelativeLayout> + + </LinearLayout> + + </LinearLayout> + </androidx.core.widget.NestedScrollView> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_fake_location.xml b/app/src/main/res/layout/fragment_fake_location.xml new file mode 100644 index 0000000..1ebe9ef --- /dev/null +++ b/app/src/main/res/layout/fragment_fake_location.xml @@ -0,0 +1,152 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + > + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top|center" + android:background="@color/white" + tools:layout_height="56dp" + /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="?android:attr/actionBarSize" + android:layout_marginBottom="32dp" + > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingLeft="32dp" + android:paddingRight="32dp" + tools:context=".main.MainActivity" + > + + <TextView + android:id="@+id/fake_location_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingTop="16dp" + android:text="@string/fake_location_info" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/learn_more_fake_location" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical" + android:text="@string/learn_more" + android:textColor="#007fff" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/my_location_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:paddingTop="16dp" + android:paddingBottom="8dp" + android:text="@string/my_location_title" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <RadioGroup + android:id="@+id/location_choices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + > + + <foundation.e.privacycentralapp.common.RightRadioButton + android:id="@+id/radio_use_real_location" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/use_real_location" + android:textSize="16sp" + /> + + <foundation.e.privacycentralapp.common.RightRadioButton + android:id="@+id/radio_use_random_location" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/use_random_location" + android:textSize="16sp" + /> + + <foundation.e.privacycentralapp.common.RightRadioButton + android:id="@+id/radio_use_specific_location" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/use_specific_location" + android:textSize="16sp" + /> + </RadioGroup> + + <ImageView + android:id="@+id/dummy_img_map" + android:layout_width="match_parent" + android:layout_height="254dp" + android:layout_marginTop="32dp" + android:layout_marginBottom="32dp" + android:src="@drawable/dummy_img_map_picker" + /> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/edittext_longitude" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/longitude" + > + + <com.google.android.material.textfield.TextInputEditText + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="numberDecimal" + /> + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/edittext_latitude" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:hint="@string/latitude" + > + + <com.google.android.material.textfield.TextInputEditText + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="numberDecimal" + /> + </com.google.android.material.textfield.TextInputLayout> + + <Button + android:id="@+id/button_add_location" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:text="@string/add_location" + app:backgroundTint="#007fff" + android:layout_marginTop="32dp" + /> + </LinearLayout> + </androidx.core.widget.NestedScrollView> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_internet_activity_policy.xml b/app/src/main/res/layout/fragment_internet_activity_policy.xml new file mode 100644 index 0000000..6a53498 --- /dev/null +++ b/app/src/main/res/layout/fragment_internet_activity_policy.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + > + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top|center" + android:background="@color/white" + tools:layout_height="56dp" + /> + + <androidx.core.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginTop="?android:attr/actionBarSize" + android:layout_marginBottom="32dp" + > + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingLeft="32dp" + android:paddingRight="32dp" + tools:context=".main.MainActivity" + > + + <TextView + android:id="@+id/internet_activity_privacy_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingTop="16dp" + android:text="@string/internet_activity_privacy_info" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/learn_more_internet_activity_privacy_info" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical" + android:text="@string/learn_more" + android:textColor="#007fff" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/my_internet_activity_header" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + android:paddingTop="16dp" + android:paddingBottom="8dp" + android:text="@string/internet_activity_privacy_title" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <RadioGroup + android:id="@+id/internet_activity_privacy_choices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + > + + <foundation.e.privacycentralapp.common.RightRadioButton + android:id="@+id/radio_use_real_ip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/use_real_ip" + android:textSize="16sp" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/i_can_be_tracked" + android:textSize="14sp"/> + + <foundation.e.privacycentralapp.common.RightRadioButton + android:id="@+id/radio_use_hidden_ip" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/hidden_ip" + android:textSize="16sp" + android:layout_marginTop="8dp" + /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/i_am_anonymous" + android:textSize="14sp"/> + </RadioGroup> + </LinearLayout> + </androidx.core.widget.NestedScrollView> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_permission_apps.xml b/app/src/main/res/layout/fragment_permission_apps.xml new file mode 100644 index 0000000..2888af0 --- /dev/null +++ b/app/src/main/res/layout/fragment_permission_apps.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + > + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top|center" + android:background="@color/white" + tools:layout_height="56dp" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_marginTop="?android:attr/actionBarSize" + tools:context=".main.MainActivity" + > + + <TextView + android:id="@+id/permission_control" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="32dp" + android:paddingRight="32dp" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <androidx.recyclerview.widget.RecyclerView + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_permission_apps" + android:id="@+id/recylcer_view_permission_apps"/> + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_permissions.xml b/app/src/main/res/layout/fragment_permissions.xml new file mode 100644 index 0000000..9d1e972 --- /dev/null +++ b/app/src/main/res/layout/fragment_permissions.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + > + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top|center" + android:background="@color/white" + tools:layout_height="56dp" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingLeft="32dp" + android:paddingRight="32dp" + android:layout_marginTop="?android:attr/actionBarSize" + tools:context=".main.MainActivity" + > + + <TextView + android:id="@+id/permission_control" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingTop="16dp" + android:text="@string/permission_control_info" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/learn_more_permissions" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:fontFamily="sans-serif-medium" + android:gravity="center_vertical" + android:text="@string/learn_more" + android:textColor="#007fff" + android:textSize="14sp" + /> + + <androidx.recyclerview.widget.RecyclerView + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:listitem="@layout/item_permission" + android:id="@+id/recylcer_view_permissions"/> + </LinearLayout> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_quick_protection.xml b/app/src/main/res/layout/fragment_quick_protection.xml new file mode 100644 index 0000000..e8233ee --- /dev/null +++ b/app/src/main/res/layout/fragment_quick_protection.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/white" + > + + <Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_gravity="top|center" + android:background="@color/white" + tools:layout_height="56dp" + /> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:layout_marginTop="?android:attr/actionBarSize" + android:layout_marginBottom="56dp" + tools:context=".main.MainActivity" + > + + <TextView + android:id="@+id/quick_protection_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingLeft="32dp" + android:paddingRight="32dp" + android:paddingTop="16dp" + android:text="@string/quick_protection_info" + android:textColor="@color/black" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/quick_protection_settings_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="32dp" + android:paddingRight="32dp" + android:fontFamily="sans-serif-medium" + android:text="@string/quick_protection_settings_list" + android:textColor="@color/black" + android:textSize="14sp" + /> + </LinearLayout> + <TextView + android:layout_width="wrap_content" + android:layout_height="56dp" + android:paddingRight="32dp" + android:paddingLeft="32dp" + android:gravity="center_vertical|right" + android:id="@+id/learn_more" + android:text="@string/learn_more" + android:textColor="#007fff" + android:textSize="14sp" + android:fontFamily="sans-serif-medium" + android:layout_gravity="bottom|right"/> +</FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_permission.xml b/app/src/main/res/layout/item_permission.xml new file mode 100644 index 0000000..8f54f64 --- /dev/null +++ b/app/src/main/res/layout/item_permission.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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/>. + --> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/item_permission" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:paddingBottom="16dp" + > + + <ImageView + android:id="@+id/permission_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_body_monitor" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toStartOf="@+id/chevron" + android:layout_toEndOf="@+id/permission_icon" + android:orientation="vertical" + android:paddingStart="32dp" + android:paddingEnd="32dp" + > + + <TextView + android:id="@+id/permission_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fontFamily="sans-serif-medium" + tools:text="Body sensor" + android:textColor="@color/black" + android:textSize="16sp" + /> + + <TextView + android:id="@+id/permission_count" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:text="3 of 8 apps allowed" + android:textSize="14sp" + /> + </LinearLayout> + + <ImageView + android:id="@+id/chevron" + android:layout_width="24dp" + android:layout_height="24dp" + android:padding="4dp" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_chevron_right_24dp" + /> +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_permission_apps.xml b/app/src/main/res/layout/item_permission_apps.xml new file mode 100644 index 0000000..aec8fec --- /dev/null +++ b/app/src/main/res/layout/item_permission_apps.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/item_permission_apps" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:paddingLeft="32dp" + android:paddingRight="32dp" + > + + <ImageView + android:id="@+id/app_icon" + android:layout_width="36dp" + android:layout_height="36dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:src="@drawable/ic_facebook" + /> + + <TextView + android:id="@+id/app_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toStartOf="@+id/togglePermission" + android:layout_toEndOf="@+id/app_icon" + android:fontFamily="sans-serif-medium" + android:paddingStart="32dp" + android:paddingEnd="32dp" + android:textColor="@color/black" + android:textSize="16sp" + tools:text="Body sensor" + /> + + <Switch + android:id="@+id/togglePermission" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + /> +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml deleted file mode 100644 index 647c8ce..0000000 --- a/app/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> - <!-- Base application theme. --> - <style name="Theme.PrivacyCentralApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> - <!-- Primary brand color. --> - <item name="colorPrimary">@color/purple_200</item> - <item name="colorPrimaryVariant">@color/purple_700</item> - <item name="colorOnPrimary">@color/black</item> - <!-- Secondary brand color. --> - <item name="colorSecondary">@color/teal_200</item> - <item name="colorSecondaryVariant">@color/teal_200</item> - <item name="colorOnSecondary">@color/black</item> - <!-- Status bar color. --> - <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> - <!-- Customize your theme here. --> - </style> -</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fdb9ac8..ff0cf0a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,3 +1,38 @@ <resources> <string name="app_name">PrivacyCentralApp</string> + <string name="privacy_dashboard_info">Privacy dashboard helps you control and better protect your privacy</string> + <string name="tap_to_enable_quick_protection">Tap to enable quick privacy protection</string> + <string name="personal_leakage_info">Personal data leakage over past 24 hours. </string> + <string name="am_i_tracked_title">Am I tracked?</string> + <string name="am_i_tracked_subtitle">Currently there are %1$d trackers in your apps, %2$d trackers are active</string> + <string name="apps_permissions_title">Apps Permissions</string> + <string name="apps_permissions_subtitle">%1$d apps are requesting %2$d permissions</string> + <string name="my_location_title">My Location</string> + <string name="my_location_subtitle">"%1$d apps are using location permission\nCurrent location mode: "</string> + <string name="internet_activity_privacy_title">My Internet Activity Privacy</string> + <string name="internet_activity_privacy_subtitle">"Current internet activity mode: "</string> + <string name="quick_protection_info">Quick protection enables these settings when turned on</string> + <string name="quick_protection_settings_list"> - All trackers are turned off.\n- Your geolocation will be faked.\n- Your real IP address will be hidden.</string> + <string name="learn_more">Learn more</string> + <string name="fake_location_info">Choose if you want to fake your real location when app asks for your geolocation tracking.</string> + <string name="use_real_location">Use real location</string> + <string name="use_random_location">Use random plausible location</string> + <string name="use_specific_location">Use specific location</string> + <string name="longitude">Longitude</string> + <string name="latitude">Latitude</string> + <string name="add_location">Add location</string> + <string name="internet_activity_privacy_info">Choose if you want to expose your real IP address or hide when connected to the internet (uses the tor network).</string> + <string name="use_real_ip">Use real IP address</string> + <string name="i_can_be_tracked">I can be tracked by my IP address</string> + <string name="hidden_ip">Hide IP address</string> + <string name="i_am_anonymous">I am anonymous on the internet</string> + <string name="i_am_exposing">I am exposing my real IP address</string> + <string name="permission_control_info">Manage and control apps requesting various permissions.</string> + + <string name="apps_allowed">%1$d of %2$d apps allowed</string> + <string name="apps_access_to_permission">Apps which has access to %1$s permission</string> + + <string name="real_location_mode">Real location mode</string> + <string name="random_location_mode">Random location mode</string> + <string name="fake_location_mode">Fake location mode</string> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 29ff5b8..3a7bad8 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,16 +1,12 @@ <resources xmlns:tools="http://schemas.android.com/tools"> <!-- Base application theme. --> - <style name="Theme.PrivacyCentralApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <style name="Theme.PrivacyCentralApp" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <!-- Primary brand color. --> - <item name="colorPrimary">@color/purple_500</item> - <item name="colorPrimaryVariant">@color/purple_700</item> - <item name="colorOnPrimary">@color/white</item> - <!-- Secondary brand color. --> - <item name="colorSecondary">@color/teal_200</item> - <item name="colorSecondaryVariant">@color/teal_700</item> - <item name="colorOnSecondary">@color/black</item> + <item name="colorPrimary">#007fff</item> + <item name="colorAccent">#007fff</item> + <item name="colorSecondary">#007fff</item> + <item name="colorControlNormal">#007fff</item> <!-- Status bar color. --> - <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <!-- Customize your theme here. --> </style> </resources>
\ No newline at end of file |