From 43e303886715d6115273cfba014a54805d3a1389 Mon Sep 17 00:00:00 2001 From: Guillaume Jacquart Date: Mon, 21 Mar 2022 17:13:10 +0000 Subject: Add PVC Widget #5076 --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 39 +- .../e/privacycentralapp/DependencyContainer.kt | 15 +- .../privacycentralapp/PrivacyCentralApplication.kt | 2 + .../e/privacycentralapp/common/GraphHolder.kt | 3 +- .../domain/usecases/TrackersStatisticsUseCase.kt | 8 +- .../domain/usecases/UpdateWidgetUseCase.kt | 33 ++ .../e/privacycentralapp/extensions/AnyExtension.kt | 2 + .../features/dashboard/DashboardFragment.kt | 2 + .../e/privacycentralapp/widget/Widget.kt | 137 ++++++++ .../widget/WidgetCommandReceiver.kt | 39 ++ .../e/privacycentralapp/widget/WidgetUI.kt | 167 +++++++++ app/src/main/res/drawable/bg_graph_bar.xml | 20 ++ app/src/main/res/drawable/bg_widget.xml | 28 ++ app/src/main/res/drawable/dummy_img_map_picker.png | Bin 339121 -> 0 bytes .../main/res/drawable/dummy_leakage_analytics.png | Bin 25328 -> 0 bytes app/src/main/res/drawable/dummy_trackers_usage.png | Bin 16635 -> 0 bytes app/src/main/res/drawable/ic_quick_privacy_off.png | Bin 99093 -> 0 bytes app/src/main/res/drawable/ic_quick_privacy_on.png | Bin 90230 -> 0 bytes app/src/main/res/drawable/ic_settings.xml | 10 + app/src/main/res/drawable/ic_shield_off.xml | 18 +- app/src/main/res/drawable/ic_shield_on.xml | 22 +- app/src/main/res/drawable/ic_switch_disabled.xml | 8 + app/src/main/res/drawable/ic_switch_enabled.xml | 22 ++ app/src/main/res/layout/widget.xml | 391 +++++++++++++++++++++ app/src/main/res/values/colors.xml | 14 + app/src/main/res/values/strings.xml | 18 + app/src/main/res/xml/widget_info.xml | 27 ++ build.gradle | 2 +- 29 files changed, 993 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt create mode 100644 app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt create mode 100644 app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt create mode 100644 app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt create mode 100644 app/src/main/res/drawable/bg_graph_bar.xml create mode 100644 app/src/main/res/drawable/bg_widget.xml delete mode 100644 app/src/main/res/drawable/dummy_img_map_picker.png delete mode 100644 app/src/main/res/drawable/dummy_leakage_analytics.png delete mode 100644 app/src/main/res/drawable/dummy_trackers_usage.png delete mode 100644 app/src/main/res/drawable/ic_quick_privacy_off.png delete mode 100644 app/src/main/res/drawable/ic_quick_privacy_on.png create mode 100644 app/src/main/res/drawable/ic_settings.xml create mode 100644 app/src/main/res/drawable/ic_switch_disabled.xml create mode 100644 app/src/main/res/drawable/ic_switch_enabled.xml create mode 100644 app/src/main/res/layout/widget.xml create mode 100644 app/src/main/res/xml/widget_info.xml diff --git a/app/build.gradle b/app/build.gradle index fe3f903..b9b0613 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -100,6 +100,7 @@ android { buildFeatures { dataBinding true + viewBinding true } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bab78c2..c91f330 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,34 +1,55 @@ + package="foundation.e.privacycentralapp" + > - - - + + - - - + + + + + + + + + + + + + diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index 639e7b4..fa4a3e3 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -43,6 +43,7 @@ import foundation.e.privacymodules.permissions.PermissionsPrivacyModule import foundation.e.privacymodules.permissions.data.ApplicationDescription import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.GlobalScope /** @@ -76,7 +77,7 @@ class DependencyContainer(val app: Application) { private val appListsRepository by lazy { AppListsRepository(permissionsModule, context, GlobalScope) } // Usecases - private val getQuickPrivacyStateUseCase by lazy { + val getQuickPrivacyStateUseCase by lazy { GetQuickPrivacyStateUseCase(localStateRepository) } private val ipScramblingStateUseCase by lazy { @@ -87,7 +88,7 @@ class DependencyContainer(val app: Application) { } private val appListUseCase = AppListUseCase(appListsRepository) - private val trackersStatisticsUseCase by lazy { + val trackersStatisticsUseCase by lazy { TrackersStatisticsUseCase(trackTrackersPrivacyModule, blockTrackersPrivacyModule, appListsRepository, context.resources) } @@ -126,11 +127,21 @@ class DependencyContainer(val app: Application) { } // Background + @FlowPreview fun initBackgroundSingletons() { trackersStateUseCase ipScramblingStateUseCase fakeLocationStateUseCase UpdateTrackersWorker.periodicUpdate(context) + + Widget.startListening( + context, + getQuickPrivacyStateUseCase, + ipScramblingStateUseCase, + trackersStatisticsUseCase, + trackersStateUseCase, + fakeLocationStateUseCase + ) } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt index 28e96e0..2d90c93 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt @@ -19,12 +19,14 @@ package foundation.e.privacycentralapp import android.app.Application import com.mapbox.mapboxsdk.Mapbox +import kotlinx.coroutines.FlowPreview class PrivacyCentralApplication : Application() { // Initialize the dependency container. val dependencyContainer: DependencyContainer by lazy { DependencyContainer(this) } + @FlowPreview override fun onCreate() { super.onCreate() Mapbox.getTelemetry()?.setUserTelemetryRequestState(false) diff --git a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt index db6bc7e..929d838 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt @@ -34,6 +34,7 @@ import com.github.mikephil.charting.highlight.Highlight import com.github.mikephil.charting.listener.OnChartValueSelectedListener import com.github.mikephil.charting.utils.MPPointF import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.extensions.dpToPxF class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbove: Boolean = true) { var data = emptyList() @@ -113,8 +114,6 @@ class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbov } } -private fun Int.dpToPxF(context: Context): Float = this.toFloat() * context.resources.displayMetrics.density - class PeriodMarkerView(context: Context, private val isMarkerAbove: Boolean = true) : MarkerView(context, R.layout.chart_tooltip) { enum class ArrowPosition { LEFT, CENTER, RIGHT } 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 ad8f565..69dd0d8 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 @@ -34,7 +34,8 @@ import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit class TrackersStatisticsUseCase( - private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, + // TODO private + val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, private val blockTrackersPrivacyModule: IBlockTrackersPrivacyModule, private val appListsRepository: AppListsRepository, private val resources: Resources @@ -46,6 +47,7 @@ class TrackersStatisticsUseCase( offer(Unit) } } + trackTrackersPrivacyModule.addListener(listener) awaitClose { trackTrackersPrivacyModule.removeListener(listener) } } @@ -57,6 +59,10 @@ class TrackersStatisticsUseCase( ) to trackTrackersPrivacyModule.getTrackersCount() } + fun getDayTrackersCalls() = trackTrackersPrivacyModule.getPastDayTrackersCalls() + + fun getDayTrackersCount() = trackTrackersPrivacyModule.getPastDayTrackersCount() + private fun buildDayLabels(): List { val formater = DateTimeFormatter.ofPattern( resources.getString(R.string.trackers_graph_hours_period_format) diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt new file mode 100644 index 0000000..dab0b18 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacycentralapp.domain.usecases + +import foundation.e.privacycentralapp.data.repositories.LocalStateRepository +import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule + +class UpdateWidgetUseCase( + private val localStateRepository: LocalStateRepository, + private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, +) { + init { + trackTrackersPrivacyModule.addListener(object : ITrackTrackersPrivacyModule.Listener { + override fun onNewData() { + } + }) + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/extensions/AnyExtension.kt b/app/src/main/java/foundation/e/privacycentralapp/extensions/AnyExtension.kt index a870d33..2074b69 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/extensions/AnyExtension.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/extensions/AnyExtension.kt @@ -24,3 +24,5 @@ fun Any.toText(context: Context) = when (this) { is String -> this else -> this.toString() } + +fun Int.dpToPxF(context: Context): Float = this.toFloat() * context.resources.displayMetrics.density 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 index 41f6509..4d191bd 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt @@ -150,6 +150,8 @@ class DashboardFragment : else R.drawable.ic_shield_off ) + binding.togglePrivacyCentral.isChecked = state.isQuickPrivacyEnabled + val trackersEnabled = state.isQuickPrivacyEnabled && state.isAllTrackersBlocked binding.stateTrackers.text = getString( if (trackersEnabled) R.string.dashboard_state_trackers_on diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt new file mode 100644 index 0000000..1969fe5 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacycentralapp + +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.Context +import foundation.e.privacycentralapp.domain.usecases.FakeLocationStateUseCase +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.widget.State +import foundation.e.privacycentralapp.widget.render +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.sample +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +/** + * Implementation of App Widget functionality. + */ +class Widget : AppWidgetProvider() { + @FlowPreview + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + render(context, state.value, appWidgetManager) + } + + override fun onEnabled(context: Context) { + // Enter relevant functionality for when the first widget is created + } + + override fun onDisabled(context: Context) { + // Enter relevant functionality for when the last widget is disabled + } + + @FlowPreview + companion object { + private var updateWidgetJob: Job? = null + + private var state: StateFlow = MutableStateFlow(State()) + + private fun initState( + getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, + ipScramblingStateUseCase: IpScramblingStateUseCase, + trackersStatisticsUseCase: TrackersStatisticsUseCase, + trackersStateUseCase: TrackersStateUseCase, + fakeLocationStateUseCase: FakeLocationStateUseCase, + coroutineScope: CoroutineScope + ): StateFlow { + + return combine( + getPrivacyStateUseCase.quickPrivacyEnabledFlow, + trackersStateUseCase.areAllTrackersBlocked, + fakeLocationStateUseCase.locationMode, + ipScramblingStateUseCase.internetPrivacyMode + ) { isQuickPrivacyEnabled, isAllTrackersBlocked, locationMode, internetPrivacyMode -> + + State( + isQuickPrivacyEnabled = isQuickPrivacyEnabled, + isAllTrackersBlocked = isAllTrackersBlocked, + locationMode = locationMode, + internetPrivacyMode = internetPrivacyMode + ) + }.sample(50) + .combine( + trackersStatisticsUseCase.listenUpdates() + .onStart { emit(Unit) } + .debounce(5000) + ) { state, _ -> + state.copy( + dayStatistics = trackersStatisticsUseCase.getDayTrackersCalls(), + activeTrackersCount = trackersStatisticsUseCase.getDayTrackersCount() + ) + }.stateIn( + scope = coroutineScope, + started = SharingStarted.Eagerly, + initialValue = State() + ) + } + + fun startListening( + appContext: Context, + getPrivacyStateUseCase: GetQuickPrivacyStateUseCase, + ipScramblingStateUseCase: IpScramblingStateUseCase, + trackersStatisticsUseCase: TrackersStatisticsUseCase, + trackersStateUseCase: TrackersStateUseCase, + fakeLocationStateUseCase: FakeLocationStateUseCase + ) { + state = initState( + getPrivacyStateUseCase, + ipScramblingStateUseCase, + trackersStatisticsUseCase, + trackersStateUseCase, + fakeLocationStateUseCase, + GlobalScope + ) + + updateWidgetJob?.cancel() + updateWidgetJob = GlobalScope.launch(Dispatchers.Main) { + state.collect { + render(appContext, it, AppWidgetManager.getInstance(appContext)) + } + } + } + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt new file mode 100644 index 0000000..87e88df --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacycentralapp.widget + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import foundation.e.privacycentralapp.PrivacyCentralApplication + +class WidgetCommandReceiver : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + ACTION_TOGGLE_PRIVACY -> { + (context?.applicationContext as? PrivacyCentralApplication) + ?.dependencyContainer?.getQuickPrivacyStateUseCase?.toggle() + } + else -> {} + } + } + + companion object { + const val ACTION_TOGGLE_PRIVACY = "toggle_privacy" + } +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt new file mode 100644 index 0000000..ae2238f --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2022 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.privacycentralapp.widget + +import android.app.PendingIntent +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.view.View +import android.widget.RemoteViews +import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.Widget +import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode +import foundation.e.privacycentralapp.domain.entities.LocationMode +import foundation.e.privacycentralapp.extensions.dpToPxF +import foundation.e.privacycentralapp.main.MainActivity +import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_PRIVACY +import kotlinx.coroutines.FlowPreview + +data class State( + val isQuickPrivacyEnabled: Boolean = false, + val isAllTrackersBlocked: Boolean = false, + val locationMode: LocationMode = LocationMode.REAL_LOCATION, + val internetPrivacyMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP, + val dayStatistics: List = emptyList(), + val activeTrackersCount: Int = 0, +) { + val isTrackersDenied get() = isQuickPrivacyEnabled && isAllTrackersBlocked + val isLocationHidden get() = isQuickPrivacyEnabled && locationMode != LocationMode.REAL_LOCATION +} + +@FlowPreview +fun render( + context: Context, + state: State, + appWidgetManager: AppWidgetManager, +) { + val views = RemoteViews(context.packageName, R.layout.widget) + views.apply { + setOnClickPendingIntent( + R.id.settings_btn, + PendingIntent.getActivity( + context, 0, Intent(context, MainActivity::class.java), FLAG_UPDATE_CURRENT + ) + ) + + setImageViewResource( + R.id.state_icon, + if (state.isQuickPrivacyEnabled) R.drawable.ic_shield_on else R.drawable.ic_shield_off + ) + setTextViewText( + R.id.state_label, + context.getString( + if (state.isQuickPrivacyEnabled) R.string.widget_state_title_on + else R.string.widget_state_title_off + ) + ) + setImageViewResource( + R.id.toggle_privacy_central, + if (state.isQuickPrivacyEnabled) R.drawable.ic_switch_enabled + else R.drawable.ic_switch_disabled + ) + + setOnClickPendingIntent( + R.id.toggle_privacy_central, + PendingIntent.getBroadcast( + context, + 0, + Intent(context, WidgetCommandReceiver::class.java).apply { + action = ACTION_TOGGLE_PRIVACY + }, + FLAG_UPDATE_CURRENT + ) + ) + + setTextViewText( + R.id.state_trackers, + context.getString( + if (state.isTrackersDenied) R.string.widget_state_trackers_on + else R.string.widget_state_trackers_off + ) + ) + + setTextViewText( + R.id.state_geolocation, + context.getString( + if (state.isLocationHidden) R.string.widget_state_geolocation_on + else R.string.widget_state_geolocation_off + ) + ) + + setTextViewText( + R.id.state_ip_address, + context.getString( + if (state.internetPrivacyMode != InternetPrivacyMode.HIDE_IP) + R.string.widget_state_ipaddress_off + else R.string.widget_state_title_on + ) + ) + + val loading = state.internetPrivacyMode in listOf( + InternetPrivacyMode.HIDE_IP_LOADING, + InternetPrivacyMode.REAL_IP_LOADING + ) + + setViewVisibility(R.id.state_ip_address, if (loading) View.GONE else View.VISIBLE) + + setViewVisibility(R.id.state_ip_address_loader, if (loading) View.VISIBLE else View.GONE) + + val graphHeightPx = 26.dpToPxF(context) + val maxValue = state.dayStatistics.maxOrNull().let { if (it == null || it == 0) 1 else it } + val ratio = graphHeightPx / maxValue + + state.dayStatistics.zip(barIds).forEach { (value, viewId) -> + val topPadding = graphHeightPx - value * ratio + setViewPadding(viewId, 0, topPadding.toInt(), 0, 0) + } + + setTextViewText(R.id.graph_legend, context.getString(R.string.widget_graph_trackers_legend, state.activeTrackersCount.toString())) + } + + appWidgetManager.updateAppWidget(ComponentName(context, Widget::class.java), views) +} + +private val barIds = listOf( + R.id.widget_graph_bar_0, + R.id.widget_graph_bar_1, + R.id.widget_graph_bar_2, + R.id.widget_graph_bar_3, + R.id.widget_graph_bar_4, + R.id.widget_graph_bar_5, + R.id.widget_graph_bar_6, + R.id.widget_graph_bar_7, + R.id.widget_graph_bar_8, + R.id.widget_graph_bar_9, + R.id.widget_graph_bar_10, + R.id.widget_graph_bar_11, + R.id.widget_graph_bar_12, + R.id.widget_graph_bar_13, + R.id.widget_graph_bar_14, + R.id.widget_graph_bar_15, + R.id.widget_graph_bar_16, + R.id.widget_graph_bar_17, + R.id.widget_graph_bar_18, + R.id.widget_graph_bar_19, + R.id.widget_graph_bar_20, + R.id.widget_graph_bar_21, + R.id.widget_graph_bar_22, + R.id.widget_graph_bar_23 +) diff --git a/app/src/main/res/drawable/bg_graph_bar.xml b/app/src/main/res/drawable/bg_graph_bar.xml new file mode 100644 index 0000000..cdeae6e --- /dev/null +++ b/app/src/main/res/drawable/bg_graph_bar.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_widget.xml b/app/src/main/res/drawable/bg_widget.xml new file mode 100644 index 0000000..b0d91de --- /dev/null +++ b/app/src/main/res/drawable/bg_widget.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dummy_img_map_picker.png b/app/src/main/res/drawable/dummy_img_map_picker.png deleted file mode 100644 index c1cf32b..0000000 Binary files a/app/src/main/res/drawable/dummy_img_map_picker.png and /dev/null differ diff --git a/app/src/main/res/drawable/dummy_leakage_analytics.png b/app/src/main/res/drawable/dummy_leakage_analytics.png deleted file mode 100644 index 5379cd4..0000000 Binary files a/app/src/main/res/drawable/dummy_leakage_analytics.png and /dev/null differ diff --git a/app/src/main/res/drawable/dummy_trackers_usage.png b/app/src/main/res/drawable/dummy_trackers_usage.png deleted file mode 100644 index 9b7e090..0000000 Binary files a/app/src/main/res/drawable/dummy_trackers_usage.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_quick_privacy_off.png b/app/src/main/res/drawable/ic_quick_privacy_off.png deleted file mode 100644 index 90f1b04..0000000 Binary files a/app/src/main/res/drawable/ic_quick_privacy_off.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_quick_privacy_on.png b/app/src/main/res/drawable/ic_quick_privacy_on.png deleted file mode 100644 index 99f6719..0000000 Binary files a/app/src/main/res/drawable/ic_quick_privacy_on.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..e68deb7 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_shield_off.xml b/app/src/main/res/drawable/ic_shield_off.xml index f3565d5..7c2fc8e 100644 --- a/app/src/main/res/drawable/ic_shield_off.xml +++ b/app/src/main/res/drawable/ic_shield_off.xml @@ -3,13 +3,13 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> - - + + diff --git a/app/src/main/res/drawable/ic_shield_on.xml b/app/src/main/res/drawable/ic_shield_on.xml index 39416ed..b70dc6e 100644 --- a/app/src/main/res/drawable/ic_shield_on.xml +++ b/app/src/main/res/drawable/ic_shield_on.xml @@ -3,15 +3,15 @@ android:height="25dp" android:viewportWidth="24" android:viewportHeight="25"> - - + + diff --git a/app/src/main/res/drawable/ic_switch_disabled.xml b/app/src/main/res/drawable/ic_switch_disabled.xml new file mode 100644 index 0000000..4158483 --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_disabled.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_switch_enabled.xml b/app/src/main/res/drawable/ic_switch_enabled.xml new file mode 100644 index 0000000..10e83f7 --- /dev/null +++ b/app/src/main/res/drawable/ic_switch_enabled.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/layout/widget.xml b/app/src/main/res/layout/widget.xml new file mode 100644 index 0000000..612221a --- /dev/null +++ b/app/src/main/res/layout/widget.xml @@ -0,0 +1,391 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d867b74..2bf09cc 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -26,4 +26,18 @@ @lineageos.platform:color/color_default_blue1 #263238 #AADCFE + #FFE1F5FE + #FF81D4FA + #FF039BE5 + #FF01579B + + + + #DEFFFFFF + #BDFFFFFF + #FFFFFF + #61FFFFFF + + #33000000 + \ 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 30f017c..a0df574 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,4 +113,22 @@ Following trackers are in use Enable or disable this tracker for the following apps Tracker + EXAMPLE + Add widget + This is an app widget description + + + Extended privacy + Your online privacy is protected + Your online privacy is unprotected + @string/dashboard_state_trackers_label + @string/dashboard_state_trackers_off + @string/dashboard_state_trackers_on + @string/dashboard_state_geolocation_label + @string/dashboard_state_geolocation_off + @string/dashboard_state_geolocation_on + @string/dashboard_state_ipaddress_label + @string/dashboard_state_ipaddress_off + @string/dashboard_state_ipaddress_on + @string/dashboard_graph_trackers_legend \ No newline at end of file diff --git a/app/src/main/res/xml/widget_info.xml b/app/src/main/res/xml/widget_info.xml new file mode 100644 index 0000000..b20fd31 --- /dev/null +++ b/app/src/main/res/xml/widget_info.xml @@ -0,0 +1,27 @@ + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 86a50bb..5ff250d 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ buildscript { dependencies { classpath Libs.androidGradlePlugin - classpath Libs.Kotlin.gradlePlugin + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.20" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files -- cgit v1.2.1