aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/foundation/e/privacycentralapp/widget
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-05-02 21:25:17 +0200
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2023-05-02 22:00:35 +0200
commita8874167f663885f2d3371801cf03681576ac817 (patch)
tree5be07b8768142efeade536d4135f2250c1ac9071 /app/src/main/java/foundation/e/privacycentralapp/widget
parenta0ee04ea9dbc0802c828afdf660eb37dc6fa350f (diff)
downloadadvanced-privacy-a8874167f663885f2d3371801cf03681576ac817.tar.gz
1200: rename everything to AdvancedPrivacy
Diffstat (limited to 'app/src/main/java/foundation/e/privacycentralapp/widget')
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt156
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt42
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt381
3 files changed, 0 insertions, 579 deletions
diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt
deleted file mode 100644
index 3abe21b..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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 <https://www.gnu.org/licenses/>.
- */
-
-package foundation.e.privacycentralapp
-
-import android.appwidget.AppWidgetManager
-import android.appwidget.AppWidgetProvider
-import android.content.Context
-import android.os.Bundle
-import foundation.e.privacycentralapp.domain.usecases.GetQuickPrivacyStateUseCase
-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.DelicateCoroutinesApi
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.sample
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import java.time.temporal.ChronoUnit
-
-/**
- * Implementation of App Widget functionality.
- */
-class Widget : AppWidgetProvider() {
-
- 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
- }
-
- companion object {
- private var updateWidgetJob: Job? = null
-
- private var state: StateFlow<State> = MutableStateFlow(State())
-
- private const val DARK_TEXT_KEY = "foundation.e.blisslauncher.WIDGET_OPTION_DARK_TEXT"
- var isDarkText = false
-
- @OptIn(FlowPreview::class)
- private fun initState(
- getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- trackersStatisticsUseCase: TrackersStatisticsUseCase,
- coroutineScope: CoroutineScope
- ): StateFlow<State> {
-
- return combine(
- getPrivacyStateUseCase.quickPrivacyState,
- getPrivacyStateUseCase.trackerMode,
- getPrivacyStateUseCase.isLocationHidden,
- getPrivacyStateUseCase.ipScramblingMode,
- ) { quickPrivacyState, trackerMode, isLocationHidden, ipScramblingMode ->
-
- State(
- quickPrivacyState = quickPrivacyState,
- trackerMode = trackerMode,
- isLocationHidden = isLocationHidden,
- ipScramblingMode = ipScramblingMode
- )
- }.sample(50)
- .combine(
- merge(
- trackersStatisticsUseCase.listenUpdates()
- .onStart { emit(Unit) }
- .debounce(5000),
- flow {
- while (true) {
- emit(Unit)
- delay(ChronoUnit.HOURS.duration.toMillis())
- }
- }
-
- )
- ) { state, _ ->
- state.copy(
- dayStatistics = trackersStatisticsUseCase.getDayTrackersCalls(),
- activeTrackersCount = trackersStatisticsUseCase.getDayTrackersCount()
- )
- }.stateIn(
- scope = coroutineScope,
- started = SharingStarted.Eagerly,
- initialValue = State()
- )
- }
-
- @OptIn(DelicateCoroutinesApi::class)
- fun startListening(
- appContext: Context,
- getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
- trackersStatisticsUseCase: TrackersStatisticsUseCase,
- ) {
- state = initState(
- getPrivacyStateUseCase,
- trackersStatisticsUseCase,
- GlobalScope
- )
-
- updateWidgetJob?.cancel()
- updateWidgetJob = GlobalScope.launch(Dispatchers.Main) {
- state.collect {
- render(appContext, it, AppWidgetManager.getInstance(appContext))
- }
- }
- }
- }
-
- override fun onAppWidgetOptionsChanged(
- context: Context,
- appWidgetManager: AppWidgetManager,
- appWidgetId: Int,
- newOptions: Bundle?
- ) {
- super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
- if (newOptions != null) {
- isDarkText = newOptions.getBoolean(DARK_TEXT_KEY)
- }
- render(context, state.value, appWidgetManager)
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt
deleted file mode 100644
index e01f47f..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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 <https://www.gnu.org/licenses/>.
- */
-
-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?) {
- val getQuickPrivacyStateUseCase = (context?.applicationContext as? PrivacyCentralApplication)?.dependencyContainer?.getQuickPrivacyStateUseCase
-
- when (intent?.action) {
- ACTION_TOGGLE_TRACKERS -> getQuickPrivacyStateUseCase?.toggleTrackers()
- ACTION_TOGGLE_LOCATION -> getQuickPrivacyStateUseCase?.toggleLocation()
- ACTION_TOGGLE_IPSCRAMBLING -> getQuickPrivacyStateUseCase?.toggleIpScrambling()
- else -> {}
- }
- }
-
- companion object {
- const val ACTION_TOGGLE_TRACKERS = "toggle_trackers"
- const val ACTION_TOGGLE_LOCATION = "toggle_location"
- const val ACTION_TOGGLE_IPSCRAMBLING = "toggle_ipscrambling"
- }
-}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt b/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt
deleted file mode 100644
index fccfd48..0000000
--- a/app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * 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 <https://www.gnu.org/licenses/>.
- */
-
-package foundation.e.privacycentralapp.widget
-
-import android.app.PendingIntent
-import android.app.PendingIntent.FLAG_IMMUTABLE
-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.Widget.Companion.isDarkText
-import foundation.e.privacycentralapp.common.extensions.dpToPxF
-import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode
-import foundation.e.privacycentralapp.domain.entities.QuickPrivacyState
-import foundation.e.privacycentralapp.domain.entities.TrackerMode
-import foundation.e.privacycentralapp.main.MainActivity
-import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING
-import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION
-import foundation.e.privacycentralapp.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_TRACKERS
-
-data class State(
- val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED,
- val trackerMode: TrackerMode = TrackerMode.VULNERABLE,
- val isLocationHidden: Boolean = false,
- val ipScramblingMode: InternetPrivacyMode = InternetPrivacyMode.REAL_IP_LOADING,
- val dayStatistics: List<Pair<Int, Int>> = emptyList(),
- val activeTrackersCount: Int = 0,
-)
-
-fun render(
- context: Context,
- state: State,
- appWidgetManager: AppWidgetManager,
-) {
- val views = RemoteViews(context.packageName, R.layout.widget)
- applyDarkText(context, state, views)
- views.apply {
- val openPIntent = PendingIntent.getActivity(
- context,
- REQUEST_CODE_DASHBOARD,
- Intent(context, MainActivity::class.java),
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- setOnClickPendingIntent(R.id.settings_btn, openPIntent)
- setOnClickPendingIntent(R.id.widget_container, openPIntent)
-
- setTextViewText(
- R.id.state_label,
- context.getString(
- when (state.quickPrivacyState) {
- QuickPrivacyState.DISABLED -> R.string.widget_state_title_off
- QuickPrivacyState.FULL_ENABLED -> R.string.widget_state_title_on
- QuickPrivacyState.ENABLED -> R.string.widget_state_title_custom
- }
- )
- )
-
- setImageViewResource(
- R.id.toggle_trackers,
- if (state.trackerMode == TrackerMode.VULNERABLE)
- R.drawable.ic_switch_disabled
- else R.drawable.ic_switch_enabled
- )
-
- setOnClickPendingIntent(
- R.id.toggle_trackers,
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE_TOGGLE_TRACKERS,
- Intent(context, WidgetCommandReceiver::class.java).apply {
- action = ACTION_TOGGLE_TRACKERS
- },
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- )
-
- setTextViewText(
- R.id.state_trackers,
- context.getString(
- when (state.trackerMode) {
- TrackerMode.DENIED -> R.string.widget_state_trackers_on
- TrackerMode.VULNERABLE -> R.string.widget_state_trackers_off
- TrackerMode.CUSTOM -> R.string.widget_state_trackers_custom
- }
- )
- )
-
- setImageViewResource(
- R.id.toggle_location,
- if (state.isLocationHidden) R.drawable.ic_switch_enabled
- else R.drawable.ic_switch_disabled
- )
-
- setOnClickPendingIntent(
- R.id.toggle_location,
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE_TOGGLE_LOCATION,
- Intent(context, WidgetCommandReceiver::class.java).apply {
- action = ACTION_TOGGLE_LOCATION
- },
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- )
-
- setTextViewText(
- R.id.state_geolocation,
- context.getString(
- if (state.isLocationHidden) R.string.widget_state_geolocation_on
- else R.string.widget_state_geolocation_off
- )
- )
-
- setImageViewResource(
- R.id.toggle_ipscrambling,
- if (state.ipScramblingMode.isChecked) R.drawable.ic_switch_enabled
- else R.drawable.ic_switch_disabled
- )
-
- setOnClickPendingIntent(
- R.id.toggle_ipscrambling,
- PendingIntent.getBroadcast(
- context,
- REQUEST_CODE_TOGGLE_IPSCRAMBLING,
- Intent(context, WidgetCommandReceiver::class.java).apply {
- action = ACTION_TOGGLE_IPSCRAMBLING
- },
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- )
-
- setTextViewText(
- R.id.state_ip_address,
- context.getString(
- if (state.ipScramblingMode == InternetPrivacyMode.HIDE_IP) R.string.widget_state_ipaddress_on
- else R.string.widget_state_ipaddress_off
- )
- )
-
- val loading = state.ipScramblingMode.isLoading
-
- 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)
-
- if (state.dayStatistics.all { it.first == 0 && it.second == 0 }) {
- setViewVisibility(R.id.graph, View.GONE)
- setViewVisibility(R.id.graph_legend, View.GONE)
- setViewVisibility(R.id.graph_empty, View.VISIBLE)
- setViewVisibility(R.id.graph_legend_values, View.GONE)
- setViewVisibility(R.id.graph_view_trackers_btn, View.GONE)
- } else {
- setViewVisibility(R.id.graph, View.VISIBLE)
- setViewVisibility(R.id.graph_legend, View.VISIBLE)
- setViewVisibility(R.id.graph_empty, View.GONE)
- setViewVisibility(R.id.graph_legend_values, View.VISIBLE)
- setViewVisibility(R.id.graph_view_trackers_btn, View.VISIBLE)
-
- val pIntent = PendingIntent.getActivity(
- context,
- REQUEST_CODE_TRACKERS,
- MainActivity.createTrackersIntent(context),
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
-
- setOnClickPendingIntent(R.id.graph_view_trackers_btn, pIntent)
-
- val graphHeightPx = 26.dpToPxF(context)
- val maxValue =
- state.dayStatistics
- .map { it.first + it.second }
- .maxOrNull()
- .let { if (it == null || it == 0) 1 else it }
- val ratio = graphHeightPx / maxValue
-
- state.dayStatistics.forEachIndexed { index, (blocked, leaked) ->
- // blocked (the bar below)
- val middlePadding = graphHeightPx - blocked * ratio
- setViewPadding(blockedBarIds[index], 0, middlePadding.toInt(), 0, 0)
-
- // leaked (the bar above)
- val topPadding = graphHeightPx - (blocked + leaked) * ratio
- setViewPadding(leakedBarIds[index], 0, topPadding.toInt(), 0, 0)
-
- val highlightPIntent = PendingIntent.getActivity(
- context, REQUEST_CODE_HIGHLIGHT + index,
- MainActivity.createHighlightLeaksIntent(context, index),
- FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
- )
- setOnClickPendingIntent(containerBarIds[index], highlightPIntent)
- }
-
- 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 containerBarIds = listOf(
- R.id.widget_graph_bar_container_0,
- R.id.widget_graph_bar_container_1,
- R.id.widget_graph_bar_container_2,
- R.id.widget_graph_bar_container_3,
- R.id.widget_graph_bar_container_4,
- R.id.widget_graph_bar_container_5,
- R.id.widget_graph_bar_container_6,
- R.id.widget_graph_bar_container_7,
- R.id.widget_graph_bar_container_8,
- R.id.widget_graph_bar_container_9,
- R.id.widget_graph_bar_container_10,
- R.id.widget_graph_bar_container_11,
- R.id.widget_graph_bar_container_12,
- R.id.widget_graph_bar_container_13,
- R.id.widget_graph_bar_container_14,
- R.id.widget_graph_bar_container_15,
- R.id.widget_graph_bar_container_16,
- R.id.widget_graph_bar_container_17,
- R.id.widget_graph_bar_container_18,
- R.id.widget_graph_bar_container_19,
- R.id.widget_graph_bar_container_20,
- R.id.widget_graph_bar_container_21,
- R.id.widget_graph_bar_container_22,
- R.id.widget_graph_bar_container_23,
-)
-
-private val blockedBarIds = 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
-)
-
-private val leakedBarIds = listOf(
- R.id.widget_leaked_graph_bar_0,
- R.id.widget_leaked_graph_bar_1,
- R.id.widget_leaked_graph_bar_2,
- R.id.widget_leaked_graph_bar_3,
- R.id.widget_leaked_graph_bar_4,
- R.id.widget_leaked_graph_bar_5,
- R.id.widget_leaked_graph_bar_6,
- R.id.widget_leaked_graph_bar_7,
- R.id.widget_leaked_graph_bar_8,
- R.id.widget_leaked_graph_bar_9,
- R.id.widget_leaked_graph_bar_10,
- R.id.widget_leaked_graph_bar_11,
- R.id.widget_leaked_graph_bar_12,
- R.id.widget_leaked_graph_bar_13,
- R.id.widget_leaked_graph_bar_14,
- R.id.widget_leaked_graph_bar_15,
- R.id.widget_leaked_graph_bar_16,
- R.id.widget_leaked_graph_bar_17,
- R.id.widget_leaked_graph_bar_18,
- R.id.widget_leaked_graph_bar_19,
- R.id.widget_leaked_graph_bar_20,
- R.id.widget_leaked_graph_bar_21,
- R.id.widget_leaked_graph_bar_22,
- R.id.widget_leaked_graph_bar_23
-)
-
-private const val REQUEST_CODE_DASHBOARD = 1
-private const val REQUEST_CODE_TRACKERS = 3
-private const val REQUEST_CODE_TOGGLE_TRACKERS = 4
-private const val REQUEST_CODE_TOGGLE_LOCATION = 5
-private const val REQUEST_CODE_TOGGLE_IPSCRAMBLING = 6
-private const val REQUEST_CODE_HIGHLIGHT = 100
-
-fun applyDarkText(context: Context, state: State, views: RemoteViews) {
- views.apply {
- listOf(
- R.id.state_label,
- R.id.graph_legend_blocked,
- R.id.graph_legend_allowed,
-
- )
- .forEach {
- setTextColor(
- it,
- context.getColor(if (isDarkText) R.color.on_surface_disabled_light else R.color.on_primary_medium_emphasis)
- )
- }
- setTextColor(
- R.id.widget_title,
- context.getColor(if (isDarkText) R.color.on_surface_medium_emphasis_light else R.color.on_surface_high_emphasis)
- )
- listOf(
- R.id.state_trackers,
- R.id.state_geolocation,
- R.id.state_ip_address,
- R.id.graph_legend,
- R.id.graph_view_trackers_btn
- )
- .forEach {
- setTextColor(
- it,
- context.getColor(if (isDarkText) R.color.on_surface_medium_emphasis_light else R.color.on_primary_high_emphasis)
- )
- }
-
- listOf(
- R.id.trackers_label,
- R.id.geolocation_label,
- R.id.ip_address_label,
- R.id.graph_empty
-
- )
- .forEach {
- setTextColor(
- it,
- context.getColor(if (isDarkText) R.color.on_surface_disabled_light else R.color.on_primary_disabled)
- )
- }
- setTextViewCompoundDrawables(
- R.id.graph_view_trackers_btn,
- 0,
- 0,
- if (isDarkText) R.drawable.ic_chevron_right_24dp_light else R.drawable.ic_chevron_right_24dp,
- 0
- )
- setImageViewResource(
- R.id.settings_btn,
- if (isDarkText) R.drawable.ic_settings_light else R.drawable.ic_settings
- )
- setImageViewResource(
- R.id.state_icon,
- if (isDarkText) {
- if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on_light
- else R.drawable.ic_shield_off_light
- } else {
- if (state.quickPrivacyState.isEnabled()) R.drawable.ic_shield_on_white
- else R.drawable.ic_shield_off_white
- }
- )
- }
-}