--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #105203
5 | #063401
6 | #141a13
7 | #4AAB33
8 | #399623
9 | #E4E4E6
10 | #F1F1F1
11 | #449a2f
12 | #FFFFFF
13 | #000000
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_ideas.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Ideas
3 | about: Suggest an idea for the project
4 | labels:
5 | ---
6 |
7 | **Is your feature request connected to a problem you're trying to solve? Please describe.**
8 | # A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the possible solution according to you (if any)**
11 | # A clear and concise description of what your solution to the problem is.
12 |
13 | **Describe alternatives you've considered**
14 | # Any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | # Add any other context or log/screenshot(s) about the feature.
18 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | # -keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | # }
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Create a report to help the project improve
4 | labels:
5 | ---
6 |
7 | **Bug Description**
8 | # A clear and concise description of what the bug is, better if you can attach a log.
9 |
10 | **Expected Behavior**
11 | # A clear and concise description of what you expected to happen.
12 |
13 | **Any Logs/Screenshots**
14 | # If applicable, add a log or screenshot(s) to help explain your problem.
15 |
16 | **Device(s) (recommended):**
17 | - Device name/variant: [e.g. Google Pixel/6, Samsung Galaxy/J2]
18 | - Build: [e.g. arm, x86]
19 | - Android Version: [e.g. API 27 or Oreo]
20 |
21 | **Additional Context**
22 | # Any other optional context/comment about the problem.
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #built application files
2 | *.apk
3 | *.ap_
4 | *.aab
5 |
6 | # files for the dex VM
7 | *.dex
8 |
9 | # Java class files
10 | *.class
11 |
12 | # generated files
13 | bin/
14 | gen/
15 |
16 | # Local configuration file (sdk path, etc)
17 | local.properties
18 |
19 | # google config files
20 | google-services.json
21 |
22 | # Windows thumbnail db
23 | Thumbs.db
24 |
25 | # OSX files
26 | .DS_Store
27 |
28 | # Android Studio
29 | *.iml
30 | .idea
31 | .gradle/
32 | build/
33 | .navigation
34 | captures/
35 | output.json
36 |
37 | # NDK
38 | obj/
39 | .externalNativeBuild
40 |
41 | # Ignoring internal files
42 | createContext.js
43 | .cursorignore
44 | roadmap.md
45 | context.md
46 | PLUGINS.md
47 | aicontext.json
48 | /node_modules
49 | /site
50 | genctx.json
51 | .vscode/
52 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # 1. Android/Gradle Dependencies
4 | - package-ecosystem: "gradle"
5 | directory: "/"
6 | schedule:
7 | interval: "weekly"
8 | day: "monday"
9 | time: "06:00"
10 | timezone: "Asia/Kolkata"
11 | groups:
12 | all-dependencies:
13 | patterns:
14 | - "*"
15 | # Ignore major version jumps (e.g. v8 -> v9) to prevent breaking changes
16 | ignore:
17 | - dependency-name: "*"
18 | update-types: ["version-update:semver-major"]
19 |
20 | # 2. GitHub Actions (Workflows)
21 | - package-ecosystem: "github-actions"
22 | directory: "/"
23 | schedule:
24 | interval: "monthly"
25 | groups:
26 | all-actions:
27 | patterns:
28 | - "*"
29 |
--------------------------------------------------------------------------------
/docmd/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "android-smartwebview",
3 | "version": "1.0.0",
4 | "description": "Smart WebView is a versatile and lightweight project designed to help you quickly convert your website or web application into a native mobile app.",
5 | "homepage": "https://github.com/mgks/Android-SmartWebView#readme",
6 | "bugs": {
7 | "url": "https://github.com/mgks/Android-SmartWebView/issues"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/mgks/Android-SmartWebView.git"
12 | },
13 | "author": "Ghazi Khan",
14 | "scripts": {
15 | "dev": "docmd dev",
16 | "build": "docmd build",
17 | "preview": "npx serve build"
18 | },
19 | "engines": {
20 | "node": ">=22.0.0"
21 | },
22 | "license": "MIT",
23 | "private": true,
24 | "dependencies": {
25 | "@mgks/docmd": "^0.3.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/security_overlay.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/progress_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## For more details on how to configure your build environment visit
2 | # http://www.gradle.org/docs/current/userguide/build_environment.html
3 | #
4 | # Specifies the JVM arguments used for the daemon process.
5 | # The setting is particularly useful for tweaking memory settings.
6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
8 | #
9 | # When configured, Gradle will run in incubating parallel mode.
10 | # This option should only be used with decoupled projects. More details, visit
11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
12 | # org.gradle.parallel=true
13 | android.debug.obsoleteApi=true
14 | org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
15 | android.useAndroidX=true
16 | org.gradle.warning.mode=all
17 | org.gradle.unsafe.configuration-cache=true
18 | android.nonTransitiveRClass=false
19 | android.nonFinalResIds=false
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_main_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/demo/error.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 | Connection Error
11 |
12 |
13 |
14 |
15 |
16 |
⚠️
17 |
No Internet Connection
18 |
19 | You are offline. Please check your internet connection and try again.
20 |
21 |
22 |
25 | Retry
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Ghazi Khan (hello@mgks.dev)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/error.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 | Connection Error
11 |
12 |
13 |
14 |
15 |
16 |
⚠️
17 |
No Internet Connection
18 |
19 | You are offline. Please check your internet connection and try again.
20 |
21 |
22 |
25 | Retry
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_main_content.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docmd/content/features/printing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Printing'
3 | description: 'Allowing users to print the current web page content.'
4 | icon: 'print'
5 | ---
6 |
7 | Smart WebView supports printing the content currently displayed in the WebView using the native Android print framework.
8 |
9 | ---
10 |
11 | ## How to Trigger Printing
12 |
13 | Printing is initiated from your web content by using a hyperlink with the special URL scheme `print:`.
14 |
15 | **HTML Example:**
16 |
17 | ```html
18 | Print this Page
19 |
20 |
21 |
22 | ```
23 |
24 | ---
25 |
26 | ## How it Works
27 |
28 | 1. A user clicks a `print:` link in the WebView.
29 | 2. The `shouldOverrideUrlLoading` method in `MainActivity.java` intercepts this URL.
30 | 3. It calls the `Functions.print_page` method.
31 | 4. This method uses the Android `PrintManager` service to create a print job from the current WebView content.
32 | 5. The standard Android print preview screen appears, allowing the user to select a printer, save as a PDF, and adjust settings.
33 |
34 | ::: callout tip
35 | The quality of the printout depends on how well your webpage's CSS is optimized for print media (e.g., using `@media print` styles).
36 | :::
37 |
38 | ---
39 |
40 | ## Requirements
41 |
42 | * Android 4.4 (KitKat, API 19) or higher.
43 | * The device must have print services enabled or configured (e.g., Cloud Print, Wi-Fi Direct printing, or Save as PDF).
--------------------------------------------------------------------------------
/docmd/content/license.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'License'
3 | description: 'Smart WebView is open-source software licensed under the MIT License.'
4 | icon: 'scroll'
5 | ---
6 |
7 | ## MIT License
8 |
9 | Copyright (c) 2015 - Present
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy
12 | of this software and associated documentation files (the "Software"), to deal
13 | in the Software without restriction, including without limitation the rights
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | copies of the Software, and to permit persons to whom the Software is
16 | furnished to do so, subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in all
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 | SOFTWARE.
28 |
29 | ---
30 |
31 | ::: card
32 | [View LICENSE File on GitHub](https://github.com/mgks/Android-SmartWebView/blob/master/LICENSE)
33 | :::
34 | ::: card
35 | [Read MIT License Definition](https://opensource.org/licenses/MIT)
36 | :::
--------------------------------------------------------------------------------
/app/src/main/res/layout/welcome_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/java/mgks/os/swv/PluginInterface.java:
--------------------------------------------------------------------------------
1 | package mgks.os.swv;
2 |
3 | /*
4 | Smart WebView v8
5 | https://github.com/mgks/Android-SmartWebView
6 |
7 | A modern, open-source WebView wrapper for building advanced hybrid Android apps.
8 | Native features, modular plugins, and full customisation—built for developers.
9 |
10 | - Documentation: https://mgks.github.io/Android-SmartWebView/documentation
11 | - Plugins: https://mgks.github.io/Android-SmartWebView/documentation/plugins
12 | - Discussions: https://github.com/mgks/Android-SmartWebView/discussions
13 | - Sponsor the Project: https://github.com/sponsors/mgks
14 |
15 | MIT License — https://opensource.org/licenses/MIT
16 |
17 | Mentioning Smart WebView in your project helps others find it and keeps the dev loop alive.
18 | */
19 |
20 | import android.app.Activity;
21 | import android.content.Intent;
22 | import android.webkit.WebView;
23 | import java.util.Map;
24 | import androidx.annotation.NonNull;
25 |
26 | public interface PluginInterface {
27 | void initialize(Activity activity, WebView webView, Functions functions, Map config);
28 | String getPluginName();
29 | void onActivityResult(int requestCode, int resultCode, Intent data);
30 | void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults);
31 | boolean shouldOverrideUrlLoading(WebView view, String url);
32 | void onPageStarted(String url);
33 | void onPageFinished(String url);
34 | void onResume();
35 | void onPause();
36 | void onDestroy();
37 | void evaluateJavascript(String script);
38 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_main_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docmd/content/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Android Smart WebView Project'
3 | description: 'Smart WebView is a versatile and lightweight **project** designed to help you quickly convert your website or web application into a native mobile app.'
4 | ---
5 |
6 | Smart WebView is a versatile and lightweight **project** designed to help you quickly convert your website or web application into a native mobile app. It provides a robust **foundation** with essential features built-in, saving you significant development time.
7 |
8 | ::: button Smart_WebView_on_GitHub external:https://github.com/mgks/Android-SmartWebView/
9 |
10 | **Key Highlights:**
11 |
12 | * **Hybrid App Solution:** Seamlessly wrap your existing web content within a native container.
13 | * **Feature Rich:** Includes support for common requirements like file uploads, camera access, geolocation, push notifications (Firebase), analytics, and more.
14 | * **Customizable:** Easily configure and style the app to match your brand identity.
15 | * **Extensible:** (v7.0+) Features a powerful plugin architecture allowing developers to add custom native functionalities without altering the core code, making it behave more like a **framework** for hybrid apps.
16 | * **Modern:** Built with up-to-date native development practices, libraries, and target APIs (currently focused on Android).
17 |
18 | Whether you need a simple web wrapper or a more complex hybrid application with native integrations, Smart WebView provides a solid starting **point**.
19 |
20 | **Ready to get started?** Head over to the [Getting Started](/Android-SmartWebView/documentation/getting-started) guide.
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Hybrid Site
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 | workflow_dispatch:
7 |
8 | permissions:
9 | contents: read
10 | pages: write
11 | id-token: write
12 |
13 | concurrency:
14 | group: "pages"
15 | cancel-in-progress: true
16 |
17 | jobs:
18 | build-and-deploy:
19 | runs-on: ubuntu-latest
20 | environment:
21 | name: github-pages
22 | url: ${{ steps.deployment.outputs.page_url }}
23 |
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v4
27 |
28 | # --- 1. Build docmd (The Documentation) ---
29 | - name: Setup Node
30 | uses: actions/setup-node@v4
31 | with:
32 | node-version: '22'
33 |
34 | - name: Install and Build docmd
35 | working-directory: ./docmd
36 | run: |
37 | npm install
38 | npm install -g @mgks/docmd
39 | docmd build
40 |
41 | # --- 2. Merge Sites into One Folder ---
42 | - name: Assemble Final Site
43 | run: |
44 | # Create a temporary folder for the final website
45 | mkdir _final_build
46 |
47 | # A. Copy your EXISTING main site (from root docs/) to the root of the build
48 | cp -r demo/* _final_build/
49 |
50 | # B. Create the subfolder for documentation
51 | mkdir -p _final_build/documentation
52 |
53 | # C. Copy the docmd output into that subfolder
54 | cp -r docmd/site/* _final_build/documentation/
55 |
56 | # --- 3. Deploy ---
57 | - name: Setup Pages
58 | uses: actions/configure-pages@v5
59 |
60 | - name: Upload artifact
61 | uses: actions/upload-pages-artifact@v3
62 | with:
63 | path: ./_final_build # Upload the merged folder
64 |
65 | - name: Deploy to GitHub Pages
66 | id: deployment
67 | uses: actions/deploy-pages@v4
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_main_bar.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
17 |
18 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
37 |
38 |
47 |
48 |
--------------------------------------------------------------------------------
/docmd/content/plugins/toast.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Toast Plugin'
3 | description: 'Displaying native Toast messages from native code or JavaScript.'
4 | icon: 'bread-slice'
5 | ---
6 |
7 | The `ToastPlugin` is included as a basic example of how the plugin architecture works. It provides a simple way to display native "Toast" messages (short, non-blocking pop-ups).
8 |
9 | ---
10 |
11 | ## Features
12 |
13 | * Display toasts from native Java code.
14 | * Display toasts triggered from JavaScript in the WebView.
15 | * Configurable default duration (short or long).
16 |
17 | ---
18 |
19 | ## Setup & Configuration
20 |
21 | 1. **Enable Plugin:** Ensure `ToastPlugin` is listed in the `plugins.enabled` property in `app/src/main/assets/swv.properties`.
22 | ```bash
23 | plugins.enabled=...,ToastPlugin
24 | ```
25 |
26 | 2. **Internal Logic:** The `ToastPlugin.java` class uses a static initializer block to automatically register itself. During initialization, it adds a JavaScript interface named `ToastInterface` to the WebView.
27 |
28 | ---
29 |
30 | ## Usage
31 |
32 | ### From Native Code
33 |
34 | 1. Get the plugin instance from the `PluginManager`.
35 | 2. Call its `showToast` method.
36 |
37 | ```java
38 | // Example from another class, like Playground.java
39 | PluginInterface plugin = SWVContext.getPluginManager().getPluginInstance("ToastPlugin");
40 | if (plugin instanceof mgks.os.swv.plugins.ToastPlugin) {
41 | ((mgks.os.swv.plugins.ToastPlugin) plugin).showToast("Hello from Native!");
42 | }
43 | ```
44 |
45 | ### From JavaScript
46 |
47 | After the page has loaded, you can call the methods of the injected `window.Toast` helper object.
48 |
49 | ```javascript
50 | // Check if the interface is ready
51 | if (window.Toast) {
52 | // Show a toast with the default duration
53 | window.Toast.show("Hello from JavaScript!");
54 |
55 | // Show a toast with a long duration
56 | window.Toast.showLong("This JavaScript toast stays for longer.");
57 | }
58 | ```
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
20 |
21 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
45 |
53 |
--------------------------------------------------------------------------------
/docmd/content/plugins/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Plugin Architecture'
3 | description: 'Understanding the Smart WebView plugin system.'
4 | icon: 'puzzle'
5 | ---
6 |
7 | Smart WebView features a powerful plugin architecture, allowing you to extend native functionalities without altering the core project code.
8 |
9 | ## Core Concepts
10 |
11 | * **Self-Contained:** Plugins are designed as independent, modular classes.
12 | * **Self-Registration:** Plugins register themselves with the `PluginManager` when their class is first loaded.
13 | * **Standardized Interface:** All plugins implement the `PluginInterface`, which defines essential lifecycle methods (`initialize`, `onDestroy`, `onActivityResult`, etc.).
14 | * **Central Management:** The `PluginManager` class handles plugin registration, initialization, and routing of lifecycle events.
15 | * **Configuration:** Plugin activation is controlled via `swv.properties`. This prevents the need to modify Java code just to enable or disable a standard feature.
16 |
17 | ## Benefits
18 |
19 | * **Modularity:** Keeps custom features separate and organized.
20 | * **Extensibility:** Easily add new native capabilities.
21 | * **Simplified Updates:** Core project updates are easier when custom code is isolated.
22 |
23 | ## Key Components
24 |
25 | * **`PluginInterface.java`:** The contract that all plugins must implement.
26 | * **`PluginManager.java`:** The central hub for managing all registered plugins. Accessed via `SWVContext.getPluginManager()`.
27 | * **`swv.properties`:** The single configuration file where you define which plugins are enabled (`plugins.enabled`) and set their configurable properties.
28 | * **`Playground.java`:** A dedicated class for advanced plugin configuration (like API keys) and testing during development.
29 | * **`plugins/` directory:** The conventional location for plugin source files.
30 |
31 | Ready to build your own? Check out the [Creating Plugins](/Android-SmartWebView/documentation/plugins/creating-plugins) guide.
--------------------------------------------------------------------------------
/docmd/content/features/navigation.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'URL Handling & Navigation'
3 | description: 'Managing internal/external links and UI layouts.'
4 | icon: 'compass'
5 | ---
6 |
7 | Smart WebView provides flexible options for handling URL navigation and choosing the app's primary UI layout.
8 |
9 | ---
10 |
11 | ## URL Handling
12 |
13 | This controls how the app treats different types of links.
14 |
15 | **External Links:**
16 |
17 | You can configure how links that point to domains outside your main website are handled in `app/src/main/assets/swv.properties`.
18 |
19 | * `feature.open.external.urls=true`: If `true`, external links are opened outside the app. If `false`, all links are loaded inside the WebView.
20 | * `feature.chrome.tabs=true`: If external URLs are enabled, this determines whether to use integrated Chrome Custom Tabs (`true`) or the device's default browser (`false`).
21 | * `external.url.exception.list`: A comma-separated list of domains that should be treated as internal, even if they don't match your main host.
22 |
23 | **Special URL Schemes:**
24 |
25 | The app intercepts URLs with specific prefixes to trigger native actions:
26 |
27 | * `tel:*`: Opens the default phone dialer.
28 | * `rate:*`: Opens the app's page on the Google Play Store.
29 | * `share:*`: Opens the native sharing dialog.
30 | * `exit:*`: Closes the application.
31 | * `print:*`: Opens the native print dialog.
32 |
33 | These are handled in the `url_actions` method in `Functions.java`.
34 |
35 | ---
36 |
37 | ## UI Layout Modes
38 |
39 | Configure the app's main layout via the `ui.layout` property in `app/src/main/assets/swv.properties`.
40 |
41 | ### Mode 0: Fullscreen Layout
42 |
43 | * **Description:** The WebView occupies the entire screen. This is ideal for a simple, immersive web wrapper.
44 | * **Property:** `ui.layout=0`
45 |
46 | ### Mode 1: Drawer Layout (Default)
47 |
48 | * **Description:** Implements a standard Android navigation drawer with a side menu and a top action bar.
49 | * **Property:** `ui.layout=1`
--------------------------------------------------------------------------------
/docmd/content/features/analytics.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Analytics'
3 | description: 'Integrating Google Analytics for usage tracking.'
4 | icon: 'chart-area'
5 | ---
6 |
7 | Smart WebView supports integration with Google Analytics using the gtag.js library to track user interactions within your web content.
8 |
9 | ---
10 |
11 | ## Configuration
12 |
13 | 1. **Get Your Measurement ID:** Obtain your Google Analytics Measurement ID (e.g., `G-XXXXXXXXXX`) from your Google Analytics property settings.
14 | 2. **Set the ID in `swv.properties`:** Assign your Measurement ID to the `analytics.gtag.id` property in `app/src/main/assets/swv.properties`.
15 | ```bash
16 | # In swv.properties
17 | analytics.gtag.id=G-7XXC1C7CRQ # <-- Replace with your actual ID
18 | ```
19 | If the ID is left empty, Analytics integration will be disabled.
20 |
21 | ---
22 |
23 | ## How it Works
24 |
25 | * **Dynamic Injection:** Instead of adding the gtag.js snippet to your HTML, Smart WebView injects it dynamically using JavaScript *after* the page has finished loading. This is handled by the `onPageFinished` event in `MainActivity.java`.
26 | * **Improved Performance:** This approach prevents the Analytics script from blocking initial page rendering.
27 |
28 | ---
29 |
30 | ## Tracking Events
31 |
32 | Once gtag.js is loaded, you can track events within your web application's JavaScript just as you would on a regular website.
33 |
34 | **Example: Tracking a Button Click**
35 |
36 | ```javascript
37 | // In your web page's JavaScript (e.g., script.js)
38 | document.getElementById('myButton').addEventListener('click', function() {
39 | // Check if gtag function exists
40 | if (typeof gtag === 'function') {
41 | gtag('event', 'button_click', {
42 | 'event_category': 'Engagement',
43 | 'event_label': 'Special Feature Button'
44 | });
45 | console.log('GA event sent: button_click');
46 | } else {
47 | console.error('gtag function not found.');
48 | }
49 | });
50 | ```
51 |
52 | Refer to the [Google Analytics gtag.js documentation](https://developers.google.com/analytics/devguides/collection/gtagjs/events) for more details.
--------------------------------------------------------------------------------
/docmd/content/features/sharing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Inbound Sharing'
3 | description: 'Receiving text and links shared from other apps.'
4 | icon: 'share'
5 | ---
6 |
7 | Smart WebView can register as a target for Android's native sharing functionality, allowing users to share content like URLs and text directly *to* your application from other apps.
8 |
9 | ---
10 |
11 | ## How it Works
12 |
13 | 1. **Enabling via Manifest:** Sharing is enabled via `` elements for the `ShareActivity` in `AndroidManifest.xml`. These filters specify that the app can handle `ACTION_SEND` intents for `text/*` and `image/*` MIME types.
14 | 2. **User Action:** A user in another app (like a browser or social media app) uses the "Share" button and selects your app from the list.
15 | 3. **Activity Launch:** Android launches the `ShareActivity` of your app.
16 | 4. **Data Handling:** `ShareActivity` extracts the shared text or link from the intent.
17 | 5. **Redirection:** It then constructs a URL based on the main app URL (`ASWV_URL`) and appends the shared content as query parameters. For example: `https://your-site.com/?s_uri=SHARED_CONTENT`.
18 | 6. **Loading in WebView:** Finally, it launches the `MainActivity` and instructs it to load this newly constructed URL, allowing your web application to process the shared content.
19 |
20 | ---
21 |
22 | ## Processing on Your Website
23 |
24 | Your web application needs to be able to parse the URL query parameters to handle the shared data.
25 |
26 | **Example JavaScript:**
27 |
28 | ```javascript
29 | const urlParams = new URLSearchParams(window.location.search);
30 | const sharedContent = urlParams.get('s_uri'); // Matches the key from ShareActivity
31 |
32 | if (sharedContent) {
33 | // The content was shared from another app
34 | console.log('Received shared content:', sharedContent);
35 | // Now you can display it, fill a form, etc.
36 | document.getElementById('my-textarea').value = sharedContent;
37 | }
38 | ```
39 |
40 | ---
41 |
42 | ## Disabling Sharing
43 |
44 | To disable this feature, remove or comment out the entire `...` block from `AndroidManifest.xml`.
--------------------------------------------------------------------------------
/docmd/content/plugins/qr-barcode-reader.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'QR/Barcode Reader Plugin'
3 | description: 'Scanning QR codes and barcodes using the device camera.'
4 | icon: 'qrcode'
5 | ---
6 |
7 | This plugin integrates native QR code and barcode scanning functionality using the device's camera.
8 |
9 | ::: callout tip
10 | All Premium Plugins are now available for free and open source to developers. Consider becoming **[Project Sponsor](https://github.com/sponsors/mgks)**.
11 | :::
12 |
13 | ---
14 |
15 | ## Setup and Configuration
16 |
17 | 1. **Enable Plugin:** Add `QRScannerPlugin` to the `plugins.enabled` list in `app/src/main/assets/swv.properties`.
18 | ```bash
19 | # In swv.properties
20 | plugins.enabled=QRScannerPlugin,ToastPlugin,...
21 | ```
22 | 2. **Dependencies:** This plugin relies on the `zxing-android-embedded` library. Ensure the following dependencies are present in your `app/build.gradle` file (they are included by default in v8.0.0+):
23 | ```groovy
24 | implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
25 | implementation 'com.google.zxing:core:3.5.2'
26 | ```
27 | 3. **Permissions:** The app automatically requests the `CAMERA` permission declared in `AndroidManifest.xml` when the scanner is invoked.
28 |
29 | ---
30 |
31 | ## Usage
32 |
33 | The plugin is controlled via a JavaScript interface.
34 |
35 | ### Starting a Scan from JavaScript
36 |
37 | The plugin injects a `window.QRScanner` object into your web page.
38 |
39 | ```javascript
40 | // Open the camera and start scanning for a code
41 | window.QRScanner.scan();
42 | ```
43 |
44 | ### Callbacks in JavaScript
45 |
46 | Define callback functions in your JavaScript to handle the results of the scan.
47 |
48 | ```javascript
49 | // Called when a code is successfully scanned
50 | window.QRScanner.onScanSuccess = function(contents) {
51 | console.log('Scanned content:', contents);
52 | alert('Scanned: ' + contents);
53 | // Process the scanned data in your web app (e.g., redirect to URL)
54 | };
55 |
56 | // Called if the user cancels the scan (e.g., by pressing the back button)
57 | window.QRScanner.onScanCancelled = function() {
58 | console.log('Scan was cancelled by the user.');
59 | };
60 | ```
--------------------------------------------------------------------------------
/docmd/content/contributing.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Contributing'
3 | description: 'How to contribute to the Smart WebView project.'
4 | icon: 'code-merge'
5 | ---
6 |
7 | Contributions to Smart WebView are welcome! Whether it's fixing bugs, improving documentation, or adding new features, your help is appreciated.
8 |
9 | ---
10 |
11 | ## How to Contribute
12 |
13 | 1. **Fork the Repository:**
14 | Start by forking the main repository on GitHub to your own account.
15 |
16 | **[Fork Smart WebView on GitHub](https://github.com/mgks/Android-SmartWebView/fork)**
17 |
18 | 2. **Clone Your Fork:**
19 | Clone your forked repository to your local machine.
20 | ```bash
21 | git clone https://github.com/YOUR_USERNAME/Android-SmartWebView.git
22 | cd Android-SmartWebView
23 | ```
24 |
25 | 3. **Create a Feature Branch:**
26 | Create a new branch for your changes. Use a descriptive name (e.g., `fix-fcm-token-bug`, `feature-add-file-download-progress`).
27 | ```bash
28 | git checkout -b my-new-feature
29 | ```
30 |
31 | 4. **Make Your Changes:**
32 | Implement your bug fix or feature. Follow the existing code style and conventions.
33 |
34 | 5. **Test Your Changes:**
35 | Ensure your changes work correctly and don't introduce regressions.
36 |
37 | 6. **Commit Your Changes:**
38 | Commit your changes with a clear and concise message.
39 | ```bash
40 | git add .
41 | git commit -m "feat: Describe your new feature"
42 | # or for bug fixes:
43 | # git commit -m "fix: Describe the bug and the fix"
44 | ```
45 |
46 | 7. **Push to Your Fork:**
47 | Push your feature branch to your forked repository.
48 | ```bash
49 | git push origin my-new-feature
50 | ```
51 |
52 | 8. **Create a Pull Request (PR):**
53 | Go to the original Smart WebView repository and create a new Pull Request from your feature branch to the `master` branch.
54 | * Provide a clear title and description for your PR.
55 | * Reference any related issues (e.g., "Closes #123").
56 |
57 | ---
58 |
59 | ## Code of Conduct
60 |
61 | Please note that this project is released with a Contributor Code of Conduct. By participating, you agree to abide by its terms.
62 |
63 | ---
64 |
65 | Thank you for contributing!
--------------------------------------------------------------------------------
/docmd/content/features/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'File Handling'
3 | description: 'Managing file uploads, camera access, and downloads.'
4 | icon: 'folder-open'
5 | ---
6 |
7 | Smart WebView provides robust support for handling file uploads initiated from your web content, including direct access to the device camera, and manages file downloads.
8 |
9 | ---
10 |
11 | ## File Uploads & Camera Access
12 |
13 | This functionality allows users to interact with `` elements in your web content.
14 |
15 | **Configuration:**
16 |
17 | Controlled by properties in `app/src/main/assets/swv.properties`:
18 | ```bash
19 | # Globally enable/disable file input.
20 | feature.uploads=true
21 |
22 | # Include a camera capture option in the chooser.
23 | feature.camera.uploads=true
24 |
25 | # Allow multiple file selection if the HTML input tag supports it.
26 | feature.multiple.uploads=true
27 | ```
28 |
29 | **Permissions:**
30 |
31 | The following permissions are declared in `AndroidManifest.xml` and requested at runtime if needed:
32 | * `android.permission.CAMERA`
33 | * `android.permission.READ_MEDIA_IMAGES`
34 | * `android.permission.READ_MEDIA_VIDEO`
35 | * `android.permission.WRITE_EXTERNAL_STORAGE` (for older Android versions)
36 |
37 | **How it Works:**
38 |
39 | 1. A user taps an `` element in the WebView.
40 | 2. The `onShowFileChooser` method in `FileProcessing.java` is triggered.
41 | 3. It constructs an `Intent` that opens a system chooser, allowing the user to select files or use the camera (if enabled).
42 | 4. The HTML `accept` attribute can filter the file types shown (e.g., `image/*`).
43 | 5. The HTML `multiple` attribute, combined with `feature.multiple.uploads`, allows for multi-file selection.
44 | 6. The selected file URIs are returned to the WebView to be processed by your web application.
45 |
46 | ---
47 |
48 | ## Downloads
49 |
50 | This handles files downloaded *from* the WebView.
51 |
52 | **How it Works:**
53 |
54 | 1. The WebView's `DownloadListener` detects a URL that triggers a download.
55 | 2. It uses the Android `DownloadManager` service to handle the download.
56 | 3. A system notification shows the download progress.
57 | 4. Files are saved to the public "Downloads" directory on the device.
58 | 5. A Toast message confirms that the download has started.
--------------------------------------------------------------------------------
/docmd/content/plugins/rating-system.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Rating System Plugin'
3 | description: 'Prompting users to rate your app on the Google Play store.'
4 | icon: 'star'
5 | ---
6 |
7 | The `RatingPlugin` prompts users to rate your application on the Google Play Store after certain usage conditions are met.
8 |
9 | ---
10 |
11 | ## How It Works
12 |
13 | This plugin is self-activating. Once enabled, it automatically tracks:
14 | * The number of times the app has been launched.
15 | * The number of days that have passed since the app was first installed.
16 |
17 | When the configured thresholds are met, the plugin displays a standard Android dialog asking the user to rate the app.
18 |
19 | ---
20 |
21 | ## Configuration
22 |
23 | This plugin's behavior is controlled by properties in `app/src/main/assets/swv.properties`.
24 |
25 | 1. **Enable the Plugin:** First, ensure `RatingPlugin` is listed in the `plugins.enabled` property.
26 | ```bash
27 | # In swv.properties
28 | plugins.enabled=RatingPlugin,ToastPlugin,...
29 | ```
30 |
31 | 2. **Set Trigger Conditions:** Adjust the following properties to control when the dialog appears.
32 | ```bash
33 | # In swv.properties
34 |
35 | # Minimum days to wait after install before showing the dialog.
36 | rating.install.days=3
37 |
38 | # Minimum number of app launches required before showing.
39 | rating.launch.times=10
40 |
41 | # If the user selects "Later", days to wait before asking again.
42 | rating.remind.interval=2
43 | ```
44 |
45 | ---
46 |
47 | ## Dialog Options
48 |
49 | The user is presented with a non-intrusive dialog with three choices:
50 | * **Rate Now:** Opens the app's page on the Google Play Store and permanently dismisses future prompts.
51 | * **Later:** Dismisses the dialog and waits for the `rating.remind.interval` before potentially showing it again.
52 | * **No, Thanks:** Permanently dismisses future prompts for the user.
53 |
54 | ---
55 |
56 | ## Customizing Dialog Text
57 |
58 | You can change the text displayed in the rating dialog by editing the string resources in `app/src/main/res/values/strings.xml`:
59 | * `rate_dialog_title`
60 | * `rate_dialog_message`
61 | * `rate_dialog_ok` (for the "Rate Now" button)
62 | * `rate_dialog_cancel` (for the "Later" button)
63 | * `rate_dialog_no` (for the "No, Thanks" button)
--------------------------------------------------------------------------------
/docmd/content/getting-started.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Getting Started'
3 | description: 'Setting up your Smart WebView project in minutes.'
4 | icon: 'rocket'
5 | ---
6 |
7 | Follow these steps to get your Smart WebView project up and running.
8 |
9 | ## Prerequisites
10 |
11 | * **Android Studio:** The official IDE for Android development. Download from the [Android Developers site](https://developer.android.com/studio).
12 | * **Android SDK:** Minimum API Level 24 (Android 7.0 Nougat) or higher installed via the Android Studio SDK Manager.
13 |
14 | ## Step 1: Download and Open
15 |
16 | 1. **(Recommended)** Download the latest source code (`.zip` or `.tar.gz`) from the [GitHub Releases](https://github.com/mgks/Android-SmartWebView/releases) page.
17 | 2. Unzip the project and open the folder in Android Studio.
18 | * Alternatively, clone the repository: `git clone https://github.com/mgks/Android-SmartWebView.git`
19 |
20 | ## Step 2: Configure Your App
21 |
22 | All major configuration is handled in a single properties file, separating configuration from code.
23 |
24 | 1. In the Android Studio project view, navigate to `app/src/main/assets/`.
25 | 2. Open the `swv.properties` file.
26 | 3. Change `app.url` to your website's URL and adjust other settings as needed.
27 |
28 | ::: callout tip
29 | See the **[Configuration Guide](/Android-SmartWebView/documentation/configuration)** for a detailed explanation of all available options in `swv.properties`.
30 | :::
31 |
32 | ## Step 3: Add Firebase Configuration (Optional)
33 |
34 | If you plan to use Firebase Cloud Messaging (Push Notifications), you need your project's `google-services.json` file.
35 |
36 | 1. Go to your [Firebase Console](https://console.firebase.google.com/) and follow the steps to add an Android app.
37 | 2. Download the `google-services.json` file.
38 | 3. Place this file directly into the `app/` directory of your Smart WebView project.
39 | ```bash
40 | Android-SmartWebView/
41 | ├── app/
42 | │ ├── google-services.json <-- Place it here
43 | │ ├── src/
44 | │ └── ...
45 | └── ...
46 | ```
47 |
48 | ## Step 4: Build and Run
49 |
50 | 1. Allow Gradle to sync and download all dependencies. This may take a few moments.
51 | 2. Click the `Run 'app'` button (the green play icon) to build and launch the app on an emulator or a connected device.
52 |
53 | Your Smart WebView app should now launch!
--------------------------------------------------------------------------------
/app/src/main/java/mgks/os/swv/MetaPull.java:
--------------------------------------------------------------------------------
1 | package mgks.os.swv;
2 |
3 | /*
4 | Smart WebView v8
5 | https://github.com/mgks/Android-SmartWebView
6 |
7 | A modern, open-source WebView wrapper for building advanced hybrid Android apps.
8 | Native features, modular plugins, and full customisation—built for developers.
9 |
10 | - Documentation: https://mgks.github.io/Android-SmartWebView/documentation
11 | - Plugins: https://mgks.github.io/Android-SmartWebView/documentation/plugins
12 | - Discussions: https://github.com/mgks/Android-SmartWebView/discussions
13 | - Sponsor the Project: https://github.com/sponsors/mgks
14 |
15 | MIT License — https://opensource.org/licenses/MIT
16 |
17 | Mentioning Smart WebView in your project helps others find it and keeps the dev loop alive.
18 | */
19 |
20 | import android.content.Context;
21 | import android.content.pm.ApplicationInfo;
22 | import android.content.pm.PackageInfo;
23 | import android.content.pm.PackageManager;
24 | import android.os.Build;
25 | import android.util.Log;
26 |
27 | import androidx.multidex.BuildConfig;
28 |
29 | public class MetaPull {
30 |
31 | private final Context context;
32 | private static final String TAG = "MetaPull";
33 |
34 | public MetaPull(Context context) {
35 | this.context = context;
36 | }
37 |
38 | String swv() {
39 | try {
40 | PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
41 | ApplicationInfo appInfo = context.getApplicationInfo();
42 |
43 | String buildType = BuildConfig.DEBUG ? "debug" : "release";
44 | int minSdk = appInfo.minSdkVersion;
45 | int targetSdk = appInfo.targetSdkVersion;
46 |
47 | return "SWV.RELEASE : " + pInfo.versionName
48 | + "\nSWV.BUILD : " + pInfo.versionCode
49 | + "\nSWV.SDK.MIN : " + minSdk
50 | + "\nSWV.SDK.MAX : " + targetSdk
51 | + "\nSWV.BUILD.TYPE : " + buildType
52 | + "\nSWV.BUILD.NAME : " + pInfo.versionName
53 | + "\nSWV.PACKAGE.NAME : " + context.getPackageName();
54 |
55 | } catch (PackageManager.NameNotFoundException e) {
56 | Log.e(TAG, "Could not get package info", e);
57 | return "Error fetching app info.";
58 | }
59 | }
60 |
61 | String device() {
62 | return "VERSION.RELEASE : " + Build.VERSION.RELEASE
63 | + "\nVERSION.SDK.NUMBER : " + Build.VERSION.SDK_INT
64 | + "\nMANUFACTURER : " + Build.MANUFACTURER
65 | + "\nMODEL : " + Build.MODEL;
66 | }
67 | }
--------------------------------------------------------------------------------
/docmd/content/features/dark-mode.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Dark Mode & Theming'
3 | description: 'Implementing dynamic light and dark themes based on system settings.'
4 | icon: 'moon'
5 | ---
6 |
7 | Smart WebView includes a robust system for handling light and dark themes, allowing the app to automatically adapt to the user's device settings.
8 |
9 | ---
10 |
11 | ## How It Works
12 |
13 | The theming system operates on multiple levels to ensure a seamless experience:
14 |
15 | 1. **Native Android Theme:** The app uses different resource files for light (`res/values/`) and dark (`res/values-night/`) modes. Android automatically applies the correct theme when the app starts, based on the device's system setting.
16 | 2. **Initial State Detection:** When the app launches, it detects the current system theme.
17 | 3. **JavaScript Injection:** On page load, the native app injects the initial theme preference into the web page's JavaScript context. This allows your web content to match the native UI.
18 | 4. **CSS Styling:** The web page's `style.css` uses CSS variables and a `.dark-mode` class on the `` tag to switch between light and dark styles.
19 | 5. **Web-to-Native Sync:** A JavaScript interface is provided (`window.AndroidInterface.setNativeTheme()`) that allows your web UI (e.g., a theme toggle button on your website) to change the native Android theme.
20 |
21 | ::: callout warning
22 | The native theme toggle switch in the navigation drawer has been temporarily disabled in v7.5 to ensure stability. Theming is currently driven by the system setting and can be controlled by your web page's JavaScript.
23 | :::
24 |
25 | ---
26 |
27 | ## Configuration
28 |
29 | ### Native Theme Colors
30 |
31 | You can customize the colors for both light and dark modes in their respective files:
32 |
33 | - **Light Mode:** `app/src/main/res/values/colors.xml`
34 | - **Dark Mode:** `app/src/main/res/values-night/colors.xml`
35 |
36 | ### Web Page Control
37 |
38 | Your web page can control the native theme using the provided JavaScript interface.
39 |
40 | ```javascript
41 | // In your website's script.js
42 |
43 | function changeTheme(theme) { // theme can be 'light', 'dark', or 'system'
44 | // 1. Change the web page's CSS
45 | document.body.classList.toggle('dark-mode', theme === 'dark');
46 |
47 | // 2. Tell the native app to change its theme
48 | if (window.AndroidInterface && typeof window.AndroidInterface.setNativeTheme === 'function') {
49 | window.AndroidInterface.setNativeTheme(theme);
50 | }
51 | }
52 | ```
--------------------------------------------------------------------------------
/docmd/content/plugins/location.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Location Access Plugin'
3 | description: 'Accessing device GPS coordinates and sending them to your web app.'
4 | icon: 'map-pin'
5 | ---
6 |
7 | Smart WebView's `LocationPlugin` provides a modern, secure, and battery-efficient way to access the device's location from your web application.
8 |
9 | ---
10 |
11 | ## Enabling Location Services
12 |
13 | 1. **Enable in Configuration:** In `swv.properties`, ensure the `LOCATION` permission group is requested on launch.
14 | ```bash
15 | # swv.properties
16 | permissions.on.launch=NOTIFICATIONS,LOCATION
17 | ```
18 | 2. **Enable the Plugin:** Make sure `LocationPlugin` is in the `plugins.enabled` list.
19 | ```bash
20 | # swv.properties
21 | plugins.enabled=LocationPlugin,ToastPlugin,...
22 | ```
23 |
24 | ---
25 |
26 | ## Permissions
27 |
28 | The app declares and requests `ACCESS_FINE_LOCATION`. The user must grant this permission at runtime for the feature to work.
29 |
30 | ---
31 |
32 | ## How it Works
33 |
34 | The `LocationPlugin` provides a JavaScript interface that your web code can call on demand. This is more efficient than constantly tracking the user's location.
35 |
36 | 1. **JavaScript Call:** Your web app calls `window.SWVLocation.getCurrentPosition()`, passing a callback function.
37 | 2. **Native Request:** The plugin receives the request and asks the Android system for the current location.
38 | 3. **Callback Execution:** Once the location is retrieved (or if an error occurs), the plugin executes your JavaScript callback, passing the latitude, longitude, and any error message as arguments.
39 |
40 | **Accessing Coordinates in JavaScript:**
41 |
42 | This is the recommended way to get location data.
43 |
44 | ```javascript
45 | // Check if the location feature is available
46 | if (window.SWVLocation) {
47 |
48 | // Request the current position
49 | window.SWVLocation.getCurrentPosition(function(lat, lng, error) {
50 | if (error) {
51 | console.error("Location Error:", error);
52 | // e.g., display an error message to the user
53 | return;
54 | }
55 |
56 | console.log(`Latitude: ${lat}, Longitude: ${lng}`);
57 | // Use the coordinates in your web app
58 | // e.g., show a marker on a map
59 | });
60 |
61 | } else {
62 | console.log('Location feature not available.');
63 | }
64 | ```
65 |
66 | ::: callout warning
67 | The JavaScript object is `window.SWVLocation`, not `window.Location`. This is to avoid a critical conflict with the browser's built-in `window.location` object.
68 | :::
--------------------------------------------------------------------------------
/docmd/content/plugins/image-compression.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Image Compression Plugin'
3 | description: 'Compressing images before uploading.'
4 | icon: 'file-zipper'
5 | ---
6 |
7 | This plugin provides functionality to compress images selected for upload directly on the device, significantly reducing bandwidth usage and upload times.
8 |
9 | ::: callout tip
10 | All Premium Plugins are now available for free and open source to developers. Consider becoming **[Project Sponsor](https://github.com/sponsors/mgks)**.
11 | :::
12 |
13 | ---
14 |
15 | ## Setup and Configuration
16 |
17 | 1. **Enable Plugin:** Ensure `ImageCompressionPlugin` is listed in the `plugins.enabled` property in `app/src/main/assets/swv.properties`.
18 | ```bash
19 | # In swv.properties
20 | plugins.enabled=ImageCompressionPlugin,...
21 | ```
22 | 2. **Configure Quality:** The default compression quality is `80` (out of 100). To change this, you currently modify the static initializer block in `app/src/main/java/mgks/os/swv/plugins/ImageCompressionPlugin.java`.
23 | ```java
24 | // In ImageCompressionPlugin.java
25 | static {
26 | Map config = new HashMap<>();
27 | config.put("quality", 75); // Change default quality here
28 | PluginManager.registerPlugin(new ImageCompressionPlugin(), config);
29 | }
30 | ```
31 |
32 | ---
33 |
34 | ## Usage
35 |
36 | The plugin is designed to be used from JavaScript, typically after a user has selected an image file for upload and you have its `base64` representation.
37 |
38 | ### Compressing an Image from JavaScript
39 |
40 | The plugin injects a `window.ImageCompressor` object into your web page.
41 |
42 | ```javascript
43 | // Assume 'originalBase64' is the base64 string of the image you want to compress
44 | // (e.g., from a FileReader result)
45 |
46 | if (window.ImageCompressor) {
47 | // The compress function takes the original base64 string and a callback
48 | window.ImageCompressor.compress(originalBase64, function(compressedBase64) {
49 | if (compressedBase64) {
50 | console.log('Compression successful!');
51 | console.log('Original size:', originalBase64.length);
52 | console.log('Compressed size:', compressedBase64.length);
53 |
54 | // Now you can upload the 'compressedBase64' string to your server
55 | uploadImage(compressedBase64);
56 |
57 | } else {
58 | console.error('Compression failed.');
59 | }
60 | });
61 | }
62 | ```
63 | This workflow allows you to seamlessly compress images on the client-side before they are transmitted, saving data for both the user and your server.
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
23 |
24 |
28 |
29 |
34 |
35 |
39 |
40 |
46 |
47 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/docmd/content/plugins/admob.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'AdMob Plugin'
3 | description: 'Integrating Google AdMob advertisements.'
4 | icon: 'dollar-sign'
5 | ---
6 |
7 | This plugin facilitates the integration of Google AdMob ads (Banner, Interstitial, and Rewarded) into your Smart WebView application.
8 |
9 | ::: callout tip
10 | All Premium Plugins are now available for free and open source to developers. Consider becoming **[Project Sponsor](https://github.com/sponsors/mgks)**.
11 | :::
12 |
13 | ---
14 |
15 | ## Setup and Configuration
16 |
17 | 1. **AdMob App ID:**
18 | * Add your AdMob App ID to the `admob_app_id` string in `app/src/main/res/values/ads.xml`.
19 | 2. **Enable Plugin:** Ensure `AdMobPlugin` is listed in the `plugins.enabled` property in `app/src/main/assets/swv.properties`.
20 | ```bash
21 | # In swv.properties
22 | plugins.enabled=AdMobPlugin,DialogPlugin,...
23 | ```
24 | 3. **Configure Ad Units:** In `Playground.java`, replace the default test ad unit IDs with your real ad unit IDs for production. This keeps your production keys out of the plugin's source code.
25 | ```java
26 | // In Playground.java
27 | runPluginAction("AdMobPlugin", plugin -> {
28 | Map config = SWVContext.getPluginManager().getPluginConfig("AdMobPlugin");
29 | if (config != null) {
30 | config.put("bannerAdUnitId", "YOUR_BANNER_ID");
31 | config.put("interstitialAdUnitId", "YOUR_INTERSTITIAL_ID");
32 | config.put("rewardedAdUnitId", "YOUR_REWARDED_ID");
33 | }
34 | });
35 | ```
36 |
37 | ---
38 | ## Usage
39 |
40 | The plugin can be controlled from native code or via a JavaScript interface.
41 |
42 | ### Displaying Ads from JavaScript
43 |
44 | The plugin injects a `window.AdMob` object into your web page.
45 |
46 | ```javascript
47 | // Show a banner ad at the bottom of the screen
48 | window.AdMob.showBanner();
49 |
50 | // Hide the banner ad
51 | window.AdMob.hideBanner();
52 |
53 | // Show an interstitial ad (if one is loaded)
54 | window.AdMob.showInterstitial();
55 |
56 | // Show a rewarded ad (if one is loaded)
57 | window.AdMob.showRewarded();
58 | ```
59 |
60 | ### Safe Calling Pattern (Avoiding Race Conditions)
61 | If you try to call `window.AdMob` immediately when your page loads (e.g., in a footer script), the object might not be injected yet. Use a timeout loop to ensure it is ready:
62 |
63 | ```javascript
64 | function loadBanner() {
65 | if (window.AdMob) {
66 | window.AdMob.showBanner();
67 | } else {
68 | setTimeout(loadBanner, 500); // Retry in 500ms
69 | }
70 | }
71 | loadBanner();
72 | ```
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 | :omega
90 |
--------------------------------------------------------------------------------
/docmd/content/plugins/biometric-auth.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Biometric Auth Plugin'
3 | description: 'Securing the app with fingerprint or face unlock.'
4 | icon: 'fingerprint'
5 | ---
6 |
7 | This plugin allows you to add an extra layer of security by requiring biometric authentication (fingerprint, face recognition) before granting access to your app.
8 |
9 | ::: callout tip
10 | All Premium Plugins are now available for free and open source to developers. Consider becoming **[Project Sponsor](https://github.com/sponsors/mgks)**.
11 | :::
12 |
13 | ---
14 |
15 | ## Secure, Non-Bypassable Gate
16 |
17 | The authentication flow is designed to be a true security gate.
18 |
19 | - **Total UI Lock:** When authentication is triggered, a full-screen overlay immediately blocks all app content. The native Toolbar and navigation drawer are also hidden and disabled.
20 | - **Guided Security Setup:** If the user has no screen lock, they are prompted to set one up and are guided to the Android Security Settings.
21 | - **Persistent Lock:** Resuming the app from the background will re-trigger authentication, preventing bypass.
22 |
23 | ---
24 |
25 | ## Setup and Configuration
26 |
27 | 1. **Enable Plugin:** Ensure `BiometricPlugin` is listed in the `plugins.enabled` property in `app/src/main/assets/swv.properties`.
28 | 2. **Configure Auth on Launch:** To enable authentication every time the app starts, set the `biometric.trigger.launch` property to `true` in `swv.properties`.
29 |
30 | ```bash
31 | # In swv.properties
32 | plugins.enabled=BiometricPlugin,...
33 |
34 | # Require authentication every time the app starts or resumes.
35 | biometric.trigger.launch=true
36 | ```
37 | If `false` (the default), authentication will only be triggered manually from your JavaScript.
38 |
39 | ---
40 |
41 | ## Usage
42 |
43 | ### Triggering Authentication from JavaScript
44 |
45 | The plugin injects a `window.Biometric` object. You can call this to lock a specific feature or section of your app.
46 |
47 | ```javascript
48 | // Request biometric authentication
49 | window.Biometric.authenticate();
50 | ```
51 |
52 | ### Callbacks in JavaScript
53 |
54 | Define callback functions to handle the result of the authentication attempt.
55 |
56 | ```javascript
57 | // Called on successful authentication
58 | window.Biometric.onAuthSuccess = function() {
59 | console.log("Authentication successful!");
60 | };
61 |
62 | // Called if there's an error (e.g., no hardware, lock screen not set up)
63 | window.Biometric.onAuthError = function(errorMessage) {
64 | console.error("Authentication error:", errorMessage);
65 | };
66 |
67 | // Called when the fingerprint/face is not recognized.
68 | window.Biometric.onAuthFailed = function() {
69 | console.warn("Authentication failed. Please try again.");
70 | };
71 | ```
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Smart WebView
3 | A simple use webview integrated w/ native features to help build advanced hybrid applications.
4 | Image View
5 | Loading
6 | Loading...
7 | github.com/mgks
8 | Failed to Find Location
9 | Please Check Your GPS Location.
10 | Please check if your GPS is enabled, if not, please set it on high accuracy from location settings.
11 | Allow Permission for Location
12 | Give this app permission to check your location.
13 | Please allow this app to know your location for better overall experience.
14 | Downloading file...
15 | Downloading File
16 | File Chooser
17 | Something Went Wrong!
18 | Please check your Network Connection!
19 | Share with your Friends
20 | Important Alerts
21 | Get latest and important alerts.
22 | Search
23 | Exit
24 | Open
25 | Close
26 | Enter your query here
27 | Connecting...
28 | Exit confirmation
29 | Are you sure you want to exit?
30 | Printing complete
31 | Printing failed
32 | No internet connection!
33 | Error printing!
34 | Launching app preview...
35 | Authenticate
36 | Documentation
37 | Home
38 | Plugins
39 | Firebase Messaging
40 | GPS
41 | URL Handling
42 | Changelog
43 | Need Help?
44 | Play Store Guide
45 | Dark Mode
46 |
47 |
--------------------------------------------------------------------------------
/app/src/main/java/mgks/os/swv/ShareActivity.java:
--------------------------------------------------------------------------------
1 | package mgks.os.swv;
2 |
3 | /*
4 | Smart WebView v8
5 | https://github.com/mgks/Android-SmartWebView
6 |
7 | A modern, open-source WebView wrapper for building advanced hybrid Android apps.
8 | Native features, modular plugins, and full customisation—built for developers.
9 |
10 | - Documentation: https://mgks.github.io/Android-SmartWebView/documentation
11 | - Plugins: https://mgks.github.io/Android-SmartWebView/documentation/plugins
12 | - Discussions: https://github.com/mgks/Android-SmartWebView/discussions
13 | - Sponsor the Project: https://github.com/sponsors/mgks
14 |
15 | MIT License — https://opensource.org/licenses/MIT
16 |
17 | Mentioning Smart WebView in your project helps others find it and keeps the dev loop alive.
18 | */
19 |
20 | import android.content.Intent;
21 | import android.net.Uri;
22 | import android.os.Bundle;
23 | import androidx.appcompat.app.AppCompatActivity;
24 | import android.widget.Toast;
25 |
26 | import java.util.ArrayList;
27 |
28 | public class ShareActivity extends AppCompatActivity {
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 |
33 | // Get intent, action and MIME type
34 | Intent intent = getIntent();
35 | String action = intent.getAction();
36 | String type = intent.getType();
37 |
38 | if (Intent.ACTION_SEND.equals(action) && type != null) {
39 | if("text/plain".equals(type)){
40 | handleSendText(intent); // Handle text being sent
41 | }else if(type.startsWith("image/")){
42 | handleSendImage(intent); // Handle single image being sent
43 | }
44 | }else if(Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null){
45 | if(type.startsWith("image/")){
46 | handleSendMultipleImages(intent); // Handle multiple images being sent
47 | }
48 | }else{
49 | Intent to_main = new Intent(this, MainActivity.class);
50 | startActivity(to_main);
51 | }
52 | }
53 |
54 | void handleSendText(Intent intent) {
55 | String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
56 | if (sharedText != null) {
57 | Intent i = new Intent(getBaseContext(), MainActivity.class);
58 | i.putExtra("s_uri", sharedText);
59 | startActivity(i);
60 | finish();
61 | }
62 | }
63 |
64 | // ~ This thing kinda not working at the moment -_-
65 | private void handleSendImage(Intent intent) {
66 | Uri imageUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
67 | if (imageUri != null) {
68 | Intent i = new Intent(this, MainActivity.class);
69 | i.putExtra("s_img", imageUri.toString());
70 | startActivity(i);
71 | finish();
72 | } else {
73 | Toast.makeText(this, "Error occurred, URI is invalid", Toast.LENGTH_LONG).show();
74 | }
75 | }
76 |
77 | void handleSendMultipleImages(Intent intent) {
78 | ArrayList imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
79 | if (imageUris != null) {
80 | // update UI to reflect multiple images being shared
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/docmd/content/features/firebase-messaging.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Firebase Messaging'
3 | description: 'Setting up and using Firebase push notifications.'
4 | icon: 'bell'
5 | ---
6 |
7 | Smart WebView integrates Firebase Cloud Messaging (FCM) to enable push notifications.
8 |
9 | ---
10 |
11 | ## Setup
12 |
13 | Refer to the [Getting Started](/Android-SmartWebView/documentation/getting-started#step-2-add-firebase-configuration-important) guide for the initial step of adding the `google-services.json` file to your project. This is mandatory for FCM.
14 |
15 | ::: card
16 | [Official FCM Android Setup Guide](https://firebase.google.com/docs/cloud-messaging/android/client)
17 | :::
18 |
19 | ---
20 |
21 | ## How it Works
22 |
23 | * **Token Generation:** The Firebase SDK automatically generates a unique registration token. The `Firebase.java` service listens for new tokens (`onNewToken`) and stores the latest token in `SmartWebView.fcm_token`. The `Functions.fcm_token()` method attempts to retrieve this and set it as a cookie (`FCM_TOKEN=...`) for your web application to access.
24 | * **Receiving Messages:**
25 | * **Foreground:** `Firebase.java`'s `onMessageReceived` is triggered, and a notification is manually displayed.
26 | * **Background/Closed:** The Firebase SDK automatically handles displaying notifications sent with a `notification` payload.
27 | * **Handling Clicks:** Notifications can include a `data` payload with a `uri` key. When the user taps the notification, the app opens and loads the specified `uri`. If no `uri` is provided, it defaults to the main `ASWV_URL`.
28 |
29 | ---
30 |
31 | ## Sending Notifications
32 |
33 | Use the Firebase Console or the FCM HTTP v1 API to send notifications.
34 |
35 | **Example POST Request (FCM HTTP v1 API):**
36 |
37 | ```json
38 | // POST https://fcm.googleapis.com/v1/projects/YOUR_PROJECT_ID/messages:send
39 | {
40 | "message": {
41 | "token": "DEVICE_REGISTRATION_TOKEN", // <-- Get this from the device
42 | "notification": {
43 | "title": "Your Notification Title",
44 | "body": "This is the main message body."
45 | },
46 | "android": {
47 | "notification": {
48 | "click_action": "OPEN_URI"
49 | }
50 | },
51 | "data": { // Custom data payload
52 | "uri": "https://your-website.com/specific-page",
53 | "nid": "unique_notification_id_123"
54 | }
55 | }
56 | }
57 | ```
58 |
59 | **Headers:**
60 |
61 | * `Content-Type: application/json`
62 | * `Authorization: Bearer YOUR_OAUTH2_ACCESS_TOKEN`
63 |
64 | ::: callout tip
65 | The `FCM_TOKEN` cookie can be read by your website's JavaScript to send the token to your server.
66 | :::
67 |
68 | ---
69 |
70 | ## Customization
71 |
72 | * **Notification Channel:** Customize the channel ID (`SmartWebView.asw_fcm_channel`) and names/descriptions in `app/src/main/res/values/strings.xml`. This is required for Android 8.0+.
73 | * **Notification Icon:** Set the icon in `Firebase.java` via `.setSmallIcon()`.
74 | * **Data Handling:** Modify `onMessageReceived` in `Firebase.java` to process custom `data` payloads for more complex interactions.
--------------------------------------------------------------------------------
/docmd/content/plugins/playground.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Playground'
3 | description: 'Configuring and testing plugins using Playground.java.'
4 | icon: 'flask'
5 | ---
6 |
7 | The `Playground.java` class is a dedicated component designed to facilitate plugin configuration, development, and testing. It acts as a sandbox where you can set plugin-specific options and experiment with features without modifying the core plugin source code.
8 |
9 | ::: callout tip
10 | All Premium Plugins are now available for free and open source to developers. Consider becoming **[Project Sponsor](https://github.com/sponsors/mgks)**.
11 | :::
12 |
13 | ---
14 |
15 | ## Purpose
16 |
17 | * **Plugin Configuration:** The primary place to set runtime options for plugins (e.g., providing AdMob ad unit IDs, enabling biometric authentication on launch).
18 | * **Centralized Testing:** Provides a single place to run diagnostic checks and add UI elements to the web page for manually testing plugin functionality.
19 | * **Initialization Hook:** Ensures that plugin configurations and tests only run *after* the core plugin system is fully initialized and ready.
20 | * **Fail-Safe Diagnostics:** Contains a robust system (`runPluginDiagnostic`) to test plugins without crashing the app if a plugin is missing or fails.
21 | * **Example Implementation:** Serves as a clear example of how to get a plugin instance from the `PluginManager` and interact with it.
22 |
23 | ---
24 |
25 | ## How It Works
26 |
27 | The `Playground` is initialized in `MainActivity`. The `PluginManager` calls its `onPageFinished` method after a page loads, which triggers two main actions if the playground is enabled in `swv.properties`:
28 |
29 | 1. **`configurePlugins()`:** This method applies configurations to any enabled plugins. For example, it sets the ad unit IDs for the `AdMobPlugin`.
30 | 2. **`runAllDiagnostics()` and `setupPluginDemoUI()`:** These methods inject a floating panel with buttons into the web page, allowing you to manually trigger and test each plugin's features.
31 |
32 | ---
33 |
34 | ## Configuring a Plugin
35 |
36 | To configure a plugin, modify the `configurePlugins` method in `Playground.java`.
37 |
38 | ```java
39 | // Inside configurePlugins() in Playground.java
40 |
41 | // BiometricPlugin Configuration
42 | runPluginAction("BiometricPlugin", plugin -> {
43 | Map config = SWVContext.getPluginManager().getPluginConfig("BiometricPlugin");
44 | if (config != null) {
45 | // Set to true to require auth every time the app starts
46 | config.put("authOnAppLaunch", true);
47 | }
48 | });
49 |
50 | // AdMobPlugin Configuration
51 | runPluginAction("AdMobPlugin", plugin -> {
52 | Map config = SWVContext.getPluginManager().getPluginConfig("AdMobPlugin");
53 | if (config != null) {
54 | // Replace with your real AdMob unit IDs for production
55 | config.put("bannerAdUnitId", "ca-app-pub-3940256099942544/6300978111");
56 | config.put("interstitialAdUnitId", "ca-app-pub-3940256099942544/1033173712");
57 | }
58 | });
59 | ```
60 | By using the `Playground`, you can effectively develop, configure, and debug your plugins in an isolated and controlled manner.
--------------------------------------------------------------------------------
/docmd/content/plugins/dialog.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Dialog Plugin'
3 | description: 'Showing native Android alert dialogs from JavaScript.'
4 | icon: 'message-square'
5 | ---
6 |
7 | The `DialogPlugin` provides a generic interface for showing native Android alert dialogs from your web content, ensuring a consistent and platform-native user experience.
8 |
9 | ---
10 |
11 | ## How It Works
12 |
13 | The plugin injects a JavaScript object (`window.Dialog`) into your WebView. You can call its `show` method with a set of options and a callback function to display a dialog and handle the user's response asynchronously.
14 |
15 | ---
16 |
17 | ## Enabling the Plugin
18 |
19 | Ensure `DialogPlugin` is listed in the `plugins.enabled` property in `app/src/main/assets/swv.properties`.
20 |
21 | ```bash
22 | # In swv.properties
23 | plugins.enabled=DialogPlugin,ToastPlugin,...
24 | ```
25 |
26 | ---
27 |
28 | ## Usage from JavaScript
29 |
30 | The `window.Dialog.show()` function is the primary way to interact with this plugin.
31 |
32 | ### Basic Alert Dialog (One Button)
33 |
34 | ```javascript
35 | window.Dialog.show({
36 | title: 'Update Complete',
37 | message: 'Your profile has been saved successfully.',
38 | positiveText: 'OK' // 'positiveText' is the only required button text
39 | }, function(result) {
40 | // Callback receives 'positive' when the button is clicked.
41 | console.log('Alert dialog was closed.');
42 | });
43 | ```
44 |
45 | ### Confirmation Dialog (Two Buttons)
46 |
47 | ```javascript
48 | window.Dialog.show({
49 | title: 'Confirm Deletion',
50 | message: 'Are you sure you want to delete this item? This action cannot be undone.',
51 | positiveText: 'Delete',
52 | negativeText: 'Cancel'
53 | }, function(result) {
54 | if (result === 'positive') {
55 | // User clicked 'Delete'
56 | console.log('Proceeding with deletion...');
57 | } else {
58 | // User clicked 'Cancel' or dismissed the dialog
59 | console.log('Deletion cancelled.');
60 | }
61 | });
62 | ```
63 |
64 | ### Full Dialog (Three Buttons)
65 |
66 | ```javascript
67 | window.Dialog.show({
68 | title: 'Save Changes',
69 | message: 'You have unsaved changes. What would you like to do?',
70 | positiveText: 'Save',
71 |
72 | negativeText: 'Discard',
73 | neutralText: 'Save as Draft'
74 | }, function(result) {
75 | switch (result) {
76 | case 'positive':
77 | // User clicked 'Save'
78 | break;
79 | case 'negative':
80 | // User clicked 'Discard'
81 | break;
82 | case 'neutral':
83 | // User clicked 'Save as Draft'
84 | break;
85 | case 'cancel':
86 | // User dismissed the dialog (e.g., back button)
87 | break;
88 | }
89 | });
90 | ```
91 |
92 | ### Available Options
93 |
94 | | Key | Type | Default | Description |
95 | |----------------|--------|---------|-------------------------------------------|
96 | | `title` | String | "Alert" | The title of the dialog. |
97 | | `message` | String | "" | The main body text of the dialog. |
98 | | `positiveText` | String | "OK" | Text for the positive (confirm) button. |
99 | | `negativeText` | String | `null` | Text for the negative (cancel) button. |
100 | | `neutralText` | String | `null` | Text for the neutral (alternative) button.|
101 |
102 | ### Callback Results
103 |
104 | The callback function receives a single string argument indicating how the dialog was closed:
105 | * `'positive'`
106 | * `'negative'`
107 | * `'neutral'`
108 | * `'cancel'` (if the dialog is dismissed by tapping outside or using the back button)
--------------------------------------------------------------------------------
/app/src/main/assets/web/offline.html:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 | Smart WebView - Offline
11 |
12 |
13 |
14 |
15 |
16 |
17 |
You are currently offline. Some features below work without an internet connection.
101 |
102 |
103 |
104 |
109 |
110 |
--------------------------------------------------------------------------------
/docmd/content/play-store-guide.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Google Play Submission Guide'
3 | description: 'Steps and best practices for publishing your app to the Google Play Store.'
4 | icon: 'google-play'
5 | ---
6 |
7 | Publishing your Smart WebView application to the Google Play Store involves several key steps. This guide provides a checklist to help ensure a smooth submission process.
8 |
9 | ---
10 |
11 | ### Step 1: Final Configuration
12 |
13 | Before building your app for release, finalize its configuration in `app/src/main/assets/swv.properties`.
14 |
15 | * **Disable Debug Mode:** This is the most important step.
16 | ```bash
17 | # In swv.properties
18 | debug.mode=false
19 | ```
20 | * **Disable Playground:** The testing UI should not be in your production app.
21 | ```bash
22 | # In swv.properties
23 | plugins.playground.enabled=false
24 | ```
25 | * **Set Production URL:** Ensure `app.url` points to your live website.
26 | * **Review Feature Toggles:** Double-check all `feature.*` and `plugins.*` flags to make sure only the features you need are enabled. Disabling unused features reduces the number of permissions your app requests.
27 |
28 | ---
29 |
30 | ### Step 2: App Identity and Versioning
31 |
32 | Your app's identity and version are now controlled from `swv.properties`. The `app/build.gradle` file reads these values automatically.
33 |
34 | * **Application ID:** Set a unique `build.application.id`. This is your app's permanent ID on the Play Store.
35 | * **Versioning:**
36 | * `build.version.code`: An integer that must be incremented with every new release you upload.
37 | * `build.version.name`: A public-facing string for your users (e.g., "1.0.1").
38 |
39 | ```bash
40 | # In swv.properties
41 |
42 | build.application.id=com.yourcompany.yourapp
43 | build.version.code=1
44 | build.version.name=1.0
45 | ```
46 |
47 | ---
48 |
49 | ### Step 3: Build a Release App Bundle
50 |
51 | Google Play requires you to upload your app as an **Android App Bundle (AAB)**.
52 |
53 | 1. **Generate a Signing Key:** You must sign your app with a cryptographic key. If you don't have one, go to `Build > Generate Signed Bundle / APK...` in Android Studio, select "Android App Bundle", and follow the prompts to create a new "key store".
54 | ::: callout danger
55 | **Safeguard your key!** You will lose the ability to publish updates for your app if you lose your signing key. Back it up securely.
56 | :::
57 | 2. **Build the AAB:** Use the `Build > Generate Signed Bundle / APK...` menu to build the signed AAB file. Android Studio will place it in `app/release/`.
58 |
59 | ---
60 |
61 | ### Step 4: Prepare Your Store Listing
62 |
63 | In the [Google Play Console](https://play.google.com/console):
64 |
65 | * **Create Your App:** Fill in the initial details like app name and language.
66 | * **Set Up Store Listing:** Provide a compelling title, short description, and full description.
67 | * **Upload Graphics:** You will need a high-resolution app icon (512x512) and at least two feature graphic screenshots.
68 |
69 | ---
70 |
71 | ### Step 5: Content and Policy Declarations
72 |
73 | This is a critical section for WebView-based apps.
74 |
75 | * **Privacy Policy:** You **must** provide a link to a publicly accessible privacy policy. This is non-negotiable, especially if your app uses `ASWP_LOCATION`, `ASWP_CAMUPLOAD`, or `ASWV_GTAG`.
76 | * **Permissions Declaration:** If your app requests sensitive permissions (like Location), you must explain why your app needs them in the Play Console's "App content" section.
77 | * **Content Rating:** Complete the content rating questionnaire. Answer honestly to avoid rejection.
78 | * **Ads:** Declare whether your app contains ads. If you use the AdMob plugin, you must select "Yes".
79 | * **Webviews and Spam Policy:** In your app's description, highlight the features that Smart WebView adds (e.g., push notifications, native sharing, QR scanning). This shows that your app provides more value than simply wrapping a website, which helps comply with Google's [Minimum Functionality Policy](https://support.google.com/googleplay/android-developer/answer/9898820).
80 |
81 | ---
82 |
83 | ### Step 6: Upload and Release
84 |
85 | 1. **Upload Your AAB:** In the Play Console, create a new release (e.g., on the "Internal testing" or "Production" track) and upload your signed AAB file.
86 | 2. **Review Pre-launch Reports:** After uploading, Google automatically tests your app on various real devices. Check the "Pre-launch report" for any crashes or layout issues.
87 | 3. **Roll Out:** Once you've filled in all required sections and reviewed the reports, you can submit your app for review.
88 |
89 | The first review typically takes longer (a few days), while subsequent updates are often faster.
--------------------------------------------------------------------------------
/app/src/main/java/mgks/os/swv/Firebase.java:
--------------------------------------------------------------------------------
1 | package mgks.os.swv;
2 |
3 | /*
4 | Smart WebView v8
5 | https://github.com/mgks/Android-SmartWebView
6 |
7 | A modern, open-source WebView wrapper for building advanced hybrid Android apps.
8 | Native features, modular plugins, and full customisation—built for developers.
9 |
10 | - Documentation: https://mgks.github.io/Android-SmartWebView/documentation
11 | - Plugins: https://mgks.github.io/Android-SmartWebView/documentation/plugins
12 | - Discussions: https://github.com/mgks/Android-SmartWebView/discussions
13 | - Sponsor the Project: https://github.com/sponsors/mgks
14 |
15 | MIT License — https://opensource.org/licenses/MIT
16 |
17 | Mentioning Smart WebView in your project helps others find it and keeps the dev loop alive.
18 | */
19 |
20 | import android.app.NotificationChannel;
21 | import android.app.NotificationManager;
22 | import android.app.PendingIntent;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.media.RingtoneManager;
26 | import android.net.Uri;
27 | import android.os.Build;
28 | import android.util.Log;
29 |
30 | import androidx.core.app.NotificationCompat;
31 |
32 | import com.google.firebase.messaging.FirebaseMessagingService;
33 | import com.google.firebase.messaging.RemoteMessage;
34 |
35 | public class Firebase extends FirebaseMessagingService {
36 |
37 | private final String fcm_channel = SWVContext.asw_fcm_channel;
38 |
39 | @Override
40 | public void onNewToken(String s) {
41 | super.onNewToken(s);
42 | Log.d("Firebase", "onNewToken() called"); // Prominent log to confirm if it's called
43 | if (!s.isEmpty()) {
44 | SWVContext.fcm_token = s;
45 | Log.d("TOKEN_REFRESHED", s); // Log the new token
46 | } else {
47 | Log.d("TOKEN_REFRESHED", "NULL >> FAILED");
48 | }
49 | }
50 |
51 | @Override
52 | public void onMessageReceived(RemoteMessage message) {
53 | if (message.getNotification() != null) {
54 | String title = message.getNotification().getTitle();
55 | String body = message.getNotification().getBody();
56 | String uri = message.getData().get("uri");
57 | String click_action = message.getNotification().getClickAction();
58 |
59 | // Use default values if null
60 | if (uri == null) {
61 | uri = SWVContext.ASWV_URL;
62 | }
63 | if (click_action == null) {
64 | click_action = "OPEN_URI";
65 | }
66 |
67 | Log.d("FCM_MESSAGE", "Title: " + title + ", Body: " + body + ", URI: " + uri + ", Click Action: " + click_action);
68 |
69 | sendMyNotification(title, body, click_action, uri, message.getData().get("tag"), message.getData().get("nid"), this); // Pass context from here
70 | }
71 | }
72 |
73 | public void sendMyNotification(String title, String message, String click_action, String uri, String tag, String nid, Context context) {
74 | // Create an intent based on the URI
75 | Intent intent;
76 | if (uri == null || uri.isEmpty() || uri.startsWith("file://")) {
77 | intent = new Intent(context, MainActivity.class);
78 | } else {
79 | intent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
80 | }
81 | intent.setAction(click_action);
82 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
83 |
84 | // Create a PendingIntent
85 | PendingIntent pendingIntent;
86 | pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
87 |
88 | // Use a unique ID for each notification or a more robust default
89 | int notification_id = nid != null ? Integer.parseInt(nid) : SWVContext.ASWV_FCM_ID;
90 |
91 | // Build the notification
92 | NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, fcm_channel)
93 | .setSmallIcon(R.mipmap.ic_launcher) // Use a specific notification icon if available
94 | .setContentTitle(title) // Remove notification ID from title
95 | .setContentText(message)
96 | .setAutoCancel(true)
97 | .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
98 | .setContentIntent(pendingIntent)
99 | .setPriority(NotificationCompat.PRIORITY_HIGH);
100 |
101 | // Get the NotificationManager
102 | NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
103 |
104 | // Create a notification channel for Android Oreo and above
105 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
106 | NotificationChannel channel = new NotificationChannel(fcm_channel, "SWV Channel", NotificationManager.IMPORTANCE_HIGH); // Use a more descriptive channel name
107 | notificationManager.createNotificationChannel(channel);
108 | }
109 |
110 | // Notify
111 | notificationManager.notify(notification_id, notificationBuilder.build());
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/mgks/os/swv/plugins/QRScannerPlugin.java:
--------------------------------------------------------------------------------
1 | package mgks.os.swv.plugins;
2 |
3 | /*
4 | QR/Barcode Scanner Plugin for Smart WebView
5 |
6 | This plugin provides an interface to scan QR codes and barcodes using the device's camera.
7 |
8 | FEATURES:
9 | - Initiates a camera scanning view.
10 | - Returns scanned data back to JavaScript.
11 | - Configurable scanner prompt and options.
12 | */
13 |
14 | import android.app.Activity;
15 | import android.content.Intent;
16 | import android.util.Log;
17 | import android.webkit.JavascriptInterface;
18 | import android.webkit.WebView;
19 |
20 | import androidx.activity.result.ActivityResultLauncher;
21 | import androidx.annotation.NonNull;
22 | import androidx.appcompat.app.AppCompatActivity;
23 |
24 | import com.journeyapps.barcodescanner.ScanContract;
25 | import com.journeyapps.barcodescanner.ScanIntentResult;
26 | import com.journeyapps.barcodescanner.ScanOptions;
27 |
28 | import java.util.HashMap;
29 | import java.util.Map;
30 |
31 | import mgks.os.swv.Functions;
32 | import mgks.os.swv.PluginInterface;
33 | import mgks.os.swv.PluginManager;
34 | import mgks.os.swv.R;
35 |
36 | public class QRScannerPlugin implements PluginInterface {
37 | private static final String TAG = "QRScannerPlugin";
38 | private Activity activity;
39 | private WebView webView;
40 | // MODIFY the type parameter from Intent to ScanOptions
41 | private ActivityResultLauncher launcher;
42 |
43 | static {
44 | PluginManager.registerPlugin(new QRScannerPlugin(), new HashMap<>());
45 | }
46 |
47 | @Override
48 | public void initialize(Activity activity, WebView webView, Functions functions, Map config) {
49 | this.activity = activity;
50 | this.webView = webView;
51 |
52 | webView.addJavascriptInterface(new QRScannerJSInterface(), "QRScannerInterface");
53 | Log.d(TAG, "QRScannerPlugin initialized.");
54 | }
55 |
56 | // MODIFY the method signature to accept the correct launcher type
57 | public void setLauncher(ActivityResultLauncher launcher) {
58 | this.launcher = launcher;
59 | }
60 |
61 | // MODIFY this method to accept the new result type
62 | public void handleScanResult(ScanIntentResult result) {
63 | String contents = result.getContents();
64 | if (contents == null) {
65 | Log.d(TAG, "Scan cancelled");
66 | String script = "if(window.QRScanner && window.QRScanner.onScanCancelled) { window.QRScanner.onScanCancelled(); }";
67 | evaluateJavascript(script);
68 | } else {
69 | Log.d(TAG, "Scanned QR/Barcode: " + contents);
70 | // Use String.format to prevent JS injection issues
71 | String script = String.format("if(window.QRScanner && window.QRScanner.onScanSuccess) { window.QRScanner.onScanSuccess('%s'); }", contents);
72 | evaluateJavascript(script);
73 | }
74 | }
75 |
76 | @Override
77 | public String getPluginName() { return "QRScannerPlugin"; }
78 |
79 | @Override
80 | public void onPageFinished(String url) {
81 | String js = "if(!window.QRScanner){window.QRScanner={scan:function(){if(window.QRScannerInterface)window.QRScannerInterface.startScan();},onScanSuccess:null,onScanCancelled:null};console.log('QRScanner JS interface ready.');}";
82 | evaluateJavascript(js);
83 | }
84 |
85 | public void startScan() {
86 | if (activity == null || launcher == null) {
87 | Log.e(TAG, "Plugin not ready or launcher not set by MainActivity.");
88 | return;
89 | }
90 | // Use the modern ScanOptions builder
91 | ScanOptions options = new ScanOptions();
92 | options.setDesiredBarcodeFormats(ScanOptions.ALL_CODE_TYPES);
93 | options.setPrompt("Scan a barcode or QR code");
94 | options.setCameraId(0);
95 | options.setBeepEnabled(true);
96 | options.setBarcodeImageEnabled(true);
97 | options.setOrientationLocked(false); // Allow orientation changes
98 |
99 | launcher.launch(options);
100 | }
101 |
102 | public class QRScannerJSInterface {
103 | @JavascriptInterface
104 | public void startScan() {
105 | // Ensure this runs on the main thread
106 | activity.runOnUiThread(QRScannerPlugin.this::startScan);
107 | }
108 | }
109 |
110 | // --- Unused interface methods ---
111 | @Override public void onActivityResult(int r, int c, Intent d) {}
112 | @Override public void onRequestPermissionsResult(int r, @NonNull String[] p, @NonNull int[] g) {}
113 | @Override public boolean shouldOverrideUrlLoading(WebView v, String u) { return false; }
114 | @Override public void onPageStarted(String url) {}
115 | @Override public void onResume() {}
116 | @Override public void onPause() {}
117 | @Override public void onDestroy() {}
118 | @Override public void evaluateJavascript(String script) {
119 | if (webView != null) webView.evaluateJavascript(script, null);
120 | }
121 | }
--------------------------------------------------------------------------------
/app/src/main/java/mgks/os/swv/plugins/ImageCompressionPlugin.java:
--------------------------------------------------------------------------------
1 | package mgks.os.swv.plugins;
2 |
3 | /*
4 | Image Compression Plugin for Smart WebView
5 |
6 | This plugin compresses images before they are uploaded.
7 |
8 | FEATURES:
9 | - Compresses base64 encoded images.
10 | - Configurable quality and size.
11 | - Returns a compressed base64 string to JavaScript.
12 | */
13 |
14 | import android.app.Activity;
15 | import android.content.Intent;
16 | import android.graphics.Bitmap;
17 | import android.graphics.BitmapFactory;
18 | import android.os.Handler;
19 | import android.os.Looper;
20 | import android.util.Base64;
21 | import android.util.Log;
22 | import android.webkit.JavascriptInterface;
23 | import android.webkit.WebView;
24 |
25 | import androidx.annotation.NonNull;
26 |
27 | import java.io.ByteArrayOutputStream;
28 | import java.util.HashMap;
29 | import java.util.Map;
30 |
31 | import mgks.os.swv.Functions;
32 | import mgks.os.swv.PluginInterface;
33 | import mgks.os.swv.PluginManager;
34 |
35 | public class ImageCompressionPlugin implements PluginInterface {
36 | private static final String TAG = "ImageCompressionPlugin";
37 | private Activity activity;
38 | private WebView webView;
39 | private int quality;
40 |
41 | static {
42 | Map config = new HashMap<>();
43 | config.put("quality", 80); // Default compression quality (0-100)
44 | PluginManager.registerPlugin(new ImageCompressionPlugin(), config);
45 | }
46 |
47 | @Override
48 | public void initialize(Activity activity, WebView webView, Functions functions, Map config) {
49 | this.activity = activity;
50 | this.webView = webView;
51 | this.quality = (int) config.getOrDefault("quality", 80);
52 | webView.addJavascriptInterface(new ImageCompressionJSInterface(), "ImageCompressionInterface");
53 | Log.d(TAG, "ImageCompressionPlugin initialized with quality: " + this.quality);
54 | }
55 |
56 | @Override
57 | public String getPluginName() {
58 | return "ImageCompressionPlugin";
59 | }
60 |
61 | @Override
62 | public void onPageFinished(String url) {
63 | String js = "if(!window.ImageCompressor){window.ImageCompressor={compress:function(base64,cb){if(window.ImageCompressionInterface){window.ImageCompressor.callback=cb;window.ImageCompressionInterface.compress(base64);}},callback:null};console.log('ImageCompressor JS interface ready.');}";
64 | evaluateJavascript(js);
65 | }
66 |
67 | public void compress(String base64String) {
68 | // This is a simplified example. For large images, this should be done on a background thread.
69 | try {
70 | // Remove header: "data:image/jpeg;base64,"
71 | String pureBase64 = base64String.substring(base64String.indexOf(",") + 1);
72 | byte[] decodedString = Base64.decode(pureBase64, Base64.DEFAULT);
73 | Bitmap bitmap = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
74 |
75 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
76 | bitmap.compress(Bitmap.CompressFormat.JPEG, this.quality, outputStream);
77 | byte[] compressedBytes = outputStream.toByteArray();
78 | String compressedBase64 = Base64.encodeToString(compressedBytes, Base64.DEFAULT);
79 |
80 | // Prepend the data URL scheme header back
81 | String finalBase64 = "data:image/jpeg;base64," + compressedBase64;
82 |
83 | Log.d(TAG, "Image compressed from " + base64String.length() + " to " + finalBase64.length() + " bytes.");
84 | String script = String.format("if(window.ImageCompressor && window.ImageCompressor.callback) { window.ImageCompressor.callback('%s'); }", finalBase64);
85 | evaluateJavascript(script);
86 |
87 | } catch (Exception e) {
88 | Log.e(TAG, "Image compression failed", e);
89 | String script = String.format("if(window.ImageCompressor && window.ImageCompressor.callback) { window.ImageCompressor.callback(null, '%s'); }", e.getMessage());
90 | evaluateJavascript(script);
91 | }
92 | }
93 |
94 | public class ImageCompressionJSInterface {
95 | @JavascriptInterface
96 | public void compress(String base64String) {
97 | // Run compression on the main thread for simplicity, but a background thread is recommended.
98 | new Handler(Looper.getMainLooper()).post(() -> ImageCompressionPlugin.this.compress(base64String));
99 | }
100 | }
101 |
102 | // Unused interface methods
103 | @Override public void onActivityResult(int r, int c, Intent d) {}
104 | @Override public void onRequestPermissionsResult(int r, @NonNull String[] p, @NonNull int[] g) {}
105 | @Override public boolean shouldOverrideUrlLoading(WebView v, String u) { return false; }
106 | @Override public void onResume() {}
107 | @Override public void onPause() {}
108 | @Override public void onPageStarted(String url) {}
109 | @Override public void onDestroy() {}
110 | @Override public void evaluateJavascript(String script) {
111 | if (webView != null) webView.evaluateJavascript(script, null);
112 | }
113 | }
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6 |
7 | We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8 |
9 | ## Our Standards
10 |
11 | Examples of behavior that contributes to a positive environment for our community include:
12 |
13 | * Demonstrating empathy and kindness toward other people
14 | * Being respectful of differing opinions, viewpoints, and experiences
15 | * Giving and gracefully accepting constructive feedback
16 | * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17 | * Focusing on what is best not just for us as individuals, but for the overall community
18 |
19 | Examples of unacceptable behavior include:
20 |
21 | * The use of sexualized language or imagery, and sexual attention or advances of any kind
22 | * Trolling, insulting or derogatory comments, and personal or political attacks
23 | * Public or private harassment
24 | * Publishing others' private information, such as a physical or email address, without their explicit permission
25 | * Other conduct which could reasonably be considered inappropriate in a professional setting
26 |
27 | ## Enforcement Responsibilities
28 |
29 | Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
32 |
33 | ## Scope
34 |
35 | This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
36 |
37 | ## Enforcement
38 |
39 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at hello@mgks.dev. All complaints will be reviewed and investigated promptly and fairly.
40 |
41 | All community leaders are obligated to respect the privacy and security of the reporter of any incident.
42 |
43 | ## Enforcement Guidelines
44 |
45 | Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
46 |
47 | ### 1. Correction
48 |
49 | **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
50 |
51 | **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
52 |
53 | ### 2. Warning
54 |
55 | **Community Impact**: A violation through a single incident or series of actions.
56 |
57 | **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
58 |
59 | ### 3. Temporary Ban
60 |
61 | **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
62 |
63 | **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
64 |
65 | ### 4. Permanent Ban
66 |
67 | **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
68 |
69 | **Consequence**: A permanent ban from any sort of public interaction within the community.
70 |
71 | ## Attribution
72 |
73 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
74 |
75 | Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
76 |
77 | For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
78 |
79 | [homepage]: https://www.contributor-covenant.org
80 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
81 | [Mozilla CoC]: https://github.com/mozilla/diversity
82 | [FAQ]: https://www.contributor-covenant.org/faq
83 | [translations]: https://www.contributor-covenant.org/translations
84 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 |
158 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
159 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
160 |
161 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
162 |
--------------------------------------------------------------------------------
/app/src/main/assets/web/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | Smart WebView v8 - Offline Script
3 | https://github.com/mgks/Android-SmartWebView
4 | */
5 |
6 | // This variable will store the theme detected by the native app
7 | let nativeThemePreference = 'light';
8 |
9 | document.addEventListener('DOMContentLoaded', function() {
10 |
11 | const imageInput = document.getElementById('add-img');
12 | const gallery = document.querySelector('.gallery');
13 | const MAX_WIDTH = 240;
14 |
15 | if (imageInput) {
16 | imageInput.addEventListener('change', function() {
17 | // The 'gallery' element might not exist on all pages (like error pages).
18 | const gallery = document.querySelector('.gallery');
19 | if (gallery) {
20 | gallery.innerHTML = ''; // Clear previous previews only if gallery exists.
21 | if (!this.files) return;
22 |
23 | for (const file of Array.from(this.files)) {
24 | const reader = new FileReader();
25 | reader.onload = function (e) {
26 | const img = document.createElement('img');
27 | img.src = e.target.result;
28 | img.onload = function() {
29 | const canvas = document.createElement('canvas');
30 | const ctx = canvas.getContext('2d');
31 | let width = img.width;
32 | let height = img.height;
33 |
34 | if (width > MAX_WIDTH) {
35 | height *= MAX_WIDTH / width;
36 | width = MAX_WIDTH;
37 | }
38 | canvas.width = width;
39 | canvas.height = height;
40 | ctx.drawImage(img, 0, 0, width, height);
41 | // The gallery is guaranteed to exist inside this block.
42 | gallery.appendChild(canvas);
43 | }
44 | };
45 | reader.readAsDataURL(file);
46 | }
47 | }
48 | });
49 | }
50 |
51 | // This function can be called by native code after location is fetched.
52 | window.updateLocationDisplay = function(lat, long) {
53 | const locElement = document.querySelector('.fetch-loc');
54 | if (locElement) {
55 | if (lat && long) {
56 | locElement.innerHTML = "Latitude: " + lat.toFixed(6) + " Longitude: " + long.toFixed(6);
57 | } else {
58 | locElement.innerHTML = "Could not retrieve location. Please ensure GPS is enabled and permissions are granted.";
59 | }
60 | }
61 | };
62 |
63 | // Theme switcher logic
64 | const themeSwitcher = document.getElementById('theme-switcher');
65 | if (themeSwitcher) {
66 | themeSwitcher.addEventListener('click', (event) => {
67 | if (event.target.tagName === 'BUTTON') {
68 | const theme = event.target.dataset.theme;
69 | setTheme(theme);
70 | }
71 | });
72 | }
73 | const savedTheme = localStorage.getItem('swv-theme');
74 | if (savedTheme && savedTheme !== 'system') {
75 | setTheme(savedTheme);
76 | } else {
77 | setTheme(nativeThemePreference, true);
78 | }
79 | });
80 |
81 | function fetchLocation() {
82 | const locElement = document.querySelector('.fetch-loc') || document.querySelector('.fetch-loc-area');
83 | if (locElement) {
84 | locElement.innerHTML = "
Fetching location from device...
";
85 | }
86 | // Call the new, non-conflicting object name
87 | if (window.SWVLocation) {
88 | window.SWVLocation.getCurrentPosition(function(lat, lng, error) {
89 | // In offline.html, updateLocationDisplay is global.
90 | // In docs/script.js, this logic is inside fetchLocation.
91 | // We'll make it robust for both.
92 | const displayDiv = document.querySelector('.fetch-loc') || document.querySelector('.fetch-loc-area');
93 | if (error) {
94 | displayDiv.innerHTML = "