From 8ca39b63e2d15fbb6828e255be4e0b5493c744ce Mon Sep 17 00:00:00 2001
From: Soren Stoutner
Date: Wed, 13 Sep 2017 15:09:05 -0700
Subject: [PATCH] Implement a night mode option.
https://redmine.stoutner.com/issues/145.
---
.idea/dictionaries/soren.xml | 2 +
app/src/free/res/layout/main_webview.xml | 8 +-
app/src/main/assets/de/about_licenses.html | 3 +
app/src/main/assets/en/about_licenses.html | 3 +
app/src/main/assets/en/images/night_mode.png | Bin 0 -> 1280 bytes
app/src/main/assets/es/about_licenses.html | 8 +-
app/src/main/assets/it/about_licenses.html | 4 +
.../activities/DomainsActivity.java | 10 +-
.../activities/MainWebViewActivity.java | 96 +++++-
.../fragments/DomainSettingsFragment.java | 283 ++++++++++++++++--
.../fragments/DomainsListFragment.java | 8 +-
.../fragments/SettingsFragment.java | 115 +++++--
.../helpers/DomainsDatabaseHelper.java | 25 +-
app/src/main/res/drawable/add_dark.xml | 2 +-
app/src/main/res/drawable/add_light.xml | 2 +-
.../res/drawable/night_mode_disabled_dark.xml | 19 ++
.../drawable/night_mode_disabled_light.xml | 19 ++
.../res/drawable/night_mode_enabled_dark.xml | 19 ++
.../res/drawable/night_mode_enabled_light.xml | 19 ++
app/src/main/res/drawable/privacy_mode.xml | 6 +-
.../res/drawable/refresh_disabled_dark.xml | 2 +-
.../res/drawable/refresh_disabled_light.xml | 2 +-
.../res/drawable/refresh_enabled_dark.xml | 2 +-
.../res/drawable/refresh_enabled_light.xml | 2 +-
.../res/layout/domain_settings_fragment.xml | 37 +++
app/src/main/res/layout/main_webview.xml | 2 +
app/src/main/res/values-es/strings.xml | 2 +-
app/src/main/res/values-it/strings.xml | 2 +-
app/src/main/res/values/strings.xml | 9 +-
app/src/main/res/xml/preferences.xml | 6 +
.../02 - SSL Certificate Mismatch - it.png | Bin 258201 -> 0 bytes
.../02 - SSL Certificate Mismatch.png | Bin 0 -> 258045 bytes
32 files changed, 635 insertions(+), 82 deletions(-)
create mode 100644 app/src/main/assets/en/images/night_mode.png
create mode 100644 app/src/main/res/drawable/night_mode_disabled_dark.xml
create mode 100644 app/src/main/res/drawable/night_mode_disabled_light.xml
create mode 100644 app/src/main/res/drawable/night_mode_enabled_dark.xml
create mode 100644 app/src/main/res/drawable/night_mode_enabled_light.xml
delete mode 100644 fastlane/metadata/android/it/sevenInchScreenshots/02 - SSL Certificate Mismatch - it.png
create mode 100644 fastlane/metadata/android/it/sevenInchScreenshots/02 - SSL Certificate Mismatch.png
diff --git a/.idea/dictionaries/soren.xml b/.idea/dictionaries/soren.xml
index 25a08015..859d2123 100644
--- a/.idea/dictionaries/soren.xml
+++ b/.idea/dictionaries/soren.xml
@@ -72,6 +72,7 @@
mitm
mozilla
navigationview
+ nightmode
nojs
oname
orbot
@@ -112,6 +113,7 @@
subfolders
tablayout
techrepublic
+ textarea
textview
theverge
torproject
diff --git a/app/src/free/res/layout/main_webview.xml b/app/src/free/res/layout/main_webview.xml
index c58d242f..fb80533e 100644
--- a/app/src/free/res/layout/main_webview.xml
+++ b/app/src/free/res/layout/main_webview.xml
@@ -19,6 +19,7 @@
along with Privacy Browser. If not, see . -->
+
+ tools:showIn="@layout/main_drawerlayout" >
+ ads:adUnitId="@string/ad_id" >
+ android:layout_above="@id/adview" >
is derived from ic_exit_to_app, which is part of the Android Material icon set and is released under the Apache License 2.0.
The full text of the license is below. Modifications copyright © 2017 Soren Stoutner. The resulting image is released under the GPLv3+ license.
+
+ is derived from ic_compare, which is part of the Android Material icon set and is released under the Apache License 2.0.
+ The full text of the license is below. Modifications copyright © 2017 Soren Stoutner. The resulting image is released under the GPLv3+ license.
orbot is a modified version of the status icon from the Orbot project, which is copyright
2009-2010 Nathan Freitas, The Guardian Project. It is released under the 3-clause BSD license.
The full text of the license is below. Modifications copyright © 2017 Soren Stoutner. The resulting image is released under the GPLv3+ license.
diff --git a/app/src/main/assets/en/about_licenses.html b/app/src/main/assets/en/about_licenses.html
index 295123d5..54025adb 100644
--- a/app/src/main/assets/en/about_licenses.html
+++ b/app/src/main/assets/en/about_licenses.html
@@ -58,6 +58,9 @@
is derived from ic_exit_to_app, which is part of the Android Material icon set and is released under the Apache License 2.0.
The full text of the license is below. Modifications copyright © 2017 Soren Stoutner. The resulting image is released under the GPLv3+ license.
+
+ is derived from ic_compare, which is part of the Android Material icon set and is released under the Apache License 2.0.
+ The full text of the license is below. Modifications copyright © 2017 Soren Stoutner. The resulting image is released under the GPLv3+ license.
orbot is a modified version of the status icon from the Orbot project, which is copyright
2009-2010 Nathan Freitas, The Guardian Project. It is released under the 3-clause BSD license.
The full text of the license is below. Modifications copyright © 2017 Soren Stoutner. The resulting image is released under the GPLv3+ license.
diff --git a/app/src/main/assets/en/images/night_mode.png b/app/src/main/assets/en/images/night_mode.png
new file mode 100644
index 0000000000000000000000000000000000000000..5e6cf45409029876e838a2ed33350d2eebd3de38
GIT binary patch
literal 1280
zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEX7WqAsj$Z!;#Vf4nJ
zsKb*8A(o`+VxU;JP=iD4&@Sz$GhEVzU(d`fKUaNj&wqKI+dApz=GN}0KKG`A
zRS*a`(;L6Da242m{JoBm{nwn1cEk6aA_-MVx4wCQrQ<01v=-)_BSwaflkbw`)Ijy>(e?uM=G
zAHEs#$}k>_-lHe}I`h=meF@Io56%|K-?;E-Ywd)mQVGljtP!ae%l_Q=dLZ?^d{%S5yFXZSxYGBk+ySdu_kWAcue@{3JAr-bd!8)5
z1?NTUSG_7{+sZZ}`RM#rujV?gWqcC#arvsU^nhfBo#B60uDT<6MXcd^mwjldb@MEy
z4>3T=yz~HX2Gun`fkm-%F_a^_CaEV!?^yvIo;avA6
zr^p;|d(iM*h35~;obU6F)OhhGFj=tHJh2sIHa<}HVA^+=drOZn*s=LBd+$|GdZGE@
z>znI~ridL-dLZ#(=XaN?59VjB5B7X_d8Vb?5O45MvEojbj_9$3GM`?y>y
zD?PxAL0a?0qLhF$=N7auv~79hAA0880qX|Ebw7edHyVH7NjL#?+dFIK^9*hK9{E4r
zW&D9l;Z&FX)Z6C{C^uvP<@BtZ?=ZRmy;pQo@(E7@=h@FifxZ{5!EVBtXtqgm{+>>D^9#5WtJn9Nlicencia GPLv3+.
deriva de ic_exit_to_app, que es parte del conjunto de iconos Android Material y es liberado bajo la Licencia Apache 2.0.
- El texto completo de la licencia se encuentra debajo. Copyright de modificaciones © 2017 Soren Stoutner. La imagen resultante se libera bajo la
- licencia GPLv3+.
+ El texto completo de la licencia se encuentra debajo. Copyright de modificaciones © 2017 Soren Stoutner.
+ La imagen resultante se libera bajo la licencia GPLv3+.
+
+ deriva de ic_exit_to_app, que es parte del conjunto de iconos Android Material y es liberado bajo la Licencia Apache 2.0.
+ El texto completo de la licencia se encuentra debajo. Copyright de modificaciones © 2017 Soren Stoutner.
+ La imagen resultante se libera bajo la licencia GPLv3+.
orbot es una versión modificada del icono de estado del proyecto Orbot,
que tiene copyright 2009-2010 por Nathan Freitas, The Guardian Project. Es liberado bajo la licencia BSD modificada (de 3 cláusulas).
El texto completo de la licencia se encuentra debajo. Copyright de modificaciones © 2017 Soren Stoutner.
diff --git a/app/src/main/assets/it/about_licenses.html b/app/src/main/assets/it/about_licenses.html
index 7e389967..77986b30 100644
--- a/app/src/main/assets/it/about_licenses.html
+++ b/app/src/main/assets/it/about_licenses.html
@@ -66,6 +66,10 @@
è stata derivata da ic_exit_to_app, che fa parte dell'Android Material icon set ed è stata rilasciata sotto Licenza Apache 2.0.
Il testo completo della licenza è riportato di seguito. Copyright delle modifiche © 2017 Soren Stoutner.
L'immagine risultante è rilasciata sotto Licenza GPLv3+.
+
+ è stata derivata da ic_compare, che fa parte dell'Android Material icon set ed è stata rilasciata sotto Licenza Apache 2.0.
+ Il testo completo della licenza è riportato di seguito. Copyright delle modifiche © 2017 Soren Stoutner.
+ L'immagine risultante è rilasciata sotto Licenza GPLv3+.
orbot è una versione modificata della icona di stato del progetto Orbot, il cui copyright
è 2009-2010 Nathan Freitas, The Guardian Project. E' rilasciata sotto 3-clause BSD license. Il testo completo della Licenza è riportato di seguito.
Copyright delle modifiche © 2017Soren Stoutner. L'immagine risultante è rilasciata sotto Licenza GPLv3+.
diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
index f71a1384..de68623e 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
+++ b/app/src/main/java/com/stoutner/privacybrowser/activities/DomainsActivity.java
@@ -520,6 +520,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
EditText customUserAgentEditText = (EditText) findViewById(R.id.domain_settings_custom_user_agent_edittext);
Spinner fontSizeSpinner = (Spinner) findViewById(R.id.domain_settings_font_size_spinner);
Spinner displayWebpageImagesSpinner = (Spinner) findViewById(R.id.domain_settings_display_webpage_images_spinner);
+ Spinner nightModeSpinner = (Spinner) findViewById(R.id.domain_settings_night_mode_spinner);
Switch pinnedSslCertificateSwitch = (Switch) findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
RadioButton savedSslCertificateRadioButton = (RadioButton) findViewById(R.id.saved_ssl_certificate_radiobutton);
RadioButton currentWebsiteCertificateRadioButton = (RadioButton) findViewById(R.id.current_website_certificate_radiobutton);
@@ -534,6 +535,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition();
int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition();
int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
+ int nightModeInt = nightModeSpinner.getSelectedItemPosition();
boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
// Get the data for the `Spinners` from the entry values string arrays.
@@ -550,7 +552,7 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
if (savedSslCertificateRadioButton.isChecked()) { // The current certificate is being used.
// Update the database except for the certificate.
domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
- formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate);
+ formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt, pinnedSslCertificate);
} else if (currentWebsiteCertificateRadioButton.isChecked()) { // The certificate is being updated with the current website certificate.
// Get the current website SSL certificate.
SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
@@ -567,12 +569,12 @@ public class DomainsActivity extends AppCompatActivity implements AddDomainDialo
// Update the database.
domainsDatabaseHelper.updateDomainWithCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean,
- userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, issuedByOrganizationalUnit, startDateLong,
- endDateLong);
+ userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization, issuedByOrganizationalUnit,
+ startDateLong, endDateLong);
} else { // No certificate is selected.
// Update the database, with PINNED_SSL_CERTIFICATE set to false.
domainsDatabaseHelper.updateDomainExceptCertificate(currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean, formDataEnabledBoolean,
- userAgentString, fontSizeInt, displayWebpageImagesInt, false);
+ userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt, false);
}
}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
index b13a7852..02ac2567 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
+++ b/app/src/main/java/com/stoutner/privacybrowser/activities/MainWebViewActivity.java
@@ -41,6 +41,7 @@ import android.net.http.SslCertificate;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.preference.PreferenceManager;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
@@ -151,8 +152,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
// `displayWebpageImagesBoolean` is public static so it can be accessed from `DomainSettingsFragment`. It is also used in `applyAppSettings()` and `applyDomainSettings()`.
public static boolean displayWebpageImagesBoolean;
- // `reloadOnRestartBoolean` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
- public static boolean reloadOnRestartBoolean;
+ // `reloadOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`
+ public static boolean reloadOnRestart;
+
+ // `reloadUrlOnRestart` is public static so it can be accessed from `SettingsFragment`. It is also used in `onRestart()`.
+ public static boolean loadUrlOnRestart;
// The pinned domain SSL Certificate variables are public static so they can be accessed from `PinnedSslCertificateMismatchDialog`. They are also used in `onCreate()` and `applyDomainSettings()`.
public static int domainSettingsDatabaseId;
@@ -203,22 +207,24 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
// `customHeader` is used in `onCreate()`, `onOptionsItemSelected()`, `onCreateContextMenu()`, and `loadUrl()`.
private final Map customHeaders = new HashMap<>();
- // `javaScriptEnabled` is also used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, `loadUrlFromTextBox()`, and `applyAppSettings()`.
- // It is `Boolean` instead of `boolean` because `applyAppSettings()` needs to know if it is `null`.
- private Boolean javaScriptEnabled;
+ // `javaScriptEnabled` is also used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `applyDomainSettings()`, and `updatePrivacyIcons()`.
+ private boolean javaScriptEnabled;
- // `firstPartyCookiesEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyAppSettings()`.
+ // `firstPartyCookiesEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, `onDownloadImage()`, `onDownloadFile()`, and `applyDomainSettings()`.
private boolean firstPartyCookiesEnabled;
- // `thirdPartyCookiesEnabled` used in `onCreate()`, `onCreateOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
+ // `thirdPartyCookiesEnabled` used in `onCreate()`, `onPrepareOptionsMenu()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
private boolean thirdPartyCookiesEnabled;
- // `domStorageEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
+ // `domStorageEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
private boolean domStorageEnabled;
- // `saveFormDataEnabled` is used in `onCreate()`, `onCreateOptionsMenu()`, `onOptionsItemSelected()`, and `applyAppSettings()`.
+ // `saveFormDataEnabled` is used in `onCreate()`, `onPrepareOptionsMenu()`, `onOptionsItemSelected()`, and `applyDomainSettings()`.
private boolean saveFormDataEnabled;
+ // `nightMode` is used in `onCreate()` and `applyDomainSettings()`.
+ private boolean nightMode;
+
// `swipeToRefreshEnabled` is used in `onPrepareOptionsMenu()` and `applyAppSettings()`.
private boolean swipeToRefreshEnabled;
@@ -749,6 +755,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
// Update the URL in urlTextBox when the page starts to load.
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ // If night mode is enabled, hide `mainWebView` until after the night mode CSS is applied.
+ if (nightMode) {
+ mainWebView.setVisibility(View.INVISIBLE);
+ }
+
// Check to see if we are waiting on Orbot.
if (!waitingForOrbot) { // We are not waiting on Orbot, so we need to process the URL.
// We need to update `formattedUrlString` at the beginning of the load, so that if the user toggles JavaScript during the load the new website is reloaded.
@@ -770,7 +781,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
}
}
- // Update formattedUrlString and urlTextBox. It is necessary to do this after the page finishes loading because the final URL can change during load.
+ // It is necessary to update `formattedUrlString` and `urlTextBox` after the page finishes loading because the final URL can change during load.
@Override
public void onPageFinished(WebView view, String url) {
// Reset `urlIsLoading`, which is used to prevent reloads on redirect if the user agent changes.
@@ -933,10 +944,36 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
public void onProgressChanged(WebView view, int progress) {
progressBar.setProgress(progress);
if (progress < 100) {
+ // Show the progress bar.
progressBar.setVisibility(View.VISIBLE);
} else {
+ // Hide the progress bar.
progressBar.setVisibility(View.GONE);
+ // Inject the night mode CSS if night mode is enabled.
+ if (nightMode) {
+ // `background-color: #212121` sets the background to be dark gray. `color: #BDBDBD` sets the text color to be light gray. `box-shadow: none` removes a lower underline on links used by WordPress.
+ // `text-decoration: none` removes all text underlines. `text-shadow: none` removes text shadows, which usually have a hard coded color. `border: none` removes all borders, which can also be used to underline text.
+ // `a {color: #1565C0}` sets links to be a dark blue. `!important` takes precedent over any existing sub-settings.
+ mainWebView.evaluateJavascript("(function() {var parent = document.getElementsByTagName('head').item(0); var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = '" +
+ "* {background-color: #212121 !important; color: #BDBDBD !important; box-shadow: none !important; text-decoration: none !important; text-shadow: none !important; border: none !important}" +
+ "a {color: #1565C0 !important;}" +
+ "'; parent.appendChild(style)})()", null);
+ }
+
+ // Initialize a `Handler` to display `mainWebView`, which may have been hid by a night mode domain setting even if night mode is not currently enabled.
+ Handler displayWebViewHandler = new Handler();
+
+ // Setup a `Runnable` to display `mainWebView` after a delay to allow the CSS to be applied.
+ Runnable displayWebViewRunnable = new Runnable() {
+ public void run() {
+ mainWebView.setVisibility(View.VISIBLE);
+ }
+ };
+
+ // Use `displayWebViewHandler` to delay the displaying of `mainWebView` for 1000 milliseconds.
+ displayWebViewHandler.postDelayed(displayWebViewRunnable, 1000);
+
//Stop the `SwipeToRefresh` indicator if it is running
swipeRefreshLayout.setRefreshing(false);
}
@@ -1079,6 +1116,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
thirdPartyCookiesEnabled = false;
domStorageEnabled = false;
saveFormDataEnabled = false;
+ nightMode = false;
// Initialize `webViewTitle`.
webViewTitle = getString(R.string.no_title);
@@ -1139,12 +1177,21 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
setDisplayWebpageImages();
// Reload the webpage if displaying of images has been disabled in `SettingsFragment`.
- if (reloadOnRestartBoolean) {
+ if (reloadOnRestart) {
// Reload `mainWebView`.
mainWebView.reload();
// Reset `reloadOnRestartBoolean`.
- reloadOnRestartBoolean = false;
+ reloadOnRestart = false;
+ }
+
+ // Load the URL on restart to apply changes to night mode.
+ if (loadUrlOnRestart) {
+ // Load the current `formattedUrlString`.
+ loadUrl(formattedUrlString);
+
+ // Reset `loadUrlOnRestart.
+ loadUrlOnRestart = false;
}
}
@@ -2619,10 +2666,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
// Get a handle for the shared preference. `this` references the current context.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- // Store the default font size and user agent information.
+ // Store the general preference information.
String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
+ nightMode = sharedPreferences.getBoolean("night_mode", false);
if (domainSettingsApplied) { // The url we are loading has custom domain settings.
// Get a cursor for the current host and move it to the first position.
@@ -2639,6 +2687,7 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
String userAgentString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
int fontSize = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
displayWebpageImagesInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+ int nightModeInt = currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
pinnedDomainSslCertificate = (currentHostDomainSettingsCursor.getInt(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE)) == 1);
pinnedDomainSslIssuedToCNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
pinnedDomainSslIssuedToONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
@@ -2647,6 +2696,22 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
pinnedDomainSslIssuedByONameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATION));
pinnedDomainSslIssuedByUNameString = currentHostDomainSettingsCursor.getString(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_BY_ORGANIZATIONAL_UNIT));
+ // Set `nightMode` according to `nightModeInt`. If `nightModeInt` is `DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT` the current setting from `sharedPreferences` will be used.
+ switch (nightModeInt) {
+ case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+ nightMode = true;
+ break;
+
+ case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+ nightMode = false;
+ break;
+ }
+
+ // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+ if (nightMode) {
+ javaScriptEnabled = true;
+ }
+
// Set the pinned SSL certificate start date to `null` if the saved date `long` is 0.
if (currentHostDomainSettingsCursor.getLong(currentHostDomainSettingsCursor.getColumnIndex(DomainsDatabaseHelper.SSL_START_DATE)) == 0) {
pinnedDomainSslStartDate = null;
@@ -2729,6 +2794,11 @@ public class MainWebViewActivity extends AppCompatActivity implements Navigation
domStorageEnabled = sharedPreferences.getBoolean("dom_storage_enabled", false);
saveFormDataEnabled = sharedPreferences.getBoolean("save_form_data_enabled", false);
+ // Set `javaScriptEnabled` to be `true` if `night_mode` is `true`.
+ if (nightMode) {
+ javaScriptEnabled = true;
+ }
+
// Apply the default settings.
mainWebView.getSettings().setJavaScriptEnabled(javaScriptEnabled);
cookieManager.setAcceptCookie(firstPartyCookiesEnabled);
diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
index 3889ef7f..31dd0278 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
+++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainSettingsFragment.java
@@ -87,15 +87,16 @@ public class DomainSettingsFragment extends Fragment {
// Get a handle for the shared preference.
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
- // Store the default user agent string values.
+ // Store the default settings.
final String defaultUserAgentString = sharedPreferences.getString("user_agent", "PrivacyBrowser/1.0");
final String defaultCustomUserAgentString = sharedPreferences.getString("custom_user_agent", "PrivacyBrowser/1.0");
String defaultFontSizeString = sharedPreferences.getString("default_font_size", "100");
- boolean defaultDisplayWebpageImagesBoolean = sharedPreferences.getBoolean("display_website_images", true);
+ final boolean defaultDisplayWebpageImagesBoolean = sharedPreferences.getBoolean("display_website_images", true);
+ final boolean defaultNightModeBoolean = sharedPreferences.getBoolean("night_mode", false);
// Get handles for the views in the fragment.
final EditText domainNameEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_name_edittext);
- Switch javaScriptEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_javascript_switch);
+ final Switch javaScriptEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_javascript_switch);
final ImageView javaScriptImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_javascript_imageview);
Switch firstPartyCookiesEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_first_party_cookies_switch);
final ImageView firstPartyCookiesImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_first_party_cookies_imageview);
@@ -106,14 +107,17 @@ public class DomainSettingsFragment extends Fragment {
final ImageView domStorageImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_dom_storage_imageview);
Switch formDataEnabledSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_form_data_switch);
final ImageView formDataImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_form_data_imageview);
- Spinner userAgentSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_user_agent_spinner);
+ final Spinner userAgentSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_user_agent_spinner);
final TextView userAgentTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_user_agent_textview);
final EditText customUserAgentEditText = (EditText) domainSettingsView.findViewById(R.id.domain_settings_custom_user_agent_edittext);
- Spinner fontSizeSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_font_size_spinner);
+ final Spinner fontSizeSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_font_size_spinner);
final TextView fontSizeTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_font_size_textview);
final ImageView displayWebpageImagesImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_imageview);
- Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_spinner);
+ final Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_spinner);
final TextView displayImagesTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_display_webpage_images_textview);
+ final ImageView nightModeImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_night_mode_imageview);
+ final Spinner nightModeSpinner = (Spinner) domainSettingsView.findViewById(R.id.domain_settings_night_mode_spinner);
+ final TextView nightModeTextView = (TextView) domainSettingsView.findViewById(R.id.domain_settings_night_mode_textview);
final ImageView pinnedSslCertificateImageView = (ImageView) domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_imageview);
Switch pinnedSslCertificateSwitch = (Switch) domainSettingsView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
final LinearLayout savedSslCertificateLinearLayout = (LinearLayout) domainSettingsView.findViewById(R.id.saved_ssl_certificate_linearlayout);
@@ -157,14 +161,15 @@ public class DomainSettingsFragment extends Fragment {
// Save the `Cursor` entries as variables.
String domainNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.DOMAIN_NAME));
- int javaScriptEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT));
+ final int javaScriptEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_JAVASCRIPT));
int firstPartyCookiesEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FIRST_PARTY_COOKIES));
int thirdPartyCookiesEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_THIRD_PARTY_COOKIES));
- int domStorageEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE));
+ final int domStorageEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_DOM_STORAGE));
int formDataEnabledInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.ENABLE_FORM_DATA));
final String currentUserAgentString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.USER_AGENT));
int fontSizeInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.FONT_SIZE));
int displayImagesInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.DISPLAY_IMAGES));
+ int nightModeInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.NIGHT_MODE));
int pinnedSslCertificateInt = domainCursor.getInt(domainCursor.getColumnIndex(DomainsDatabaseHelper.PINNED_SSL_CERTIFICATE));
final String savedSslCertificateIssuedToCNameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_COMMON_NAME));
String savedSslCertificateIssuedToONameString = domainCursor.getString(domainCursor.getColumnIndex(DomainsDatabaseHelper.SSL_ISSUED_TO_ORGANIZATION));
@@ -191,17 +196,20 @@ public class DomainSettingsFragment extends Fragment {
final ArrayAdapter userAgentEntryValuesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_user_agent_entry_values, R.layout.spinner_item);
ArrayAdapter fontSizeArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_font_size_entries, R.layout.spinner_item);
ArrayAdapter fontSizeEntryValuesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.domain_settings_font_size_entry_values, R.layout.spinner_item);
- final ArrayAdapter displayImagesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.display_website_images_array, R.layout.spinner_item);
+ final ArrayAdapter displayImagesArrayAdapter = ArrayAdapter.createFromResource(context, R.array.display_webpage_images_array, R.layout.spinner_item);
+ ArrayAdapter nightModeArrayAdapter = ArrayAdapter.createFromResource(context, R.array.night_mode_array, R.layout.spinner_item);
// Set the `DropDownViewResource` on the `Spinners`.
userAgentArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
fontSizeArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
displayImagesArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
+ nightModeArrayAdapter.setDropDownViewResource(R.layout.spinner_dropdown_item);
// Set the `ArrayAdapters` for the `Spinners`.
userAgentSpinner.setAdapter(userAgentArrayAdapter);
fontSizeSpinner.setAdapter(fontSizeArrayAdapter);
displayWebpageImagesSpinner.setAdapter(displayImagesArrayAdapter);
+ nightModeSpinner.setAdapter(nightModeArrayAdapter);
// Create a `SpannableStringBuilder` for each `TextView` that needs multiple colors of text.
SpannableStringBuilder savedSslCertificateIssuedToCNameStringBuilder = new SpannableStringBuilder(cNameLabel + savedSslCertificateIssuedToCNameString);
@@ -303,13 +311,28 @@ public class DomainSettingsFragment extends Fragment {
}
});
- // Set the JavaScript status.
+ // Create a `boolean` to track if night mode is enabled.
+ boolean nightModeEnabled = (nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((nightModeInt == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightModeBoolean);
+
+ // Disable the JavaScript `Switch` if night mode is enabled.
+ if (nightModeEnabled) {
+ javaScriptEnabledSwitch.setEnabled(false);
+ } else {
+ javaScriptEnabledSwitch.setEnabled(true);
+ }
+
+ // Set the JavaScript icon.
+ if ((javaScriptEnabledInt == 1) || nightModeEnabled) {
+ javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
+ } else {
+ javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
+ }
+
+ // Set the JavaScript `Switch` status.
if (javaScriptEnabledInt == 1) { // JavaScript is enabled.
javaScriptEnabledSwitch.setChecked(true);
- javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
} else { // JavaScript is disabled.
javaScriptEnabledSwitch.setChecked(false);
- javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
}
// Set the first-party cookies status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
@@ -369,7 +392,10 @@ public class DomainSettingsFragment extends Fragment {
}
// Only enable DOM storage if JavaScript is enabled.
- if (javaScriptEnabledInt == 1) { // JavaScript is enabled.
+ if ((javaScriptEnabledInt == 1) || nightModeEnabled) { // JavaScript is enabled.
+ // Enable the DOM storage `Switch`.
+ domStorageEnabledSwitch.setEnabled(true);
+
// Set the DOM storage status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
if (domStorageEnabledInt == 1) { // Both JavaScript and DOM storage are enabled.
domStorageEnabledSwitch.setChecked(true);
@@ -386,6 +412,9 @@ public class DomainSettingsFragment extends Fragment {
}
}
} else { // JavaScript is disabled.
+ // Disable the DOM storage `Switch`.
+ domStorageEnabledSwitch.setEnabled(false);
+
// Set the checked status of DOM storage.
if (domStorageEnabledInt == 1) { // DOM storage is enabled but JavaScript is disabled.
domStorageEnabledSwitch.setChecked(true);
@@ -393,9 +422,6 @@ public class DomainSettingsFragment extends Fragment {
domStorageEnabledSwitch.setChecked(false);
}
- // Disable `domStorageEnabledSwitch`.
- domStorageEnabledSwitch.setEnabled(false);
-
// Set the icon according to the theme.
if (MainWebViewActivity.darkTheme) {
domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_dark));
@@ -482,6 +508,15 @@ public class DomainSettingsFragment extends Fragment {
}
}
+ // Open the user agent spinner when the `TextView` is clicked.
+ userAgentTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Open the user agent spinner.
+ userAgentSpinner.performClick();
+ }
+ });
+
// Set the selected font size.
int fontSizeArrayPosition = fontSizeEntryValuesArrayAdapter.getPosition(String.valueOf(fontSizeInt));
fontSizeSpinner.setSelection(fontSizeArrayPosition);
@@ -497,27 +532,36 @@ public class DomainSettingsFragment extends Fragment {
fontSizeTextView.setVisibility(View.GONE);
}
- // Set the selected display website images mode.
+ // Open the font size spinner when the `TextView` is clicked.
+ fontSizeTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Open the user agent spinner.
+ fontSizeSpinner.performClick();
+ }
+ });
+
+ // Display the website images mode in the spinner.
displayWebpageImagesSpinner.setSelection(displayImagesInt);
// Set the default display images text.
if (defaultDisplayWebpageImagesBoolean) {
- displayImagesTextView.setText(displayImagesArrayAdapter.getItem(1));
+ displayImagesTextView.setText(displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_ENABLED));
} else {
- displayImagesTextView.setText(displayImagesArrayAdapter.getItem(2));
+ displayImagesTextView.setText(displayImagesArrayAdapter.getItem(DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_DISABLED));
}
- // Set the display website images icon and `TextView` settings.
+ // Set the display website images icon and `TextView` settings. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
switch (displayImagesInt) {
case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
- if (MainWebViewActivity.displayWebpageImagesBoolean) {
+ if (defaultDisplayWebpageImagesBoolean) { // Display webpage images enabled by default.
// Set the icon according to the theme.
if (MainWebViewActivity.darkTheme) {
displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
} else {
displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_light));
}
- } else {
+ } else { // Display webpage images disabled by default.
// Set the icon according to the theme.
if (MainWebViewActivity.darkTheme) {
displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_disabled_dark));
@@ -554,9 +598,85 @@ public class DomainSettingsFragment extends Fragment {
displayImagesTextView.setVisibility(View.GONE);
break;
}
+
+ // Open the display images spinner when the `TextView` is clicked.
+ displayImagesTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Open the user agent spinner.
+ displayWebpageImagesSpinner.performClick();
+ }
+ });
+
+ // Display the night mode in the spinner.
+ nightModeSpinner.setSelection(nightModeInt);
+
+ // Set the default night mode text.
+ if (defaultNightModeBoolean) {
+ nightModeTextView.setText(nightModeArrayAdapter.getItem(DomainsDatabaseHelper.NIGHT_MODE_ENABLED));
+ } else {
+ nightModeTextView.setText(nightModeArrayAdapter.getItem(DomainsDatabaseHelper.NIGHT_MODE_DISABLED));
+ }
+
+ // Set the night mode icon and `TextView` settings. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
+ switch (displayImagesInt) {
+ case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT:
+ if (defaultNightModeBoolean) { // Night mode enabled by default.
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_light));
+ }
+ } else { // Night mode disabled by default.
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_light));
+ }
+ }
+
+ // Show `nightModeTextView`.
+ nightModeTextView.setVisibility(View.VISIBLE);
+ break;
+
+ case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_light));
+ }
+
+ // Hide `nightModeTextView`.
+ nightModeTextView.setVisibility(View.GONE);
+ break;
+
+ case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_light));
+ }
+
+ // Hide `nightModeTextView`.
+ nightModeTextView.setVisibility(View.GONE);
+ break;
+ }
+
+ // Open the night mode spinner when the `TextView` is clicked.
+ nightModeTextView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Open the user agent spinner.
+ nightModeSpinner.performClick();
+ }
+ });
// Set the pinned SSL certificate icon.
- if (pinnedSslCertificateInt == 1) { // Pinned SSL certificate is enabled.
+ if (pinnedSslCertificateInt == 1) { // Pinned SSL certificate is enabled. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
// Check the switch.
pinnedSslCertificateSwitch.setChecked(true);
@@ -962,7 +1082,7 @@ public class DomainSettingsFragment extends Fragment {
// Update the icon and the visibility of `displayImagesTextView`.
switch (position) {
case DomainsDatabaseHelper.DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT:
- if (MainWebViewActivity.displayWebpageImagesBoolean) {
+ if (defaultDisplayWebpageImagesBoolean) {
// Set the icon according to the theme.
if (MainWebViewActivity.darkTheme) {
displayWebpageImagesImageView.setImageDrawable(resources.getDrawable(R.drawable.images_enabled_dark));
@@ -1013,6 +1133,121 @@ public class DomainSettingsFragment extends Fragment {
// Do nothing.
}
});
+
+ // Set the `nightModeSpinner` `onItemSelectedListener()`.
+ nightModeSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ // Update the icon and the visibility of `nightModeTextView`. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
+ switch (position) {
+ case DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT:
+ if (defaultNightModeBoolean) { // Night mode enabled by default.
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_light));
+ }
+ } else { // Night mode disabled by default.
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_light));
+ }
+ }
+
+ // Show `nightModeTextView`.
+ nightModeTextView.setVisibility(View.VISIBLE);
+ break;
+
+ case DomainsDatabaseHelper.NIGHT_MODE_ENABLED:
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_enabled_light));
+ }
+
+ // Hide `nightModeTextView`.
+ nightModeTextView.setVisibility(View.GONE);
+ break;
+
+ case DomainsDatabaseHelper.NIGHT_MODE_DISABLED:
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_dark));
+ } else {
+ nightModeImageView.setImageDrawable(resources.getDrawable(R.drawable.night_mode_disabled_light));
+ }
+
+ // Hide `nightModeTextView`.
+ nightModeTextView.setVisibility(View.GONE);
+ break;
+ }
+
+ // Create a `boolean` to store the current night mode setting.
+ boolean currentNightModeEnabled = (position == DomainsDatabaseHelper.NIGHT_MODE_ENABLED) || ((position == DomainsDatabaseHelper.NIGHT_MODE_SYSTEM_DEFAULT) && defaultNightModeBoolean);
+
+ // Disable the JavaScript `Switch` if night mode is enabled.
+ if (currentNightModeEnabled) {
+ javaScriptEnabledSwitch.setEnabled(false);
+ } else {
+ javaScriptEnabledSwitch.setEnabled(true);
+ }
+
+ // Update the JavaScript icon.
+ if ((javaScriptEnabledInt == 1) || currentNightModeEnabled) {
+ javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.javascript_enabled));
+ } else {
+ javaScriptImageView.setImageDrawable(resources.getDrawable(R.drawable.privacy_mode));
+ }
+
+ // Update the DOM storage status.
+ if ((javaScriptEnabledInt == 1) || currentNightModeEnabled) { // JavaScript is enabled.
+ // Enable the DOM storage `Switch`.
+ domStorageEnabledSwitch.setEnabled(true);
+
+ // Set the DOM storage status. Once minimum API >= 21 we can use a selector as the tint mode instead of specifying different icons.
+ if (domStorageEnabledInt == 1) { // Both JavaScript and DOM storage are enabled.
+ domStorageEnabledSwitch.setChecked(true);
+ domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_enabled));
+ } else { // JavaScript is enabled but DOM storage is disabled.
+ // Set the DOM storage switch to off.
+ domStorageEnabledSwitch.setChecked(false);
+
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_dark));
+ } else {
+ domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_disabled_light));
+ }
+ }
+ } else { // JavaScript is disabled.
+ // Disable the DOM storage `Switch`.
+ domStorageEnabledSwitch.setEnabled(false);
+
+ // Set the checked status of DOM storage.
+ if (domStorageEnabledInt == 1) { // DOM storage is enabled but JavaScript is disabled.
+ domStorageEnabledSwitch.setChecked(true);
+ } else { // Both JavaScript and DOM storage are disabled.
+ domStorageEnabledSwitch.setChecked(false);
+ }
+
+ // Set the icon according to the theme.
+ if (MainWebViewActivity.darkTheme) {
+ domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_dark));
+ } else {
+ domStorageImageView.setImageDrawable(resources.getDrawable(R.drawable.dom_storage_ghosted_light));
+ }
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ // Do nothing.
+ }
+ });
// Set the `pinnedSSLCertificateSwitch` `onCheckedChangeListener()`.
pinnedSslCertificateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java
index 34349d79..172a6438 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java
+++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/DomainsListFragment.java
@@ -80,6 +80,7 @@ public class DomainsListFragment extends Fragment {
EditText customUserAgentEditText = (EditText) domainSettingsFragmentView.findViewById(R.id.domain_settings_custom_user_agent_edittext);
Spinner fontSizeSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_font_size_spinner);
Spinner displayWebpageImagesSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_display_webpage_images_spinner);
+ Spinner nightModeSpinner = (Spinner) domainSettingsFragmentView.findViewById(R.id.domain_settings_night_mode_spinner);
Switch pinnedSslCertificateSwitch = (Switch) domainSettingsFragmentView.findViewById(R.id.domain_settings_pinned_ssl_certificate_switch);
RadioButton savedSslCertificateRadioButton = (RadioButton) domainSettingsFragmentView.findViewById(R.id.saved_ssl_certificate_radiobutton);
RadioButton currentWebsiteCertificateRadioButton = (RadioButton) domainSettingsFragmentView.findViewById(R.id.current_website_certificate_radiobutton);
@@ -94,6 +95,7 @@ public class DomainsListFragment extends Fragment {
int userAgentPositionInt = userAgentSpinner.getSelectedItemPosition();
int fontSizePositionInt = fontSizeSpinner.getSelectedItemPosition();
int displayWebpageImagesInt = displayWebpageImagesSpinner.getSelectedItemPosition();
+ int nightModeInt = nightModeSpinner.getSelectedItemPosition();
boolean pinnedSslCertificate = pinnedSslCertificateSwitch.isChecked();
// Get the data for the `Spinners` from the entry values string arrays.
@@ -110,7 +112,7 @@ public class DomainsListFragment extends Fragment {
if (savedSslCertificateRadioButton.isChecked()) { // The current certificate is being used.
// Update the database except for the certificate.
domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
- formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate);
+ formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt, pinnedSslCertificate);
} else if (currentWebsiteCertificateRadioButton.isChecked()) { // The certificate is being updated with the current website certificate.
// Get the current website SSL certificate.
SslCertificate currentWebsiteSslCertificate = MainWebViewActivity.sslCertificate;
@@ -127,12 +129,12 @@ public class DomainsListFragment extends Fragment {
// Update the database.
domainsDatabaseHelper.updateDomainWithCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
- formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization,
+ formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt, pinnedSslCertificate, issuedToCommonName, issuedToOrganization, issuedToOrganizationalUnit, issuedByCommonName, issuedByOrganization,
issuedByOrganizationalUnit, startDateLong, endDateLong);
} else { // No certificate is selected.
// Update the database, with PINNED_SSL_CERTIFICATE set to false.
domainsDatabaseHelper.updateDomainExceptCertificate(DomainsActivity.currentDomainDatabaseId, domainNameString, javaScriptEnabledBoolean, firstPartyCookiesEnabledBoolean, thirdPartyCookiesEnabledBoolean, domStorageEnabledEnabledBoolean,
- formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, false);
+ formDataEnabledBoolean, userAgentString, fontSizeInt, displayWebpageImagesInt, nightModeInt, false);
}
}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java
index 6ce6cdcb..cbacb385 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java
+++ b/app/src/main/java/com/stoutner/privacybrowser/fragments/SettingsFragment.java
@@ -75,30 +75,34 @@ public class SettingsFragment extends PreferenceFragment {
final Preference swipeToRefreshPreference = findPreference("swipe_to_refresh");
final Preference displayAdditionalAppBarIconsPreference = findPreference("display_additional_app_bar_icons");
final Preference darkThemePreference = findPreference("dark_theme");
+ final Preference nightModePreference = findPreference("night_mode");
final Preference displayWebpageImagesPreference = findPreference("display_webpage_images");
// Set dependencies.
- domStoragePreference.setDependency("javascript_enabled");
torHomepagePreference.setDependency("proxy_through_orbot");
torSearchPreference.setDependency("proxy_through_orbot");
hideSystemBarsPreference.setDependency("full_screen_browsing_mode");
- // Get strings from the preferences.
+ // Get Strings from the preferences.
String torSearchString = savedPreferences.getString("tor_search", "https://3g2upl4pq6kufc4m.onion/html/?q=");
String searchString = savedPreferences.getString("search", "https://duckduckgo.com/html/?q=");
// Get booleans from the preferences.
- boolean javaScriptEnabledBoolean = savedPreferences.getBoolean("javascript_enabled", false);
+ final boolean javaScriptEnabledBoolean = savedPreferences.getBoolean("javascript_enabled", false);
boolean firstPartyCookiesEnabledBoolean = savedPreferences.getBoolean("first_party_cookies_enabled", false);
boolean thirdPartyCookiesEnabledBoolean = savedPreferences.getBoolean("third_party_cookies_enabled", false);
boolean proxyThroughOrbotBoolean = savedPreferences.getBoolean("proxy_through_orbot", false);
boolean fullScreenBrowsingModeBoolean = savedPreferences.getBoolean("full_screen_browsing_mode", false);
boolean hideSystemBarsBoolean = savedPreferences.getBoolean("hide_system_bars", false);
boolean clearEverythingBoolean = savedPreferences.getBoolean("clear_everything", true);
+ final boolean nightModeBoolean = savedPreferences.getBoolean("night_mode", false);
// Only enable `thirdPartyCookiesPreference` if `firstPartyCookiesEnabledBoolean` is `true` and API >= 21.
thirdPartyCookiesPreference.setEnabled(firstPartyCookiesEnabledBoolean && (Build.VERSION.SDK_INT >= 21));
+ // Only enable `domStoragePreference` if either `javaScriptEnabledBoolean` or `nightModeBoolean` is true.
+ domStoragePreference.setEnabled(javaScriptEnabledBoolean || nightModeBoolean);
+
// We need to inflated a `WebView` to get the default user agent.
LayoutInflater inflater = getActivity().getLayoutInflater();
// `@SuppressLint("InflateParams")` removes the warning about using `null` as the `ViewGroup`, which in this case makes sense because we don't want to display `bare_webview` on the screen. `false` does not attach the view to the root.
@@ -177,9 +181,11 @@ public class SettingsFragment extends PreferenceFragment {
// Set the default font size as the summary text for the `Default Font Size` preference when the preference screen is loaded. The default is `100`.
defaultFontSizePreference.setSummary(savedPreferences.getString("default_font_size", "100") + "%%");
+ // Disable `javaScriptPreference` if `nightModeBoolean` is true. JavaScript will be enabled for all web pages.
+ javaScriptPreference.setEnabled(!nightModeBoolean);
// Set the `javaScriptPreference` icon.
- if (javaScriptEnabledBoolean) {
+ if (javaScriptEnabledBoolean || nightModeBoolean) {
javaScriptPreference.setIcon(R.drawable.javascript_enabled);
} else {
javaScriptPreference.setIcon(R.drawable.privacy_mode);
@@ -216,17 +222,17 @@ public class SettingsFragment extends PreferenceFragment {
}
// Set the `domStoragePreference` icon.
- if (javaScriptEnabledBoolean) {
- if (savedPreferences.getBoolean("dom_storage_enabled", false)) {
+ if (javaScriptEnabledBoolean || nightModeBoolean) { // The preference is enabled.
+ if (savedPreferences.getBoolean("dom_storage_enabled", false)) { // DOM storage is enabled.
domStoragePreference.setIcon(R.drawable.dom_storage_enabled);
- } else {
+ } else { // DOM storage is disabled.
if (MainWebViewActivity.darkTheme) {
domStoragePreference.setIcon(R.drawable.dom_storage_disabled_dark);
} else {
domStoragePreference.setIcon(R.drawable.dom_storage_disabled_light);
}
}
- } else {
+ } else { // The preference is disabled. The icon should be ghosted.
if (MainWebViewActivity.darkTheme) {
domStoragePreference.setIcon(R.drawable.dom_storage_ghosted_dark);
} else {
@@ -506,6 +512,21 @@ public class SettingsFragment extends PreferenceFragment {
darkThemePreference.setIcon(R.drawable.theme_light);
}
+ // Set the `nightModePreference` icon.
+ if (nightModeBoolean) {
+ if (MainWebViewActivity.darkTheme) {
+ nightModePreference.setIcon(R.drawable.night_mode_enabled_dark);
+ } else {
+ nightModePreference.setIcon(R.drawable.night_mode_enabled_light);
+ }
+ } else {
+ if (MainWebViewActivity.darkTheme) {
+ nightModePreference.setIcon(R.drawable.night_mode_disabled_dark);
+ } else {
+ nightModePreference.setIcon(R.drawable.night_mode_disabled_light);
+ }
+ }
+
// Set the `displayWebpageImagesPreference` icon.
if (savedPreferences.getBoolean("display_webpage_images", true)) {
if (MainWebViewActivity.darkTheme) {
@@ -531,12 +552,15 @@ public class SettingsFragment extends PreferenceFragment {
switch (key) {
case "javascript_enabled":
- // Update the icons.
- if (sharedPreferences.getBoolean("javascript_enabled", false)) {
- // Update the icon for `javascript_enabled`.
+ // Update the icons and the DOM storage preference status.
+ if (sharedPreferences.getBoolean("javascript_enabled", false)) { // The JavaScript preference is enabled.
+ // Update the icon for the JavaScript preference.
javaScriptPreference.setIcon(R.drawable.javascript_enabled);
- // Update the icon for `dom_storage_enabled`.
+ // Update the status of the DOM storage preference.
+ domStoragePreference.setEnabled(true);
+
+ // Update the icon for the DOM storage preference.
if (sharedPreferences.getBoolean("dom_storage_enabled", false)) {
domStoragePreference.setIcon(R.drawable.dom_storage_enabled);
} else {
@@ -546,11 +570,14 @@ public class SettingsFragment extends PreferenceFragment {
domStoragePreference.setIcon(R.drawable.dom_storage_disabled_light);
}
}
- } else { // `javascript_enabled` is `false`.
- // Update the icon for `javascript_enabled`.
+ } else { // The JavaScript preference is disabled.
+ // Update the icon for the JavaScript preference.
javaScriptPreference.setIcon(R.drawable.privacy_mode);
- // Set the icon for `dom_storage_disabled` to be ghosted.
+ // Update the status of the DOM storage preference.
+ domStoragePreference.setEnabled(false);
+
+ // Set the icon for DOM storage preference to be ghosted.
if (MainWebViewActivity.darkTheme) {
domStoragePreference.setIcon(R.drawable.dom_storage_ghosted_dark);
} else {
@@ -1188,6 +1215,60 @@ public class SettingsFragment extends PreferenceFragment {
startActivity(intent);
break;
+ case "night_mode":
+ // Set the URL to be reloaded on restart to apply the new night mode setting.
+ MainWebViewActivity.loadUrlOnRestart = true;
+
+ // Store the current night mode status.
+ boolean currentNightModeBoolean = sharedPreferences.getBoolean("night_mode", false);
+ boolean currentJavaScriptBoolean = sharedPreferences.getBoolean("javascript_enabled", false);
+
+ // Update the icon.
+ if (currentNightModeBoolean) {
+ if (MainWebViewActivity.darkTheme) {
+ nightModePreference.setIcon(R.drawable.night_mode_enabled_dark);
+ } else {
+ nightModePreference.setIcon(R.drawable.night_mode_enabled_light);
+ }
+ } else {
+ if (MainWebViewActivity.darkTheme) {
+ nightModePreference.setIcon(R.drawable.night_mode_disabled_dark);
+ } else {
+ nightModePreference.setIcon(R.drawable.night_mode_disabled_light);
+ }
+ }
+
+ // Update the status of `javaScriptPreference` and `domStoragePreference`.
+ javaScriptPreference.setEnabled(!currentNightModeBoolean);
+ domStoragePreference.setEnabled(currentNightModeBoolean || currentJavaScriptBoolean);
+
+ // Update the `javaScriptPreference` icon.
+ if (currentNightModeBoolean || currentJavaScriptBoolean) {
+ javaScriptPreference.setIcon(R.drawable.javascript_enabled);
+ } else {
+ javaScriptPreference.setIcon(R.drawable.privacy_mode);
+ }
+
+ // Update the `domStoragePreference` icon.
+ if (currentNightModeBoolean || currentJavaScriptBoolean) { // The preference is enabled.
+ if (sharedPreferences.getBoolean("dom_storage_enabled", false)) { // DOM storage is enabled.
+ domStoragePreference.setIcon(R.drawable.dom_storage_enabled);
+ } else { // DOM storage is disabled.
+ if (MainWebViewActivity.darkTheme) {
+ domStoragePreference.setIcon(R.drawable.dom_storage_disabled_dark);
+ } else {
+ domStoragePreference.setIcon(R.drawable.dom_storage_disabled_light);
+ }
+ }
+ } else { // The preference is disabled. The icon should be ghosted.
+ if (MainWebViewActivity.darkTheme) {
+ domStoragePreference.setIcon(R.drawable.dom_storage_ghosted_dark);
+ } else {
+ domStoragePreference.setIcon(R.drawable.dom_storage_ghosted_light);
+ }
+ }
+ break;
+
case "display_webpage_images":
if (sharedPreferences.getBoolean("display_webpage_images", true)) {
// Update the icon.
@@ -1198,7 +1279,7 @@ public class SettingsFragment extends PreferenceFragment {
}
// `mainWebView` does not need to be reloaded because unloaded images will load automatically.
- MainWebViewActivity.reloadOnRestartBoolean = false;
+ MainWebViewActivity.reloadOnRestart = false;
} else {
// Update the icon.
if (MainWebViewActivity.darkTheme) {
@@ -1208,7 +1289,7 @@ public class SettingsFragment extends PreferenceFragment {
}
// Set `mainWebView` to reload on restart to remove the current images.
- MainWebViewActivity.reloadOnRestartBoolean = true;
+ MainWebViewActivity.reloadOnRestart = true;
}
break;
}
diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java b/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
index 3100d694..0331a01f 100644
--- a/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
+++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/DomainsDatabaseHelper.java
@@ -26,7 +26,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DomainsDatabaseHelper extends SQLiteOpenHelper {
- private static final int SCHEMA_VERSION = 3;
+ private static final int SCHEMA_VERSION = 4;
private static final String DOMAINS_DATABASE = "domains.db";
private static final String DOMAINS_TABLE = "domains";
@@ -40,6 +40,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
public static final String USER_AGENT = "useragent";
public static final String FONT_SIZE = "fontsize";
public static final String DISPLAY_IMAGES = "displayimages";
+ public static final String NIGHT_MODE = "nightmode";
public static final String PINNED_SSL_CERTIFICATE = "pinnedsslcertificate";
public static final String SSL_ISSUED_TO_COMMON_NAME = "sslissuedtocommonname";
public static final String SSL_ISSUED_TO_ORGANIZATION = "sslissuedtoorganization";
@@ -50,10 +51,16 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
public static final String SSL_START_DATE = "sslstartdate";
public static final String SSL_END_DATE = "sslenddate";
+ // Display webpage images constants.
public static final int DISPLAY_WEBPAGE_IMAGES_SYSTEM_DEFAULT = 0;
public static final int DISPLAY_WEBPAGE_IMAGES_ENABLED = 1;
public static final int DISPLAY_WEBPAGE_IMAGES_DISABLED = 2;
+ // Night mode constants.
+ public static final int NIGHT_MODE_SYSTEM_DEFAULT = 0;
+ public static final int NIGHT_MODE_ENABLED = 1;
+ public static final int NIGHT_MODE_DISABLED = 2;
+
// Initialize the database. The lint warnings for the unused parameters are suppressed.
public DomainsDatabaseHelper(Context context, @SuppressWarnings("UnusedParameters") String name, SQLiteDatabase.CursorFactory cursorFactory, @SuppressWarnings("UnusedParameters") int version) {
super(context, DOMAINS_DATABASE, cursorFactory, SCHEMA_VERSION);
@@ -73,6 +80,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
USER_AGENT + " TEXT, " +
FONT_SIZE + " INTEGER, " +
DISPLAY_IMAGES + " INTEGER, " +
+ NIGHT_MODE + " INTEGER, " +
PINNED_SSL_CERTIFICATE + " BOOLEAN, " +
SSL_ISSUED_TO_COMMON_NAME + " TEXT, " +
SSL_ISSUED_TO_ORGANIZATION + " TEXT, " +
@@ -108,6 +116,11 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_ISSUED_BY_ORGANIZATIONAL_UNIT + " TEXT");
domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_START_DATE + " INTEGER");
domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + SSL_END_DATE + " INTEGER");
+
+ // Upgrade from `SCHEMA_VERSION` 3.
+ case 3:
+ // Add the `NIGHT_MODE` column.
+ domainsDatabase.execSQL("ALTER TABLE " + DOMAINS_TABLE + " ADD COLUMN " + NIGHT_MODE + " INTEGER");
}
}
@@ -166,7 +179,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
// Store the domain data in a `ContentValues`.
ContentValues domainContentValues = new ContentValues();
- // Create entries for each field in the database. The ID is created automatically.
+ // Create entries for the database fields. The ID is created automatically. The pinned SSL certificate information is not created unless added by the user.
domainContentValues.put(DOMAIN_NAME, domainName);
domainContentValues.put(ENABLE_JAVASCRIPT, false);
domainContentValues.put(ENABLE_FIRST_PARTY_COOKIES, false);
@@ -176,6 +189,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
domainContentValues.put(USER_AGENT, "System default user agent");
domainContentValues.put(FONT_SIZE, 0);
domainContentValues.put(DISPLAY_IMAGES, 0);
+ domainContentValues.put(NIGHT_MODE, 0);
// Get a writable database handle.
SQLiteDatabase domainsDatabase = this.getWritableDatabase();
@@ -186,11 +200,12 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
// Close the database handle.
domainsDatabase.close();
+ // Return the new domain database ID.
return newDomainDatabaseId;
}
public void updateDomainExceptCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize,
- int displayImages, boolean pinnedSslCertificate) {
+ int displayImages, int nightMode, boolean pinnedSslCertificate) {
// Store the domain data in a `ContentValues`.
ContentValues domainContentValues = new ContentValues();
@@ -204,6 +219,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
domainContentValues.put(USER_AGENT, userAgent);
domainContentValues.put(FONT_SIZE, fontSize);
domainContentValues.put(DISPLAY_IMAGES, displayImages);
+ domainContentValues.put(NIGHT_MODE, nightMode);
domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate);
// Get a writable database handle.
@@ -217,7 +233,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
}
public void updateDomainWithCertificate(int databaseId, String domainName, boolean javaScriptEnabled, boolean firstPartyCookiesEnabled, boolean thirdPartyCookiesEnabled, boolean domStorageEnabled, boolean formDataEnabled, String userAgent, int fontSize,
- int displayImages, boolean pinnedSslCertificate, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName, String sslIssuedByOrganization,
+ int displayImages, int nightMode, boolean pinnedSslCertificate, String sslIssuedToCommonName, String sslIssuedToOrganization, String sslIssuedToOrganizationalUnit, String sslIssuedByCommonName, String sslIssuedByOrganization,
String sslIssuedByOrganizationalUnit, long sslStartDate, long sslEndDate) {
// Store the domain data in a `ContentValues`.
ContentValues domainContentValues = new ContentValues();
@@ -232,6 +248,7 @@ public class DomainsDatabaseHelper extends SQLiteOpenHelper {
domainContentValues.put(USER_AGENT, userAgent);
domainContentValues.put(FONT_SIZE, fontSize);
domainContentValues.put(DISPLAY_IMAGES, displayImages);
+ domainContentValues.put(NIGHT_MODE, nightMode);
domainContentValues.put(PINNED_SSL_CERTIFICATE, pinnedSslCertificate);
domainContentValues.put(SSL_ISSUED_TO_COMMON_NAME, sslIssuedToCommonName);
domainContentValues.put(SSL_ISSUED_TO_ORGANIZATION, sslIssuedToOrganization);
diff --git a/app/src/main/res/drawable/add_dark.xml b/app/src/main/res/drawable/add_dark.xml
index 1da26b74..da95078e 100644
--- a/app/src/main/res/drawable/add_dark.xml
+++ b/app/src/main/res/drawable/add_dark.xml
@@ -6,7 +6,7 @@
android:viewportHeight="24.0"
android:viewportWidth="24.0" >
-
+
diff --git a/app/src/main/res/drawable/add_light.xml b/app/src/main/res/drawable/add_light.xml
index a64e797b..3719987b 100644
--- a/app/src/main/res/drawable/add_light.xml
+++ b/app/src/main/res/drawable/add_light.xml
@@ -6,7 +6,7 @@
android:viewportHeight="24.0"
android:viewportWidth="24.0" >
-
+
diff --git a/app/src/main/res/drawable/night_mode_disabled_dark.xml b/app/src/main/res/drawable/night_mode_disabled_dark.xml
new file mode 100644
index 00000000..175e81bb
--- /dev/null
+++ b/app/src/main/res/drawable/night_mode_disabled_dark.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/night_mode_disabled_light.xml b/app/src/main/res/drawable/night_mode_disabled_light.xml
new file mode 100644
index 00000000..81af0370
--- /dev/null
+++ b/app/src/main/res/drawable/night_mode_disabled_light.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/night_mode_enabled_dark.xml b/app/src/main/res/drawable/night_mode_enabled_dark.xml
new file mode 100644
index 00000000..b71a9838
--- /dev/null
+++ b/app/src/main/res/drawable/night_mode_enabled_dark.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/night_mode_enabled_light.xml b/app/src/main/res/drawable/night_mode_enabled_light.xml
new file mode 100644
index 00000000..2d13d31b
--- /dev/null
+++ b/app/src/main/res/drawable/night_mode_enabled_light.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/privacy_mode.xml b/app/src/main/res/drawable/privacy_mode.xml
index d5879ce9..8d12208e 100644
--- a/app/src/main/res/drawable/privacy_mode.xml
+++ b/app/src/main/res/drawable/privacy_mode.xml
@@ -8,21 +8,21 @@
android:viewportHeight="256.0"
android:viewportWidth="256.0" >
-
+
-
+
-
+
-
+
diff --git a/app/src/main/res/drawable/refresh_disabled_light.xml b/app/src/main/res/drawable/refresh_disabled_light.xml
index 2758345b..37eb36f6 100644
--- a/app/src/main/res/drawable/refresh_disabled_light.xml
+++ b/app/src/main/res/drawable/refresh_disabled_light.xml
@@ -11,7 +11,7 @@
android:viewportWidth="24.0"
tools:ignore="VectorRaster" >
-
+
diff --git a/app/src/main/res/drawable/refresh_enabled_dark.xml b/app/src/main/res/drawable/refresh_enabled_dark.xml
index 6c17502f..23fd9c6b 100644
--- a/app/src/main/res/drawable/refresh_enabled_dark.xml
+++ b/app/src/main/res/drawable/refresh_enabled_dark.xml
@@ -11,7 +11,7 @@
android:viewportWidth="24.0"
tools:ignore="VectorRaster" >
-
+
diff --git a/app/src/main/res/drawable/refresh_enabled_light.xml b/app/src/main/res/drawable/refresh_enabled_light.xml
index f2e0916b..3dd3ecab 100644
--- a/app/src/main/res/drawable/refresh_enabled_light.xml
+++ b/app/src/main/res/drawable/refresh_enabled_light.xml
@@ -11,7 +11,7 @@
android:viewportWidth="24.0"
tools:ignore="VectorRaster" >
-
+
diff --git a/app/src/main/res/layout/domain_settings_fragment.xml b/app/src/main/res/layout/domain_settings_fragment.xml
index b27a1c41..73c8e5da 100644
--- a/app/src/main/res/layout/domain_settings_fragment.xml
+++ b/app/src/main/res/layout/domain_settings_fragment.xml
@@ -341,6 +341,43 @@
android:textSize="13sp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
. -->
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 4111fc6a..d99b3371 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -204,7 +204,7 @@
Nombre de dominio
Dominio borrado
*. puede ser añadido a un dominio para incluir todos los subdominios (p.ej. *.stoutner.com)
-
+
- Por defecto del sistema
- Imágenes habilitadas
- Imágenes deshabilitadas
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 2dcf2baf..e73cf7a2 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -206,7 +206,7 @@
Nome del Dominio
Dominio Eliminato
è possibile anteporre *. a un dominio per includere tutti i sottodomini (es. *.stoutner.com)
-
+
- Impostazioni di default
- Abilita Immagini
- Disabilita Immagini
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c15727c5..62e75a04 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -209,11 +209,16 @@
Domain name
Domain deleted
*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)
-
+
- System default
- Images enabled
- Images disabled
+
+ - System default
+ - Night mode enabled
+ - Night mode disabled
+
Pinned SSL certificate
Saved SSL certificate
Current website SSL certificate
@@ -425,6 +430,8 @@
Display icons for toggling cookies, DOM storage, and form data in the app bar if there is room.
Dark theme
Changing the theme will restart Privacy Browser.
+ Night mode
+ Enabling night mode will also enable JavaScript for all web pages.
Display webpage images
Disable to conserve bandwidth.
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index fbfb633e..160536df 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -231,6 +231,12 @@
android:summary="@string/dark_theme_summary"
android:defaultValue="false" />
+
+
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