summaryrefslogtreecommitdiff
path: root/app/src/main
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-03-21 17:13:11 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-03-21 17:13:11 +0000
commitda00c3d0b78815c242b14e66629365fda9f18098 (patch)
tree799c478bff90fcada978801801b198873aad9338 /app/src/main
parentd534cee490986771896f4fd2ca07742007ab6751 (diff)
parent43e303886715d6115273cfba014a54805d3a1389 (diff)
Merge branch 'widget_5076' into 'main'
Add PVC Widget #5076 See merge request e/privacy-central/privacycentralapp!28
Diffstat (limited to 'app/src/main')
-rw-r--r--app/src/main/AndroidManifest.xml39
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt15
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/PrivacyCentralApplication.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/common/GraphHolder.kt3
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt8
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/UpdateWidgetUseCase.kt33
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/extensions/AnyExtension.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/dashboard/DashboardFragment.kt2
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/Widget.kt137
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/WidgetCommandReceiver.kt39
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/widget/WidgetUI.kt167
-rw-r--r--app/src/main/res/drawable/bg_graph_bar.xml20
-rw-r--r--app/src/main/res/drawable/bg_widget.xml28
-rw-r--r--app/src/main/res/drawable/dummy_img_map_picker.pngbin339121 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/dummy_leakage_analytics.pngbin25328 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/dummy_trackers_usage.pngbin16635 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/ic_quick_privacy_off.pngbin99093 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/ic_quick_privacy_on.pngbin90230 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/ic_settings.xml10
-rw-r--r--app/src/main/res/drawable/ic_shield_off.xml18
-rw-r--r--app/src/main/res/drawable/ic_shield_on.xml22
-rw-r--r--app/src/main/res/drawable/ic_switch_disabled.xml8
-rw-r--r--app/src/main/res/drawable/ic_switch_enabled.xml22
-rw-r--r--app/src/main/res/layout/widget.xml391
-rw-r--r--app/src/main/res/values/colors.xml14
-rw-r--r--app/src/main/res/values/strings.xml18
-rw-r--r--app/src/main/res/xml/widget_info.xml27
27 files changed, 991 insertions, 34 deletions
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 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- package="foundation.e.privacycentralapp">
+ package="foundation.e.privacycentralapp"
+ >
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission
+ android:name="android.permission.WRITE_SECURE_SETTINGS"
tools:ignore="ProtectedPermissions"
/>
- <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
+ <uses-permission
+ android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
tools:ignore="ProtectedPermissions"
/>
<uses-permission android:name="lineageos.permission.ACCESS_BLOCKER" />
-
- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
+ <uses-permission
+ android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission"
/>
<application
- android:name=".PrivacyCentralApplication"
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
+ android:name=".PrivacyCentralApplication"
android:persistent="${persistent}"
android:supportsRtl="true"
android:theme="@style/Theme.PrivacyCentralApp"
android:windowSoftInputMode="adjustResize"
-
tools:replace="android:icon,android:label,android:theme"
>
+ <receiver
+ android:exported="true"
+ android:name=".Widget"
+ >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/widget_info"
+ />
+ </receiver>
+ <receiver android:name=".widget.WidgetCommandReceiver">
+ <intent-filter>
+ <action android:name="toggle_privacy" />
+ </intent-filter>
+ </receiver>
+
<activity android:name=".main.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
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<Int>()
@@ -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<String> {
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 <https://www.gnu.org/licenses/>.
+ */
+
+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 <https://www.gnu.org/licenses/>.
+ */
+
+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<State> = MutableStateFlow(State())
+
+ private fun initState(
+ getPrivacyStateUseCase: GetQuickPrivacyStateUseCase,
+ ipScramblingStateUseCase: IpScramblingStateUseCase,
+ trackersStatisticsUseCase: TrackersStatisticsUseCase,
+ trackersStateUseCase: TrackersStateUseCase,
+ fakeLocationStateUseCase: FakeLocationStateUseCase,
+ coroutineScope: CoroutineScope
+ ): StateFlow<State> {
+
+ 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 <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?) {
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+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<Int> = 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 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/purple_200" />
+</shape> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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/>.
+ -->
+
+<!--
+Background for widgets to make the rounded corners based on the
+appWidgetRadius attribute value
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ >
+
+ <corners android:radius="12dp" />
+ <solid android:color="@color/widget_background" />
+</shape> \ 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
--- a/app/src/main/res/drawable/dummy_img_map_picker.png
+++ /dev/null
Binary files 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
--- a/app/src/main/res/drawable/dummy_leakage_analytics.png
+++ /dev/null
Binary files 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
--- a/app/src/main/res/drawable/dummy_trackers_usage.png
+++ /dev/null
Binary files 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
--- a/app/src/main/res/drawable/ic_quick_privacy_off.png
+++ /dev/null
Binary files 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
--- a/app/src/main/res/drawable/ic_quick_privacy_on.png
+++ /dev/null
Binary files 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 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M19.14,12.94C19.18,12.64 19.2,12.33 19.2,12C19.2,11.68 19.18,11.36 19.13,11.06L21.16,9.48C21.34,9.34 21.39,9.07 21.28,8.87L19.36,5.55C19.24,5.33 18.99,5.26 18.77,5.33L16.38,6.29C15.88,5.91 15.35,5.59 14.76,5.35L14.4,2.81C14.36,2.57 14.16,2.4 13.92,2.4H10.08C9.84,2.4 9.65,2.57 9.61,2.81L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33C5.02,5.25 4.77,5.33 4.65,5.55L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48L4.89,11.06C4.84,11.36 4.8,11.69 4.8,12C4.8,12.31 4.82,12.64 4.87,12.94L2.84,14.52C2.66,14.66 2.61,14.93 2.72,15.13L4.64,18.45C4.76,18.67 5.01,18.74 5.23,18.67L7.62,17.71C8.12,18.09 8.65,18.41 9.24,18.65L9.6,21.19C9.65,21.43 9.84,21.6 10.08,21.6H13.92C14.16,21.6 14.36,21.43 14.39,21.19L14.75,18.65C15.34,18.41 15.88,18.09 16.37,17.71L18.76,18.67C18.98,18.75 19.23,18.67 19.35,18.45L21.27,15.13C21.39,14.91 21.34,14.66 21.15,14.52L19.14,12.94ZM12,15.6C10.02,15.6 8.4,13.98 8.4,12C8.4,10.02 10.02,8.4 12,8.4C13.98,8.4 15.6,10.02 15.6,12C15.6,13.98 13.98,15.6 12,15.6Z"
+ android:fillColor="#ffffff"
+ android:fillAlpha="0.87"/>
+</vector>
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">
- <path
- android:pathData="M4,11V5.6499L12,2.0943L20,5.6499V11C20,16.0434 16.556,20.7257 12,21.9673C7.444,20.7257 4,16.0434 4,11Z"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#F8432E"/>
- <path
- android:pathData="M15.1213,10.1213L15.8284,9.4142L14.4142,8L13.7071,8.7071L11.9142,10.5L10.1213,8.7071L9.4142,8L8,9.4142L8.7071,10.1213L10.5,11.9142L8.7071,13.7071L8,14.4142L9.4142,15.8284L10.1213,15.1213L11.9142,13.3284L13.7071,15.1213L14.4142,15.8284L15.8284,14.4142L15.1213,13.7071L13.3284,11.9142L15.1213,10.1213Z"
- android:fillColor="#F8432E"
- android:fillType="evenOdd"/>
+ <path
+ android:pathData="M4,11V5.6499L12,2.0943L20,5.6499V11C20,16.0434 16.556,20.7257 12,21.9673C7.444,20.7257 4,16.0434 4,11Z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#F8432E"/>
+ <path
+ android:pathData="M15.1213,10.1213L15.8284,9.4142L14.4142,8L13.7071,8.7071L11.9142,10.5L10.1213,8.7071L9.4142,8L8,9.4142L8.7071,10.1213L10.5,11.9142L8.7071,13.7071L8,14.4142L9.4142,15.8284L10.1213,15.1213L11.9142,13.3284L13.7071,15.1213L14.4142,15.8284L15.8284,14.4142L15.1213,13.7071L13.3284,11.9142L15.1213,10.1213Z"
+ android:fillColor="#F8432E"
+ android:fillType="evenOdd"/>
</vector>
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">
- <path
- android:pathData="M4,11.5V6.1499L12,2.5943L20,6.1499V11.5C20,16.5434 16.556,21.2257 12,22.4673C7.444,21.2257 4,16.5434 4,11.5Z"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#2CC766"/>
- <path
- android:pathData="M9,12.9234L10.6951,14.5L15,10.5"
- android:strokeWidth="2"
- android:fillColor="#00000000"
- android:strokeColor="#2CC766"
- android:strokeLineCap="square"/>
+ <path
+ android:pathData="M4,11.5V6.1499L12,2.5943L20,6.1499V11.5C20,16.5434 16.556,21.2257 12,22.4673C7.444,21.2257 4,16.5434 4,11.5Z"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#2CC766"/>
+ <path
+ android:pathData="M9,12.9234L10.6951,14.5L15,10.5"
+ android:strokeWidth="2"
+ android:fillColor="#00000000"
+ android:strokeColor="#2CC766"
+ android:strokeLineCap="square"/>
</vector>
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 @@
+<vector android:height="30dp" android:viewportHeight="29"
+ android:viewportWidth="53" android:width="54.827587dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillAlpha="0.38" android:fillColor="#ffffff" android:pathData="M14.5,0L38.5,0A14.5,14.5 0,0 1,53 14.5L53,14.5A14.5,14.5 0,0 1,38.5 29L14.5,29A14.5,14.5 0,0 1,0 14.5L0,14.5A14.5,14.5 0,0 1,14.5 0z"/>
+ <path android:fillColor="#121212" android:fillType="evenOdd" android:pathData="M14.9869,26.9286C21.9321,26.9286 27.5623,21.3641 27.5623,14.5C27.5623,7.6359 21.9321,2.0714 14.9869,2.0714C8.0417,2.0714 2.4115,7.6359 2.4115,14.5C2.4115,21.3641 8.0417,26.9286 14.9869,26.9286Z"/>
+ <path android:fillAlpha="0.05" android:fillColor="#ffffff"
+ android:fillType="evenOdd" android:pathData="M14.9869,26.9286C21.9321,26.9286 27.5623,21.3641 27.5623,14.5C27.5623,7.6359 21.9321,2.0714 14.9869,2.0714C8.0417,2.0714 2.4115,7.6359 2.4115,14.5C2.4115,21.3641 8.0417,26.9286 14.9869,26.9286Z"/>
+ <path android:fillColor="#ffffff" android:fillType="evenOdd" android:pathData="M14.9869,26.9286C21.9321,26.9286 27.5623,21.3641 27.5623,14.5C27.5623,7.6359 21.9321,2.0714 14.9869,2.0714C8.0417,2.0714 2.4115,7.6359 2.4115,14.5C2.4115,21.3641 8.0417,26.9286 14.9869,26.9286Z"/>
+</vector>
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 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="55dp"
+ android:height="30dp"
+ android:viewportWidth="55"
+ android:viewportHeight="30">
+ <path
+ android:pathData="M15.3417,0L40.3417,0A14.5,14.5 0,0 1,54.8417 14.5L54.8417,14.5A14.5,14.5 0,0 1,40.3417 29L15.3417,29A14.5,14.5 0,0 1,0.8417 14.5L0.8417,14.5A14.5,14.5 0,0 1,15.3417 0z"
+ android:fillColor="#2CC766"/>
+ <path
+ android:pathData="M39.5854,26.9286C46.6616,26.9286 52.398,21.3642 52.398,14.5C52.398,7.6359 46.6616,2.0715 39.5854,2.0715C32.5091,2.0715 26.7727,7.6359 26.7727,14.5C26.7727,21.3642 32.5091,26.9286 39.5854,26.9286Z"
+ android:fillColor="#121212"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M39.5854,26.9286C46.6616,26.9286 52.398,21.3642 52.398,14.5C52.398,7.6359 46.6616,2.0715 39.5854,2.0715C32.5091,2.0715 26.7727,7.6359 26.7727,14.5C26.7727,21.3642 32.5091,26.9286 39.5854,26.9286Z"
+ android:fillColor="#ffffff"
+ android:fillAlpha="0.05"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M39.5854,26.9286C46.6616,26.9286 52.398,21.3642 52.398,14.5C52.398,7.6359 46.6616,2.0715 39.5854,2.0715C32.5091,2.0715 26.7727,7.6359 26.7727,14.5C26.7727,21.3642 32.5091,26.9286 39.5854,26.9286Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
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 @@
+<!--
+ ~ 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/>.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:orientation="vertical"
+ android:background="@drawable/bg_widget"
+ >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="40dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="12dp"
+ android:textSize="16sp"
+ android:textColor="@color/on_surface_high_emphasis"
+ android:textAllCaps="true"
+ android:text="@string/widget_title"
+ />
+ <ImageView
+ android:id="@+id/settings_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="8dp"
+ android:src="@drawable/ic_settings"
+ />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="16dp"
+ >
+ <ImageView
+ android:id="@+id/state_icon"
+ android:layout_height="25dp"
+ android:layout_width="24dp"
+ android:src="@drawable/ic_shield_off"
+ android:scaleType="fitCenter"
+ />
+ <TextView
+ android:id="@+id/state_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:layout_marginLeft="8dp"
+ android:text="@string/widget_state_title_off"
+ android:textSize="12sp"
+ android:textColor="@color/on_primary_medium_emphasis"
+ />
+ <ImageView
+ android:id="@+id/toggle_privacy_central"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_switch_disabled"
+ />
+
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginBottom="16dp">
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dashboard_state_trackers_label"
+ android:textSize="10sp"
+ android:textColor="@color/on_primary_disabled"
+ android:layout_marginBottom="4dp"
+ android:textAllCaps="true"
+ />
+ <TextView
+ android:id="@+id/state_trackers"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dashboard_state_trackers_off"
+ android:textSize="14sp"
+ android:textColor="@color/on_primary_high_emphasis"
+ android:textAllCaps="true"
+ />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dashboard_state_geolocation_label"
+ android:textSize="10sp"
+ android:textColor="@color/on_primary_disabled"
+ android:layout_marginBottom="4dp"
+ android:textAllCaps="true"
+ />
+ <TextView
+ android:id="@+id/state_geolocation"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dashboard_state_geolocation_off"
+ android:textSize="14sp"
+ android:textColor="@color/on_primary_high_emphasis"
+ android:textAllCaps="true"
+ />
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ >
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dashboard_state_ipaddress_label"
+ android:textSize="10sp"
+ android:textColor="@color/on_primary_disabled"
+ android:layout_marginBottom="4dp"
+ android:textAllCaps="true"
+ />
+ <TextView
+ android:id="@+id/state_ip_address"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/dashboard_state_ipaddress_off"
+ android:textSize="14sp"
+ android:textColor="@color/on_primary_high_emphasis"
+ android:textAllCaps="true"
+ android:visibility="gone"
+ />
+ <ProgressBar
+ android:id="@+id/state_ip_address_loader"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:indeterminate="true"
+ android:visibility="visible"/>
+ </LinearLayout>
+ </LinearLayout>
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_marginTop="16dp"
+ android:layout_marginHorizontal="24dp"
+ android:layout_height="26dp"
+ >
+ <ImageView
+ android:id="@+id/widget_graph_bar_0"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_1"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_2"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_3"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_4"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_5"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_6"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_7"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_8"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_9"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_10"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_11"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_12"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_13"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_14"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_15"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_16"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_17"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_18"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_19"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_20"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_21"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_22"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ android:layout_marginRight="1.5dp"
+ />
+ <ImageView
+ android:id="@+id/widget_graph_bar_23"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent"
+ android:src="@color/accent"
+ />
+ </LinearLayout>
+
+<!-- <com.github.mikephil.charting.charts.BarChart-->
+<!-- android:id="@+id/graph"-->
+<!-- android:layout_height="144dp"-->
+<!-- android:layout_width="match_parent"-->
+<!-- android:layout_marginHorizontal="24dp"-->
+<!-- />-->
+
+ <TextView
+ android:id="@+id/graph_legend"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ tools:text="0 Trackers"
+ android:textSize="12sp"
+ android:textColor="@color/on_primary_high_emphasis"
+ android:layout_marginTop="16dp"
+ android:layout_marginHorizontal="24dp"
+ android:layout_marginBottom="24dp"
+ />
+
+</LinearLayout> \ 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 @@
<color name="e_blue2">@lineageos.platform:color/color_default_blue1</color>
<color name="dark_color">#263238</color>
<color name="blue_unselected">#AADCFE</color>
+ <color name="light_blue_50">#FFE1F5FE</color>
+ <color name="light_blue_200">#FF81D4FA</color>
+ <color name="light_blue_600">#FF039BE5</color>
+ <color name="light_blue_900">#FF01579B</color>
+
+
+ <!-- Widget -->
+ <color name="on_surface_high_emphasis">#DEFFFFFF</color>
+ <color name="on_primary_medium_emphasis">#BDFFFFFF</color>
+ <color name="on_primary_high_emphasis">#FFFFFF</color>
+ <color name="on_primary_disabled">#61FFFFFF</color>
+
+ <color name="widget_background">#33000000</color>
+
</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 30f017c..a0df574 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -113,4 +113,22 @@
<string name="following_trackers_in_use">Following trackers are in use</string>
<string name="enable_disable_tracker_info">Enable or disable this tracker for the following apps</string>
<string name="tracker">Tracker</string>
+ <string name="appwidget_text">EXAMPLE</string>
+ <string name="add_widget">Add widget</string>
+ <string name="app_widget_description">This is an app widget description</string>
+
+ <!-- Widget -->
+ <string name="widget_title">Extended privacy</string>
+ <string name="widget_state_title_on">Your online privacy is protected</string>
+ <string name="widget_state_title_off">Your online privacy is unprotected</string>
+ <string name="widget_state_trackers_label">@string/dashboard_state_trackers_label</string>
+ <string name="widget_state_trackers_off">@string/dashboard_state_trackers_off</string>
+ <string name="widget_state_trackers_on">@string/dashboard_state_trackers_on</string>
+ <string name="widget_state_geolocation_label">@string/dashboard_state_geolocation_label</string>
+ <string name="widget_state_geolocation_off">@string/dashboard_state_geolocation_off</string>
+ <string name="widget_state_geolocation_on">@string/dashboard_state_geolocation_on</string>
+ <string name="widget_state_ipaddress_label">@string/dashboard_state_ipaddress_label</string>
+ <string name="widget_state_ipaddress_off">@string/dashboard_state_ipaddress_off</string>
+ <string name="widget_state_ipaddress_on">@string/dashboard_state_ipaddress_on</string>
+ <string name="widget_graph_trackers_legend">@string/dashboard_graph_trackers_legend</string>
</resources> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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/>.
+ -->
+
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:initialKeyguardLayout="@layout/widget"
+ android:initialLayout="@layout/widget"
+ android:minHeight="180dp"
+ android:minWidth="250dp"
+
+ android:updatePeriodMillis="0"
+ android:widgetCategory="home_screen"
+ /> \ No newline at end of file