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