/*
* Copyright (C) 2023 MURENA SAS
* 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.advancedprivacy.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.advancedprivacy.R
import foundation.e.advancedprivacy.Widget
import foundation.e.advancedprivacy.Widget.Companion.isDarkText
import foundation.e.advancedprivacy.common.extensions.dpToPxF
import foundation.e.advancedprivacy.domain.entities.FeatureState
import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState
import foundation.e.advancedprivacy.domain.entities.TrackerMode
import foundation.e.advancedprivacy.features.dashboard.DashboardFragmentArgs
import foundation.e.advancedprivacy.main.MainActivity
import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING
import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION
import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_TRACKERS
import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.PARAM_FEATURE_ENABLED
data class State(
val quickPrivacyState: QuickPrivacyState = QuickPrivacyState.DISABLED,
val trackerMode: TrackerMode = TrackerMode.VULNERABLE,
val isLocationHidden: Boolean = false,
val ipScramblingMode: FeatureState = FeatureState.STOPPING,
val dayStatistics: List> = 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
}
)
)
val trackersEnabled = state.trackerMode != TrackerMode.VULNERABLE
setImageViewResource(
R.id.toggle_trackers,
if (trackersEnabled) R.drawable.ic_switch_enabled else R.drawable.ic_switch_disabled
)
setOnClickPendingIntent(
R.id.toggle_trackers,
PendingIntent.getBroadcast(
context,
REQUEST_CODE_TOGGLE_TRACKERS,
Intent(context, WidgetCommandReceiver::class.java).apply {
action = ACTION_TOGGLE_TRACKERS
putExtra(PARAM_FEATURE_ENABLED, !trackersEnabled)
},
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
putExtra(PARAM_FEATURE_ENABLED, !state.isLocationHidden)
},
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
putExtra(PARAM_FEATURE_ENABLED, !state.ipScramblingMode.isChecked)
},
FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT
)
)
setTextViewText(
R.id.state_ip_address,
context.getString(
if (state.ipScramblingMode == FeatureState.ON) 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 = MainActivity.deepLinkBuilder(context)
.setDestination(R.id.trackersFragment)
.createPendingIntent()
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 = MainActivity.deepLinkBuilder(context)
.setDestination(R.id.dashboardFragment)
.setArguments(DashboardFragmentArgs(highlightLeaks = index).toBundle())
.createPendingIntent()
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
}
)
}
}