From 0d19911e80745e1bfce345f777822fc83bab7551 Mon Sep 17 00:00:00 2001
From: abaye <8140hp@gmail.com>
Date: Wed, 4 Feb 2026 03:35:17 +0200
Subject: [PATCH] feat: add environment variable support and enhance app
functionality
---
.gitignore | 1 +
.idea/AndroidProjectSystem.xml | 6 ++
.idea/compiler.xml | 2 +-
.idea/deploymentTargetSelector.xml | 10 +++
.idea/gradle.xml | 2 +-
.idea/markdown.xml | 8 ++
.idea/misc.xml | 3 +-
.idea/runConfigurations.xml | 17 ++++
.idea/studiobot.xml | 6 ++
README.md | 48 +++++++++--
app/build.gradle | 82 +++++++++++++++----
app/src/main/AndroidManifest.xml | 1 +
.../webview/myapplication/MainActivity.java | 23 ++++++
13 files changed, 185 insertions(+), 24 deletions(-)
create mode 100644 .idea/AndroidProjectSystem.xml
create mode 100644 .idea/deploymentTargetSelector.xml
create mode 100644 .idea/markdown.xml
create mode 100644 .idea/runConfigurations.xml
create mode 100644 .idea/studiobot.xml
diff --git a/.gitignore b/.gitignore
index 698a241..5918aa6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@
.cxx
local.properties
*.jks
+.env
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index b589d56..b86273d 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 0897082..639c779 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,6 +4,7 @@
-
diff --git a/.idea/markdown.xml b/.idea/markdown.xml
new file mode 100644
index 0000000..c61ea33
--- /dev/null
+++ b/.idea/markdown.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 55c0ec2..74dd639 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,7 @@
-
+
+
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml
new file mode 100644
index 0000000..539e3b8
--- /dev/null
+++ b/.idea/studiobot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 15e3d19..d79de11 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,50 @@
-# Android Restricted WebView
+# Single Site Android Browser
-The Android Restricted WebView is an Android application template that allows you to display a specific website using a WebView while restricting access to other sites. If the application attempts to load a URL other than the allowed site, an error message "This URL is not allowed" will be displayed.
+הפרויקט מאפשר לך ליצור אפליקציית אנדרואיד עבור כל אתר אינטרנט, עם אפשרות להגביל את הגישה לאתרים באמצעות רשימה לבנה.
-## Configuration
-In the file [`app/build.gradle`](./app/build.gradle) (editing the file or using env variables).
+בנוי כהרחבה למתקדמים לפרויקט:
+https://github.com/ShlomoCode/AndroidRestrictedWebView
-recompile and redeploy the application for the change to take effect.
+### תכונות
-## License
+- מאפשר רשימה לבנה לא מוגבלת.
+- מאפשר לציין כתובת ייחודית שתהיה נקודת ההתחלה (אם לא צוינה, יתחיל בכתובת הראשונה ברשימה הלבנה).
+- טיפול ב-URI מותאם של `tel`, `mailto` ו-`whatsapp`.
+- כפיית מצב תצוגה.
+- חסימת מדיה.
+- מצב no ssl.
+- הסתרת הבאנר של נטפרי.
+- טעינה מחדש של הדף בהחלקה למטה בשליש העליון של המסך.
+
+## Configuration - קונפיגורציה
+
+ניתן לערוך את ההגדרות ישירות ב-[`app/build.gradle`](./app/build.gradle) (או לטעון באמצעות קובץ `env` שממוקם בתיקיית `app`).
+
+לאחר ביצוע השינויים יש לבצע הידור מחדש של הפרויקט כדי לסנכרן את השינויים.
+
+### אפשרויות בקובץ `env`:
+
+```env
+ALLOWED_DOMAINS=example.com // כתובות דומיין שיאופשרו באפליקציה
+STARTUP_URL=example.com // כתובת האתר שבו תתחיל האפליקציה
+ALLOW_GOOGLE_LOGIN=true // אפשר התחברות עם גוגל
+VIEW_MODE=AUTO // מצב תצוגה (אפשרי: AUTO, PORTRAIT, LANDSCAPE)
+BLOCK_MEDIA=false
+BLOCK_ADS=true
+NO_SSL=false
+HIDE_NETFREE=true // הסתרת הבאנר של נטפרי
+APP_NAME=my app
+APPLICATION_ID=com.myapp.webapp
+VERSION=1.0.0
+APP_ICON_PATH= // אייקון לאפליקציה, יש לשים לב שצריך לוכסנים כפולים או הפוכים
+```
+
+## License - רישיון
This application is distributed under the GNU General Public License version 3 (GNU GPL-3.0). See the [LICENSE](LICENSE) file for more details.
-## Disclaimer
+## Disclaimer - כתב ויתור
+
+לידיעתך, אינני עורך דין ואין לראות במידע הניתן כאן ייעוץ משפטי. חשוב להתייעץ עם גורם משפטי מוסמך כדי להבטיח עמידה בכל דרישות הרישוי הרלוונטיות וחובות.
Please note that I am not a lawyer and the information provided here should not be considered legal advice. It is important to consult with a qualified legal professional to ensure compliance with all relevant licensing requirements and obligations.
diff --git a/app/build.gradle b/app/build.gradle
index be48f2d..675710e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,37 +2,53 @@ plugins {
id 'com.android.application'
}
+def loadEnvProps() {
+ def envFile = new File("${project.projectDir}/.env")
+ def props = new Properties()
+ if (envFile.exists()) {
+ envFile.withInputStream { stream ->
+ props.load(new InputStreamReader(stream, "UTF-8"))
+ }
+ }
+ return props
+}
+
+def getEnvValue(key, defaultValue = '') {
+ def props = loadEnvProps()
+ return props.getProperty(key, System.getenv(key) ?: defaultValue)
+}
android {
signingConfigs {
release {
storeFile = file("keystore/android_keystore.jks")
- storePassword System.getenv("SIGNING_STORE_PASSWORD")
- keyAlias System.getenv("SIGNING_KEY_ALIAS")
- keyPassword System.getenv("SIGNING_KEY_PASSWORD")
+ storePassword getEnvValue('SIGNING_STORE_PASSWORD')
+ keyAlias getEnvValue('SIGNING_KEY_ALIAS')
+ keyPassword getEnvValue('SIGNING_KEY_PASSWORD')
}
}
compileSdkVersion 34
defaultConfig {
- def allowGoogleLogin = System.getenv('ALLOW_GOOGLE_LOGIN') == 'true'
- def baseDomains = System.getenv('ALLOWED_DOMAINS') ?: 'example.com'
+ def allowGoogleLogin = getEnvValue('ALLOW_GOOGLE_LOGIN') == 'true'
+ def baseDomains = getEnvValue('ALLOWED_DOMAINS') ?: 'example.com'
def allowedDomains = allowGoogleLogin ? "${baseDomains},accounts.google.com" : baseDomains
buildConfigField "String", "ALLOWED_DOMAINS", "\"${allowedDomains}\""
buildConfigField "boolean", "ALLOW_GOOGLE_LOGIN", "${allowGoogleLogin}"
- buildConfigField "String", "STARTUP_URL", "\"${System.getenv('STARTUP_URL') ?: '' }\""
- buildConfigField "String", "VIEW_MODE", "\"${System.getenv('VIEW_MODE') ?: 'AUTO'}\""
- buildConfigField "boolean", "BLOCK_MEDIA", "${System.getenv('BLOCK_MEDIA') ?: 'false'}"
- buildConfigField "boolean", "BLOCK_ADS", "${System.getenv('BLOCK_ADS') ?: 'true'}"
- buildConfigField "boolean", "NO_SSL", "${System.getenv('NO_SSL') ?: 'false'}"
- resValue "string", "app_name", System.getenv('APP_NAME') ?: "My Application"
- applicationId System.getenv('APPLICATION_ID') ?: "com.webview.myapplication"
+ buildConfigField "String", "STARTUP_URL", "\"${getEnvValue('STARTUP_URL') ?: '' }\""
+ buildConfigField "String", "VIEW_MODE", "\"${getEnvValue('VIEW_MODE') ?: 'AUTO'}\""
+ buildConfigField "boolean", "BLOCK_MEDIA", "${getEnvValue('BLOCK_MEDIA') ?: 'false'}"
+ buildConfigField "boolean", "BLOCK_ADS", "${getEnvValue('BLOCK_ADS') ?: 'true'}"
+ buildConfigField "boolean", "NO_SSL", "${getEnvValue('NO_SSL') ?: 'false'}"
+ buildConfigField "boolean", "HIDE_NETFREE", "${getEnvValue('HIDE_NETFREE', 'true')}"
+ resValue "string", "app_name", getEnvValue('APP_NAME') ?: "My Application"
+ applicationId getEnvValue('APPLICATION_ID') ?: "com.webview.myapplication"
minSdkVersion 16
targetSdkVersion 34
versionCode 1
- versionName "1.0"
+ versionName getEnvValue('VERSION', '1.0.0')
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -52,6 +68,43 @@ android {
namespace 'com.webview.myapplication'
}
+def appIconPath = getEnvValue('APP_ICON_PATH')
+
+task replaceAppIcons {
+ doLast {
+ if (appIconPath && !appIconPath.isEmpty()) {
+ def iconFile = file(appIconPath)
+ if (!iconFile.exists()) {
+ project.logger.warn("App icon not found at: ${appIconPath}. Skipping icon replacement.")
+ return
+ }
+
+ android.sourceSets.main.res.srcDirs.each { resDir ->
+ file(resDir).eachDirMatch(~/^mipmap-.*$/) { mipmapDir ->
+ println "Replacing icons in ${mipmapDir.name}"
+ copy {
+ from iconFile
+ into mipmapDir
+ rename { 'ic_launcher.png' }
+ }
+ copy {
+ from iconFile
+ into mipmapDir
+ rename { 'ic_launcher_round.png' }
+ }
+ }
+ }
+ }
+ }
+}
+
+// This is the correct, more compatible way to hook into the build process.
+// It finds the 'mergeResources' task for each variant and makes it depend on our icon task.
+android.applicationVariants.all { variant ->
+ variant.mergeResources.dependsOn(replaceAppIcons)
+}
+
+
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
@@ -59,4 +112,5 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
-}
\ No newline at end of file
+ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6ddd991..5000266 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/src/main/java/com/webview/myapplication/MainActivity.java b/app/src/main/java/com/webview/myapplication/MainActivity.java
index 601e966..ff7ee86 100644
--- a/app/src/main/java/com/webview/myapplication/MainActivity.java
+++ b/app/src/main/java/com/webview/myapplication/MainActivity.java
@@ -113,6 +113,7 @@ public class MainActivity extends Activity {
private static final boolean BLOCK_MEDIA = BuildConfig.BLOCK_MEDIA;
private static final boolean BLOCK_ADS = BuildConfig.BLOCK_ADS;
private static final boolean NO_SSL = BuildConfig.NO_SSL;
+ private static final boolean HIDE_NETFREE = BuildConfig.HIDE_NETFREE;
private static final boolean ALLOW_GOOGLE_LOGIN = BuildConfig.ALLOW_GOOGLE_LOGIN;
private static final String CHROME_USER_AGENT = "Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36";
private String defaultUserAgent;
@@ -287,6 +288,18 @@ public boolean shouldOverrideUrlLoading(final WebView view, final String url) {
String host = Uri.parse(url).getHost();
boolean isAllowed = false;
+ if (url != null && url.startsWith("whatsapp://")) {
+ try {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(url));
+ view.getContext().startActivity(intent);
+ return true;
+ } catch (Exception e) {
+ Toast.makeText(getApplicationContext(), "Error opening whatsapp, the application may not be installed", Toast.LENGTH_LONG).show();
+ return true;
+ }
+ }
+
if (url.startsWith("tel:")) {
Intent tel = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(tel);
@@ -361,9 +374,19 @@ public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
mProgressBar.setVisibility(View.GONE);
+
if (BLOCK_MEDIA) {
view.loadUrl("javascript: (() => { function handle(node) { if (node.tagName === 'IMG' && node.style.visibility !== 'hidden' && node.width > 32 && node.height > 32) { const blankImageUrl = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; const { width, height } = window.getComputedStyle(node); node.src = blankImageUrl; node.style.visibility = 'hidden'; node.style.background = 'none'; node.style.backgroundImage = `url(${blankImageUrl})`; node.style.width = width; node.style.height = height; } else if (node.tagName === 'VIDEO' || node.tagName === 'IFRAME' || ((!node.type || node.type.includes('video')) && node.tagName === 'SOURCE') || node.tagName === 'OBJECT') { node.remove(); } } document.querySelectorAll('img,video,source,object,embed,iframe,[type^=video]').forEach(handle); const observer = new MutationObserver((mutations) => mutations.forEach((mutation) => mutation.addedNodes.forEach(handle))); observer.observe(document.body, { childList: true, subtree: true }); })();");
}
+
+ // remove netfree card
+ if (HIDE_NETFREE)
+ view.loadUrl("javascript: (() => { " +
+ "const element = document.querySelector('div#netfree-popup-window'); " +
+ "if (element) { " +
+ " element.parentNode.remove(); " +
+ "} " +
+ "})();");
}
@Override