diff options
22 files changed, 766 insertions, 281 deletions
diff --git a/app/build.gradle b/app/build.gradle index 12b2e1c..63b197e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -94,6 +94,7 @@ android { dependencies { compileOnly files('libs/e-ui-sdk-1.0.1-q.jar') implementation files('libs/lineage-sdk.jar') + implementation files('libs/trackerfilter.aar') //implementation project(":privacymodulesapi") // include the google specific version of the modules, just for the google flavor diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58b6dfd..756fd0e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,8 @@ android:supportsRtl="true" android:theme="@style/Theme.PrivacyCentralApp" android:windowSoftInputMode="adjustResize" + + tools:replace="android:icon,android:label,android:theme" > <activity android:name=".main.MainActivity"> <intent-filter> diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 1ba235b..f36405d 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -24,6 +24,7 @@ import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase import foundation.e.privacycentralapp.domain.usecases.IpScramblingStateUseCase +import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase import foundation.e.privacycentralapp.domain.usecases.TrackersStatisticsUseCase import foundation.e.privacycentralapp.dummy.TrackTrackersPrivacyMock import foundation.e.privacycentralapp.features.dashboard.DashBoardViewModelFactory @@ -31,12 +32,15 @@ import foundation.e.privacycentralapp.features.internetprivacy.InternetPrivacyVi import foundation.e.privacycentralapp.features.location.FakeLocationViewModelFactory import foundation.e.privacycentralapp.features.location.LocationApiDelegate import foundation.e.privacycentralapp.features.trackers.TrackersViewModelFactory +import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersViewModelFactory import foundation.e.privacymodules.ipscrambler.IpScramblerModule import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule import foundation.e.privacymodules.location.FakeLocation import foundation.e.privacymodules.location.IFakeLocation import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.trackerfilter.api.BlockTrackersPrivacyModule +import foundation.e.trackerfilter.api.TrackTrackersPrivacyModule import kotlinx.coroutines.GlobalScope import lineageos.blockers.BlockerInterface @@ -67,9 +71,13 @@ class DependencyContainer constructor(val app: Application) { LocationApiDelegate(fakeLocationModule, permissionsModule, appDesc) } + private val blockTrackersPrivacyModule by lazy { BlockTrackersPrivacyModule.getInstance(context) } + // Repositories private val localStateRepository by lazy { LocalStateRepository(context) } - private val trackTrackersPrivacyModule by lazy { TrackTrackersPrivacyMock() } + private val trackTrackersPrivacyModule by lazy { TrackTrackersPrivacyModule.getInstance(context) } + + private val trackersPrivacyMock by lazy { TrackTrackersPrivacyMock() } // Usecases private val getQuickPrivacyStateUseCase by lazy { GetQuickPrivacyStateUseCase(localStateRepository) @@ -81,7 +89,11 @@ class DependencyContainer constructor(val app: Application) { AppListUseCase(permissionsModule) } private val trackersStatisticsUseCase by lazy { - TrackersStatisticsUseCase(trackTrackersPrivacyModule) + TrackersStatisticsUseCase(trackersPrivacyMock) + } + + private val trackersStateUseCase by lazy { + TrackersStateUseCase(trackersPrivacyMock, trackersPrivacyMock, permissionsModule) } // ViewModelFactories @@ -102,4 +114,8 @@ class DependencyContainer constructor(val app: Application) { val trackersViewModelFactory by lazy { TrackersViewModelFactory(getQuickPrivacyStateUseCase, trackersStatisticsUseCase, appListUseCase) } + + val appTrackersViewModelFactory by lazy { + AppTrackersViewModelFactory(trackersStateUseCase) + } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt new file mode 100644 index 0000000..cabe6a1 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt @@ -0,0 +1,54 @@ +/* + * 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.domain.usecases + +import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule +import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule +import foundation.e.privacymodules.trackers.Tracker + +class TrackersStateUseCase( + private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, + private val trackersPrivacyModule: ITrackTrackersPrivacyModule, + private val permissionsPrivacyModule: PermissionsPrivacyModule +) { + fun getApplicationPermission(packageName: String): ApplicationDescription { + return permissionsPrivacyModule.getApplicationDescription(packageName) + } + + fun getTrackers(appUid: Int): List<Tracker> { + return trackersPrivacyModule.getTrackersForApp(appUid) + } + + fun isWhitelisted(appUid: Int): Boolean { + return blockTrackersPrivacyModule.isWhitelisted(appUid) + } + + fun getTrackersWhitelistIds(appUid: Int): List<Int> { + return blockTrackersPrivacyModule.getWhiteList(appUid).map { it.id } + } + + fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) { + blockTrackersPrivacyModule.setWhiteListed(appUid, isWhitelisted) + } + + fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) { + blockTrackersPrivacyModule.setWhiteListed(tracker, appUid, !isBlocked) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt index 33c3f64..dc0b92b 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -17,19 +17,19 @@ package foundation.e.privacycentralapp.domain.usecases -import foundation.e.privacycentralapp.dummy.TrackTrackersPrivacyMock +import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule class TrackersStatisticsUseCase( - private val trackTrackersPrivacyModule: TrackTrackersPrivacyMock + private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule ) { - fun getPast24HoursTrackersCalls(): List<Int> { - return trackTrackersPrivacyModule.getPast24HoursTrackersCalls() + fun getPastDayTrackersCalls(): List<Int> { + return trackTrackersPrivacyModule.getPastDayTrackersCalls() } fun getDayMonthYearStatistics(): Triple<List<Int>, List<Int>, List<Int>> { return Triple( - trackTrackersPrivacyModule.getPast24HoursTrackersCalls(), + trackTrackersPrivacyModule.getPastDayTrackersCalls(), trackTrackersPrivacyModule.getPastMonthTrackersCalls(), trackTrackersPrivacyModule.getPastYearTrackersCalls() ) @@ -37,7 +37,7 @@ class TrackersStatisticsUseCase( fun getDayMonthYearCounts(): Triple<Int, Int, Int> { return Triple( - trackTrackersPrivacyModule.getPast24HoursTrackersCount(), + trackTrackersPrivacyModule.getPastDayTrackersCount(), trackTrackersPrivacyModule.getPastMonthTrackersCount(), trackTrackersPrivacyModule.getPastYearTrackersCount() ) diff --git a/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt b/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt index 55ca6ec..5b1eb9e 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/dummy/TrackTrackersPrivacyMock.kt @@ -17,11 +17,20 @@ package foundation.e.privacycentralapp.dummy +import foundation.e.privacymodules.trackers.IBlockTrackersPrivacyModule import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule import foundation.e.privacymodules.trackers.Tracker -class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule { - override fun getPast24HoursTrackersCalls(): List<Int> { +class TrackTrackersPrivacyMock : + ITrackTrackersPrivacyModule, + IBlockTrackersPrivacyModule { + + private val trackers = listOf( + Tracker(1, "Crashlytics", null), + Tracker(2, label = "Facebook", null) + ) + + override fun getPastDayTrackersCalls(): List<Int> { return listOf( 2000, 2300, 130, 2500, 1000, 2000, 2000, 2300, 130, 2500, 1000, 2000, @@ -30,7 +39,7 @@ class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule { ) } - override fun getPast24HoursTrackersCount(): Int { + override fun getPastDayTrackersCount(): Int { return 30 } @@ -64,9 +73,67 @@ class TrackTrackersPrivacyMock : ITrackTrackersPrivacyModule { } override fun getTrackersForApp(appUid: Int): List<Tracker> { - return listOf( - Tracker("Crashlytics", null), - Tracker(label = "Facebook", null) - ) + return trackers + } + + private var isBlockingEnabled = false + private val appWhitelist = mutableSetOf<Int>() + private val trackersWhitelist = mutableMapOf<Int, MutableSet<Tracker>>() + + override fun addListener(listener: IBlockTrackersPrivacyModule.Listener) { + TODO("Not yet implemented") + } + + override fun removeListener(listener: IBlockTrackersPrivacyModule.Listener) { + TODO("Not yet implemented") + } + + override fun clearListeners() { + TODO("Not yet implemented") + } + + override fun disableBlocking() {} + + override fun enableBlocking() {} + + override fun getWhiteList(appUid: Int): List<Tracker> { + return trackersWhitelist[appUid]?.toList() ?: emptyList() + } + + override fun getWhiteListedApp(): List<Int> { + return appWhitelist.toList() + } + + override fun isBlockingEnabled(): Boolean { + return isBlockingEnabled + } + + override fun isWhiteListEmpty(): Boolean { + return appWhitelist.isEmpty() && + (trackersWhitelist.isEmpty() || trackersWhitelist.values.all { it.isEmpty() }) + } + + override fun isWhitelisted(appUid: Int): Boolean { + return appUid in appWhitelist + } + + override fun setWhiteListed(tracker: Tracker, appUid: Int, isWhiteListed: Boolean) { + if (appUid !in trackersWhitelist) { + trackersWhitelist[appUid] = mutableSetOf<Tracker>() + } + + if (isWhiteListed) { + trackersWhitelist[appUid]?.add(tracker) + } else { + trackersWhitelist[appUid]?.remove(tracker) + } + } + + override fun setWhiteListed(appUid: Int, isWhiteListed: Boolean) { + if (isWhiteListed) { + appWhitelist.add(appUid) + } else { + appWhitelist.remove(appUid) + } } } 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 index d38d4f6..a6ac87a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFeature.kt @@ -190,7 +190,7 @@ class DashboardFeature( Effect.IpScramblingModeUpdatedEffect(it) }, flow { - emit(Effect.TrackersStatisticsUpdatedEffect(trackersStatisticsUseCase.getPast24HoursTrackersCalls())) + emit(Effect.TrackersStatisticsUpdatedEffect(trackersStatisticsUseCase.getPastDayTrackersCalls())) } ) /* diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackerAppsAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackerAppsAdapter.kt deleted file mode 100644 index ae236b9..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackerAppsAdapter.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.trackers - -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 -import foundation.e.privacycentralapp.dummy.Tracker - -class TrackerAppsAdapter( - private var tracker: Tracker, - private val listener: (Tracker, Boolean) -> Unit -) : - RecyclerView.Adapter<TrackerAppsAdapter.TrackerViewHolder>() { - - class TrackerViewHolder(view: View) : RecyclerView.ViewHolder(view) { - val titleView: TextView = view.findViewById(R.id.app_title) - @SuppressLint("UseSwitchCompatOrMaterialCode") - val toggleBlocker: Switch = view.findViewById(R.id.toggle) - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TrackerViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_app_toggle, parent, false) - val holder = TrackerViewHolder(view) - holder.toggleBlocker.setOnClickListener { - if (it is Switch) { - listener(tracker, it.isChecked) - } - } - return holder - } - - override fun onBindViewHolder(holder: TrackerViewHolder, position: Int) { - val app = tracker.trackedApps[position] - holder.titleView.text = app.appName - holder.toggleBlocker.isChecked = app.isEnabled - } - - override fun getItemCount(): Int = tracker.trackedApps.size -} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackerAppsFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackerAppsFragment.kt deleted file mode 100644 index fff24dc..0000000 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackerAppsFragment.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.trackers - -import android.os.Bundle -import android.util.Log -import android.view.View -import android.widget.Toast -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 foundation.e.privacycentralapp.common.NavToolbarFragment -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect - -class TrackerAppsFragment : - NavToolbarFragment(R.layout.fragment_tracker_apps), - MVIView<TrackersFeature.State, TrackersFeature.Action> { - - private val viewModel: TrackersViewModel by viewModels() - - private val TAG = "TrackerAppsFragment" - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - lifecycleScope.launchWhenStarted { - viewModel.trackersFeature.takeView(this, this@TrackerAppsFragment) - } - lifecycleScope.launchWhenStarted { - viewModel.trackersFeature.singleEvents.collect { event -> - when (event) { - is TrackersFeature.SingleEvent.ErrorEvent -> displayToast(event.error) - is TrackersFeature.SingleEvent.BlockerErrorEvent -> { - displayToast("Couldn't toggle") - // Re-render the current state to reset the switches. - render(viewModel.trackersFeature.state.value) - } - } - } - } - lifecycleScope.launchWhenStarted { - viewModel.submitAction( - TrackersFeature.Action.ObserveTracker( - requireArguments().getString( - "TRACKER" - ) - ) - ) - } - } - - private fun displayToast(message: String) { - Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) - .show() - } - - override fun getTitle(): String = getString(R.string.tracker) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - view.findViewById<RecyclerView>(R.id.recylcer_view_tracker_apps)?.apply { - layoutManager = LinearLayoutManager(requireContext()) - setHasFixedSize(true) - } - } - - override fun render(state: TrackersFeature.State) { - Log.d(TAG, "render() called with: state = $state") - state.currentSelectedTracker?.let { tracker -> - view?.findViewById<RecyclerView>(R.id.recylcer_view_tracker_apps)?.adapter = TrackerAppsAdapter(tracker) { it, grant -> - viewModel.submitAction( - TrackersFeature.Action.ToggleTrackerAction( - it, - grant - ) - ) - } - getToolbar()?.title = tracker.name - } - } - - override fun actions(): Flow<TrackersFeature.Action> = viewModel.actions -} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt index 0394abb..0120fae 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt @@ -64,7 +64,7 @@ class TrackersFeature( sealed class SingleEvent { data class ErrorEvent(val error: String) : SingleEvent() - data class OpenAppDetailsEvent(val packageName: String) : SingleEvent() + data class OpenAppDetailsEvent(val appDesc: ApplicationDescription) : SingleEvent() object BlockerErrorEvent : SingleEvent() } @@ -93,7 +93,7 @@ class TrackersFeature( data class AvailableAppsListEffect( val apps: List<ApplicationDescription> ) : Effect() - data class OpenAppDetailsEffect(val packageName: String) : Effect() + data class OpenAppDetailsEffect(val appDesc: ApplicationDescription) : Effect() object QuickPrivacyDisabledWarningEffect : Effect() data class TrackersLoadedEffect(val trackers: List<Tracker>) : Effect() data class TrackerSelectedEffect(val tracker: Tracker) : Effect() @@ -159,9 +159,11 @@ class TrackersFeature( ) is Action.ClickAppAction -> flowOf( - if (getPrivacyStateUseCase.isQuickPrivacyEnabled) - Effect.OpenAppDetailsEffect(action.packageName) - else Effect.QuickPrivacyDisabledWarningEffect + if (getPrivacyStateUseCase.isQuickPrivacyEnabled) { + state.apps?.find { it.packageName == action.packageName }?.let { + Effect.OpenAppDetailsEffect(it) + } ?: run { Effect.ErrorEffect("Can't find back app.") } + } else Effect.QuickPrivacyDisabledWarningEffect ) Action.ObserveTrackers -> TrackersDataSource.trackers.map { Effect.TrackersLoadedEffect( @@ -202,7 +204,7 @@ class TrackersFeature( singleEventProducer = { _, _, effect -> when (effect) { is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) - is Effect.OpenAppDetailsEffect -> SingleEvent.OpenAppDetailsEvent(effect.packageName) + is Effect.OpenAppDetailsEffect -> SingleEvent.OpenAppDetailsEvent(effect.appDesc) is Effect.TrackerToggleEffect -> { if (!effect.result) SingleEvent.BlockerErrorEvent else null } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt index 441f39a..a259f0b 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFragment.kt @@ -21,6 +21,8 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.core.content.ContextCompat +import androidx.fragment.app.add +import androidx.fragment.app.commit import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager @@ -36,6 +38,7 @@ import foundation.e.privacycentralapp.common.NavToolbarFragment import foundation.e.privacycentralapp.databinding.FragmentTrackersBinding import foundation.e.privacycentralapp.databinding.TrackersItemGraphBinding import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf +import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect @@ -65,7 +68,11 @@ class TrackersFragment : displayToast(event.error) } is TrackersFeature.SingleEvent.OpenAppDetailsEvent -> { - displayToast(event.packageName) + requireActivity().supportFragmentManager.commit { + add<AppTrackersFragment>(R.id.container, args = AppTrackersFragment.buildArgs(event.appDesc.label.toString(), event.appDesc.packageName)) + setReorderingAllowed(true) + addToBackStack("apptrackers") + } } } } @@ -108,14 +115,6 @@ class TrackersFragment : ) } } - - // - // requireActivity().supportFragmentManager.commit { - // val bundle = bundleOf("TRACKER" to it.name) - // add<TrackerAppsFragment>(R.id.container, args = bundle) - // setReorderingAllowed(true) - // addToBackStack("trackers") - // } } override fun getTitle() = getString(R.string.trackers_title) diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt new file mode 100644 index 0000000..a62ed16 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt @@ -0,0 +1,171 @@ +/* + * 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.trackers.apptrackers + +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.domain.usecases.TrackersStateUseCase +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import foundation.e.privacymodules.trackers.Tracker +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 Tracker feature. +class AppTrackersFeature( + initialState: State, + coroutineScope: CoroutineScope, + reducer: Reducer<State, Effect>, + actor: Actor<State, Action, Effect>, + singleEventProducer: SingleEventProducer<State, Action, Effect, SingleEvent> +) : BaseFeature<AppTrackersFeature.State, AppTrackersFeature.Action, AppTrackersFeature.Effect, AppTrackersFeature.SingleEvent>( + initialState, + actor, + reducer, + coroutineScope, + { message -> Log.d("TrackersFeature", message) }, + singleEventProducer +) { + data class State( + val appDesc: ApplicationDescription? = null, + val isBlockingActivated: Boolean = false, + val trackers: List<Tracker>? = null, + val whitelist: List<Int>? = null + ) { + fun getTrackersStatus(): List<Pair<Tracker, Boolean>>? { + if (trackers != null && whitelist != null) { + return trackers.map { it to (it.id !in whitelist) } + } else { + return null + } + } + } + + sealed class SingleEvent { + data class ErrorEvent(val error: String) : SingleEvent() + } + + sealed class Action { + data class InitAction(val packageName: String) : Action() + data class BlockAllToggleAction(val isBlocked: Boolean) : Action() + data class ToggleTrackerAction(val tracker: Tracker, val isBlocked: Boolean) : Action() + } + + sealed class Effect { + data class SetAppEffect(val appDesc: ApplicationDescription) : Effect() + data class AppTrackersBlockingActivatedEffect(val isBlockingActivated: Boolean) : Effect() + data class AvailableTrackersListEffect( + val isBlockingActivated: Boolean, + val trackers: List<Tracker>, + val whitelist: List<Int> + ) : Effect() + data class TrackersWhitelistUpdateEffect(val whitelist: List<Int>) : Effect() + + // object QuickPrivacyDisabledWarningEffect : Effect() + data class ErrorEffect(val message: String) : Effect() + } + + companion object { + fun create( + initialState: State = State(), + coroutineScope: CoroutineScope, + trackersStateUseCase: TrackersStateUseCase + ) = AppTrackersFeature( + initialState, coroutineScope, + reducer = { state, effect -> + when (effect) { + is Effect.SetAppEffect -> state.copy(appDesc = effect.appDesc) + is Effect.AvailableTrackersListEffect -> state.copy( + isBlockingActivated = effect.isBlockingActivated, + trackers = effect.trackers, + whitelist = effect.whitelist + ) + + is Effect.AppTrackersBlockingActivatedEffect -> + state.copy(isBlockingActivated = effect.isBlockingActivated) + + is Effect.TrackersWhitelistUpdateEffect -> + state.copy(whitelist = effect.whitelist) + is Effect.ErrorEffect -> state + } + }, + actor = { state, action -> + when (action) { + is Action.InitAction -> merge( + flow { + val appDesc = + trackersStateUseCase.getApplicationPermission(action.packageName) + emit(Effect.SetAppEffect(appDesc)) + + emit( + Effect.AvailableTrackersListEffect( + isBlockingActivated = !trackersStateUseCase.isWhitelisted( + appDesc.uid + ), + trackers = trackersStateUseCase.getTrackers(appDesc.uid), + whitelist = trackersStateUseCase.getTrackersWhitelistIds(appDesc.uid) + ) + ) + } + ) + is Action.BlockAllToggleAction -> + state.appDesc?.uid?.let { appUid -> + flow { + trackersStateUseCase.toggleAppWhitelist(appUid, !action.isBlocked) + + emit( + Effect.AppTrackersBlockingActivatedEffect( + !trackersStateUseCase.isWhitelisted( + appUid + ) + ) + ) + } + } ?: run { flowOf(Effect.ErrorEffect("No appDesc.")) } + is Action.ToggleTrackerAction -> { + state.appDesc?.uid?.let { appUid -> + flow { + trackersStateUseCase.blockTracker( + appUid, + action.tracker, + action.isBlocked + ) + emit( + Effect.TrackersWhitelistUpdateEffect( + trackersStateUseCase.getTrackersWhitelistIds(appUid) + ) + ) + } + } ?: run { flowOf(Effect.ErrorEffect("No appDesc.")) } + } + } + }, + singleEventProducer = { _, _, effect -> + when (effect) { + is Effect.ErrorEffect -> SingleEvent.ErrorEvent(effect.message) + else -> null + } + } + ) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt new file mode 100644 index 0000000..508aa5a --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt @@ -0,0 +1,126 @@ +/* + * 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.trackers.apptrackers + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.core.os.bundleOf +import androidx.core.view.isVisible +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import foundation.e.flowmvi.MVIView +import foundation.e.privacycentralapp.DependencyContainer +import foundation.e.privacycentralapp.PrivacyCentralApplication +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.common.NavToolbarFragment +import foundation.e.privacycentralapp.databinding.ApptrackersFragmentBinding +import foundation.e.privacycentralapp.extensions.viewModelProviderFactoryOf +import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFeature.Action +import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFeature.SingleEvent +import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFeature.State +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect + +class AppTrackersFragment : + NavToolbarFragment(R.layout.apptrackers_fragment), + MVIView<State, Action> { + companion object { + private val PARAM_LABEL = "PARAM_LABEL" + private val PARAM_PACKAGE_NAME = "PARAM_PACKAGE_NAME" + fun buildArgs(label: String, packageName: String): Bundle = bundleOf( + PARAM_LABEL to label, + PARAM_PACKAGE_NAME to packageName + ) + } + + private val dependencyContainer: DependencyContainer by lazy { + (this.requireActivity().application as PrivacyCentralApplication).dependencyContainer + } + + private val viewModel: AppTrackersViewModel by viewModels { + viewModelProviderFactoryOf { + dependencyContainer.appTrackersViewModelFactory.create() + } + } + + private lateinit var binding: ApptrackersFragmentBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + lifecycleScope.launchWhenStarted { + viewModel.feature.takeView(this, this@AppTrackersFragment) + } + lifecycleScope.launchWhenStarted { + viewModel.feature.singleEvents.collect { event -> + when (event) { + is SingleEvent.ErrorEvent -> displayToast(event.error) + } + } + } + lifecycleScope.launchWhenStarted { + viewModel.submitAction( + Action.InitAction(requireArguments().getString(PARAM_PACKAGE_NAME)) + ) + } + } + + private fun displayToast(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT) + .show() + } + + override fun getTitle(): String = requireArguments().getString(PARAM_LABEL) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = ApptrackersFragmentBinding.bind(view) + + // binding.blockAllToggle.setOnCheckedChangeListener { _, isChecked -> + // viewModel.submitAction(Action.BlockAllToggleAction(isChecked)) + // } + + binding.trackers.apply { + layoutManager = LinearLayoutManager(requireContext()) + setHasFixedSize(true) + adapter = ToggleTrackersAdapter(R.layout.apptrackers_item_tracker_toggle) { tracker, isBlocked -> + viewModel.submitAction( + Action.ToggleTrackerAction( + tracker, + isBlocked + ) + ) + } + } + } + + override fun render(state: AppTrackersFeature.State) { + // binding.blockAllToggle.isChecked = state.isBlockingActivated + + state.getTrackersStatus()?.let { + binding.trackers.isVisible = true + binding.trackers.post { + (binding.trackers.adapter as ToggleTrackersAdapter?)?.dataSet = it + } + binding.noTrackersYet.isVisible = false + } + } + + override fun actions(): Flow<Action> = viewModel.actions +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt new file mode 100644 index 0000000..8acbcac --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersViewModel.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package foundation.e.privacycentralapp.features.trackers.apptrackers + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import foundation.e.privacycentralapp.common.Factory +import foundation.e.privacycentralapp.domain.usecases.TrackersStateUseCase +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch + +class AppTrackersViewModel( + private val trackersStateUseCase: TrackersStateUseCase +) : ViewModel() { + + private val _actions = MutableSharedFlow<AppTrackersFeature.Action>() + val actions = _actions.asSharedFlow() + + val feature: AppTrackersFeature by lazy { + AppTrackersFeature.create( + coroutineScope = viewModelScope, + trackersStateUseCase = trackersStateUseCase + ) + } + + fun submitAction(action: AppTrackersFeature.Action) { + Log.d("TrackersViewModel", "submitting action") + viewModelScope.launch { + _actions.emit(action) + } + } +} + +class AppTrackersViewModelFactory( + private val trackersStateUseCase: TrackersStateUseCase +) : + Factory<AppTrackersViewModel> { + override fun create(): AppTrackersViewModel { + return AppTrackersViewModel(trackersStateUseCase) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt new file mode 100644 index 0000000..f23ebf5 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt @@ -0,0 +1,68 @@ +/* + * 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.trackers.apptrackers + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.appcompat.widget.SwitchCompat +import androidx.recyclerview.widget.RecyclerView +import foundation.e.privacycentralapp.R +import foundation.e.privacymodules.trackers.Tracker + +class ToggleTrackersAdapter( + private val itemsLayout: Int, + private val listener: (Tracker, Boolean) -> Unit +) : + RecyclerView.Adapter<ToggleTrackersAdapter.ViewHolder>() { + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val title: TextView = view.findViewById(R.id.title) + + val toggle: SwitchCompat = view.findViewById(R.id.toggle) + + fun bind(item: Pair<Tracker, Boolean>) { + title.text = item.first.label + toggle.isChecked = item.second + } + } + + var dataSet: List<Pair<Tracker, Boolean>> = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(itemsLayout, parent, false) + val holder = ViewHolder(view) + holder.toggle.setOnCheckedChangeListener { _, isChecked -> + listener(dataSet[holder.adapterPosition].first, isChecked) + } + return holder + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val permission = dataSet[position] + holder.bind(permission) + } + + override fun getItemCount(): Int = dataSet.size +} diff --git a/app/src/main/res/layout/apptrackers_fragment.xml b/app/src/main/res/layout/apptrackers_fragment.xml new file mode 100644 index 0000000..1f3063d --- /dev/null +++ b/app/src/main/res/layout/apptrackers_fragment.xml @@ -0,0 +1,76 @@ +<?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/>. + --> + +<layout 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.coordinatorlayout.widget.CoordinatorLayout + + android:background="@color/white" + android:layout_height="match_parent" + android:layout_width="match_parent" + > + + <include layout="@layout/topbar" /> + + <androidx.core.widget.NestedScrollView + android:layout_height="match_parent" + android:layout_width="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + > + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical" + > + <!--LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical"> + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:text="@string/apptrackers_block_all_toggle" + /> + <androidx.appcompat.widget.SwitchCompat + android:id="@+id/block_all_toggle" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + </LinearLayout--> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/trackers" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_marginBottom="32dp" + tools:listitem="@layout/apptrackers_item_tracker_toggle" + android:visibility="gone" + /> + <TextView + android:id="@+id/no_trackers_yet" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="@string/apptrackers_no_trackers_yet" + /> + </LinearLayout> + </androidx.core.widget.NestedScrollView> + </androidx.coordinatorlayout.widget.CoordinatorLayout> +</layout>
\ No newline at end of file diff --git a/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml b/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml new file mode 100644 index 0000000..22678ce --- /dev/null +++ b/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.appcompat.widget.LinearLayoutCompat + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_height="52dp" + android:layout_width="match_parent" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:gravity="center_vertical" + > + <TextView + android:id="@+id/title" + android:layout_centerVertical="true" + android:layout_height="wrap_content" + android:layout_width="0dp" + android:layout_weight="1" + android:maxLines="1" + android:ellipsize="end" + android:layout_marginStart="16dp" + android:textSize="14sp" + tools:text="Body sensor" + /> + + <androidx.appcompat.widget.SwitchCompat + android:id="@+id/toggle" + android:layout_width="wrap_content" + android:layout_height="24dp" + android:layoutDirection="rtl" + android:checked="true" + /> +</androidx.appcompat.widget.LinearLayoutCompat> diff --git a/app/src/main/res/layout/fragment_tracker_apps.xml b/app/src/main/res/layout/fragment_tracker_apps.xml deleted file mode 100644 index 3341d95..0000000 --- a/app/src/main/res/layout/fragment_tracker_apps.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.coordinatorlayout.widget.CoordinatorLayout 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:background="@color/white" - android:layout_height="match_parent" - android:layout_width="match_parent" - > - - <include layout="@layout/topbar" /> - - <androidx.core.widget.NestedScrollView - android:layout_height="match_parent" - android:layout_width="match_parent" - app:layout_behavior="@string/appbar_scrolling_view_behavior" - > - - <LinearLayout - android:layout_height="match_parent" - android:layout_width="match_parent" - android:orientation="vertical" - tools:context=".main.MainActivity" - > - - <TextView - android:layout_gravity="center_horizontal" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:paddingBottom="16dp" - android:paddingLeft="32dp" - android:paddingRight="32dp" - android:paddingTop="16dp" - android:text="@string/enable_disable_tracker_info" - android:textColor="@color/black" - android:textSize="14sp" - /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/recylcer_view_tracker_apps" - android:layout_height="match_parent" - android:layout_width="match_parent" - tools:listitem="@layout/item_app_toggle" - /> - </LinearLayout> - </androidx.core.widget.NestedScrollView> -</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_trackers.xml b/app/src/main/res/layout/fragment_trackers.xml index c3e7e43..f04379e 100644 --- a/app/src/main/res/layout/fragment_trackers.xml +++ b/app/src/main/res/layout/fragment_trackers.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <layout> - <androidx.coordinatorlayout.widget.CoordinatorLayout 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" @@ -13,60 +12,66 @@ <androidx.core.widget.NestedScrollView android:layout_height="match_parent" - android:layout_marginBottom="32dp" android:layout_width="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" > - <LinearLayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" - android:paddingLeft="16dp" - android:paddingRight="16dp" > - <TextView - android:id="@+id/trackers_info" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:paddingTop="16dp" - android:lineSpacingExtra="5sp" - android:text="@string/manage_trackers_info" - /> - - <include layout="@layout/trackers_item_graph" - android:id="@+id/graph_day" - android:layout_marginTop="32dp" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:period="@{@string/trackers_period_day}" - /> - <include layout="@layout/trackers_item_graph" - android:id="@+id/graph_month" - android:layout_marginTop="16dp" + <LinearLayout + android:layout_height="match_parent" android:layout_width="match_parent" - android:layout_height="wrap_content" - app:period="@{@string/trackers_period_month}" - /> - <include layout="@layout/trackers_item_graph" - android:id="@+id/graph_year" - android:layout_marginTop="16dp" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:period="@{@string/trackers_period_year}" - /> - <TextView - android:layout_gravity="center_horizontal" - android:layout_height="wrap_content" - android:layout_width="match_parent" - android:paddingTop="16dp" - android:text="@string/trackers_applist_title" - /> + android:orientation="vertical" + android:paddingLeft="16dp" + android:paddingRight="16dp" + > + <TextView + android:id="@+id/trackers_info" + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:paddingTop="16dp" + android:lineSpacingExtra="5sp" + android:text="@string/manage_trackers_info" + /> + <include layout="@layout/trackers_item_graph" + android:id="@+id/graph_day" + android:layout_marginTop="32dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:period="@{@string/trackers_period_day}" + /> + <include layout="@layout/trackers_item_graph" + android:id="@+id/graph_month" + android:layout_marginTop="16dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:period="@{@string/trackers_period_month}" + /> + <include layout="@layout/trackers_item_graph" + android:id="@+id/graph_year" + android:layout_marginTop="16dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:period="@{@string/trackers_period_year}" + /> + <TextView + android:layout_height="wrap_content" + android:layout_width="match_parent" + android:layout_marginTop="32dp" + android:paddingTop="16dp" + android:text="@string/trackers_applist_title" + /> + </LinearLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/apps" android:layout_height="wrap_content" android:layout_width="match_parent" + android:paddingTop="16dp" + android:paddingBottom="32dp" + tools:listitem="@layout/trackers_item_app" /> </LinearLayout> </androidx.core.widget.NestedScrollView> diff --git a/app/src/main/res/layout/trackers_item_graph.xml b/app/src/main/res/layout/trackers_item_graph.xml index afb93de..d0bd9ec 100644 --- a/app/src/main/res/layout/trackers_item_graph.xml +++ b/app/src/main/res/layout/trackers_item_graph.xml @@ -24,6 +24,7 @@ <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingTop="16dp" > <TextView diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14ccb6f..17d5e12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,6 +61,10 @@ <string name="trackers_period_year">past year</string> <string name="trackers_applist_title">Block trackers on each app</string> + <!-- App Trackers --> + <!--string name="apptrackers_block_all_toggle">Activate trackers blocking : </string--> + <string name="apptrackers_no_trackers_yet">Trackers will appear as they are they start tracking you.</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> diff --git a/build.gradle b/build.gradle index de5adb9..63cdb08 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ allprojects { repositories { google() mavenCentral() - mavenLocal() + //mavenLocal() maven { url 'https://api.mapbox.com/downloads/v2/releases/maven' authentication { @@ -72,6 +72,17 @@ allprojects { } maven { url "https://raw.githubusercontent.com/guardianproject/gpmaven/master" } maven { url 'https://jitpack.io' } + maven { + url "https://gitlab.e.foundation/api/v4/groups/13662/-/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = 'Private-Token' + value = gitLabPrivateToken + } + authentication { + header(HttpHeaderAuthentication) + } + } maven { url '../embeddedmavenrepository/'} } } |