From 43e303886715d6115273cfba014a54805d3a1389 Mon Sep 17 00:00:00 2001
From: Guillaume Jacquart <guillaume.jacquart@hoodbrains.com>
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 ++
 28 files changed, 992 insertions(+), 34 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

(limited to 'app')

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 @@
 <?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
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 @@
+<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
-- 
cgit v1.2.1