[SpaccWebView.Android] Make actual Android library, upgrade to SDK 35

This commit is contained in:
2025-05-26 01:19:38 +02:00
parent 6313d99e30
commit fcbd40ea34
38 changed files with 206 additions and 121 deletions

View File

@ -12,9 +12,8 @@ android {
versionCode 1
versionName '1.0'
minSdk 1
//noinspection OldTargetApi
targetSdk 34
compileSdk 34
targetSdk 35
compileSdk 35
}
signingConfigs {
@ -42,6 +41,11 @@ android {
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
java {
@ -49,3 +53,7 @@ java {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
implementation project(":SpaccWebView")
}

View File

@ -13,16 +13,26 @@ import android.view.MenuItem;
import android.webkit.WebView;
import android.widget.EditText;
import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
import org.eu.spacc.spaccdotweb.android.helpers.DataMoveHelper;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
import org.eu.spacc.spaccdotweb.android.webview.SpaccWebViewClient;
import org.eu.spacc.spaccwebview.android.utils.ApiUtils;
import org.eu.spacc.spaccwebview.android.helpers.DataMoveHelper;
import org.eu.spacc.spaccwebview.android.SpaccWebViewActivity;
import org.eu.spacc.spaccwebview.android.webview.SpaccWebViewClient;
public class MainActivity extends SpaccWebViewActivity {
private ActionBar actionBar = null;
private Menu menu = null;
private long pageStartTime = 0;
private void refreshMenu(boolean started) {
if (menu != null) {
menu.findItem(R.id.stop).setVisible(started);
menu.findItem(R.id.reload).setVisible(!started);
menu.findItem(R.id.about_app).setVisible(webView.getConfig().getAboutPage() != null);
menu.findItem(R.id.backwards).setEnabled(webView.canGoBack());
menu.findItem(R.id.forward).setEnabled(webView.canGoForward());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -30,10 +40,9 @@ public class MainActivity extends SpaccWebViewActivity {
ApiUtils.apiRun(11, () -> this.actionBar = getActionBar());
DataMoveHelper.run(this, R.string.exit, R.string.move_app_data, R.string.move_app_data_info);
DataMoveHelper.run(this);
this.webView = findViewById(R.id.webview);
this.webView.setStrings(R.string.open_menu, R.string.open_externally_menu, R.string.copy_url_menu);
this.webView.setWebViewClient(new SpaccWebViewClient(this) {
@SuppressLint("UseRequiresApi")
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@ -41,16 +50,12 @@ public class MainActivity extends SpaccWebViewActivity {
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
pageStartTime = System.currentTimeMillis();
refreshMenu(true);
if (menu != null) {
menu.findItem(R.id.stop).setVisible(true);
menu.findItem(R.id.reload).setVisible(false);
menu.findItem(R.id.open_externally).setVisible(!ApiUtils.isInternalUrl(Uri.parse(url)));
menu.findItem(R.id.about_app).setVisible(webView.getConfig().getAboutPage() != null);
menu.findItem(R.id.backwards).setEnabled(webView.canGoBack());
menu.findItem(R.id.forward).setEnabled(webView.canGoForward());
}
if (actionBar != null) {
actionBar.setSubtitle(R.string.loading);
actionBar.setSubtitle(org.eu.spacc.spaccwebview.android.R.string.loading);
}
// if (justStarted) {
// new Handler().postDelayed(() -> Objects.requireNonNull(getActionBar()).hide(), 3000);
@ -63,10 +68,7 @@ public class MainActivity extends SpaccWebViewActivity {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
if (menu != null) {
menu.findItem(R.id.stop).setVisible(false);
menu.findItem(R.id.reload).setVisible(true);
}
refreshMenu(false);
if (actionBar != null && pageStartTime != 0) {
actionBar.setSubtitle("~" + (System.currentTimeMillis() - pageStartTime) + "ms");
}
@ -89,43 +91,33 @@ public class MainActivity extends SpaccWebViewActivity {
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.stop:
this.webView.stopLoading();
break;
case R.id.reload:
this.webView.reload();
break;
case R.id.backwards:
this.webView.goBack();
break;
case R.id.forward:
this.webView.goForward();
break;
case R.id.copy_url:
ApiUtils.writeToClipboard(this, webView.getUrl());
break;
case R.id.open_externally:
ApiUtils.openOrShareUrl(this, Uri.parse(webView.getUrl()));
break;
case R.id.exec_script:
EditText scriptText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle(R.string.execute_javascript)
.setView(scriptText)
.setPositiveButton("OK", (dialogInterface, i) -> webView.injectScript(scriptText.getText().toString()))
.setNeutralButton(R.string.cancel, null)
.show();
break;
case R.id.hide:
actionBar.hide();
break;
case R.id.exit:
finish();
break;
case R.id.about_app:
ApiUtils.openOrShareUrl(this, Uri.parse(webView.getConfig().getAboutPage()));
break;
int id = item.getItemId();
if (id == R.id.stop) {
webView.stopLoading();
} else if (id == R.id.reload) {
webView.reload();
} else if (id == R.id.backwards) {
webView.goBack();
} else if (id == R.id.forward) {
webView.goForward();
} else if (id == R.id.copy_url) {
ApiUtils.writeToClipboard(this, webView.getUrl());
} else if (id == R.id.open_externally) {
ApiUtils.openOrShareUrl(this, Uri.parse(webView.getUrl()));
} else if (id == R.id.exec_script) {
EditText scriptText = new EditText(this);
new AlertDialog.Builder(this)
.setTitle(org.eu.spacc.spaccwebview.android.R.string.execute_javascript)
.setView(scriptText)
.setPositiveButton("OK", (dialogInterface, i) -> webView.injectScript(scriptText.getText().toString()))
.setNeutralButton(org.eu.spacc.spaccwebview.android.R.string.cancel, null)
.show();
} else if (id == R.id.hide) {
actionBar.hide();
} else if (id == R.id.exit) {
finish();
} else if (id == R.id.about_app) {
ApiUtils.openOrShareUrl(this, Uri.parse(webView.getConfig().getAboutPage()));
}
return super.onOptionsItemSelected(item);
}

View File

@ -1,6 +1,6 @@
package com.example.spaccwebviewapplication;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewApplication;
import org.eu.spacc.spaccwebview.android.SpaccWebViewApplication;
public class MainApplication extends SpaccWebViewApplication {
// This proxy class can be left empty,

View File

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<org.eu.spacc.spaccdotweb.android.webview.SpaccWebView
<org.eu.spacc.spaccwebview.android.webview.SpaccWebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY about_app "About App">
<!ENTITY exit "Exit">
<!ENTITY hide "Hide">
<!ENTITY reload "Reload">
<!ENTITY stop_loading "Stop Loading">
<!ENTITY backwards "Backwards">
<!ENTITY forward "Forward">
<!ENTITY open "Open">
<!ENTITY open_externally "&open; Externally">
<!ENTITY copy_url "Copy URL">
<!ENTITY execute_javascript "Execute JavaScript">
]>
<resources>
<string name="app_name" translatable="false">SpaccWebView Application</string>
</resources>

View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,29 @@
plugins {
id 'com.android.library'
}
android {
namespace 'org.eu.spacc.spaccwebview.android'
defaultConfig {
minSdk 1
compileSdk 35
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
// testImplementation 'junit:junit:4.13.2'
// androidTestImplementation 'androidx.test.ext:junit:1.2.1'
// androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}

View File

@ -0,0 +1,26 @@
package org.eu.spacc.spaccwebview.android;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("org.eu.spacc.spaccwebview.android.test", appContext.getPackageName());
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -1,7 +1,7 @@
package org.eu.spacc.spaccdotweb.android;
package org.eu.spacc.spaccwebview.android;
import org.eu.spacc.spaccdotweb.android.Constants.*;
import org.eu.spacc.spaccdotweb.android.helpers.ConfigReader;
import org.eu.spacc.spaccwebview.android.Constants.*;
import org.eu.spacc.spaccwebview.android.helpers.ConfigReader;
public class Config extends Defaults {
private ConfigReader configReader;

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android;
package org.eu.spacc.spaccwebview.android;
public class Constants {
public enum ActivityCodes { UPLOAD_FILE /* , DOWNLOAD_FILE */ }

View File

@ -1,6 +1,6 @@
package org.eu.spacc.spaccdotweb.android;
package org.eu.spacc.spaccwebview.android;
import org.eu.spacc.spaccdotweb.android.Constants.*;
import org.eu.spacc.spaccwebview.android.Constants.*;
public class Defaults {
public static final Boolean ALLOW_JAVASCRIPT = true;

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android;
package org.eu.spacc.spaccwebview.android;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@ -9,8 +9,8 @@ import android.os.Build;
import android.webkit.ValueCallback;
import java.io.File;
import org.eu.spacc.spaccdotweb.android.webview.SpaccWebChromeClient;
import org.eu.spacc.spaccdotweb.android.webview.SpaccWebView;
import org.eu.spacc.spaccwebview.android.webview.SpaccWebChromeClient;
import org.eu.spacc.spaccwebview.android.webview.SpaccWebView;
public class SpaccWebViewActivity extends Activity {
protected SpaccWebView webView;

View File

@ -1,7 +1,7 @@
package org.eu.spacc.spaccdotweb.android;
package org.eu.spacc.spaccwebview.android;
import android.app.Application;
import org.eu.spacc.spaccdotweb.android.utils.StorageUtils;
import org.eu.spacc.spaccwebview.android.utils.StorageUtils;
import java.io.File;
public class SpaccWebViewApplication extends Application {

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.helpers;
package org.eu.spacc.spaccwebview.android.helpers;
import android.content.Context;
import android.content.res.XmlResourceParser;
@ -7,7 +7,7 @@ import java.util.HashMap;
import java.util.Map;
import org.xmlpull.v1.XmlPullParserException;
import org.eu.spacc.spaccdotweb.android.Constants.*;
import org.eu.spacc.spaccwebview.android.Constants.*;
public class ConfigReader {
private final Map<String, Object> configData = new HashMap<>();

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.helpers;
package org.eu.spacc.spaccwebview.android.helpers;
import android.app.Activity;
import android.app.AlertDialog;
@ -7,13 +7,14 @@ import android.content.Intent;
import android.os.Build;
import java.io.IOException;
import org.eu.spacc.spaccdotweb.android.Constants;
import org.eu.spacc.spaccdotweb.android.utils.StorageUtils;
import org.eu.spacc.spaccdotweb.android.utils.FileUtils;
import org.eu.spacc.spaccwebview.android.Constants;
import org.eu.spacc.spaccwebview.android.R;
import org.eu.spacc.spaccwebview.android.utils.StorageUtils;
import org.eu.spacc.spaccwebview.android.utils.FileUtils;
public class DataMoveHelper {
public static void run(Context context, int labelExit, int dialogTitle, int dialogMessage) {
public static void run(Context context) {
SharedPrefHelper sharedPrefHelper = new SharedPrefHelper(context);
Constants.DataLocation dataLocationReal = (StorageUtils.isInstalledOnExternalStorage(context) ? Constants.DataLocation.EXTERNAL : Constants.DataLocation.INTERNAL);
Integer dataLocationSaved = sharedPrefHelper.getInt("data_location");
@ -21,10 +22,10 @@ public class DataMoveHelper {
sharedPrefHelper.setInt("data_location", dataLocationReal.ordinal());
} else if (!dataLocationSaved.equals(dataLocationReal.ordinal())) {
new AlertDialog.Builder(context)
.setTitle(dialogTitle)
.setMessage(dialogMessage)
.setTitle(R.string.move_app_data)
.setMessage(R.string.move_app_data_info)
.setCancelable(false)
.setNegativeButton(labelExit, (dialogInterface, i) -> ((Activity)context).finish())
.setNegativeButton(R.string.exit, (dialogInterface, i) -> ((Activity)context).finish())
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
// TODO: Check that the storage locations are all present to copy, and implement an error dialog
try {

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.helpers;
package org.eu.spacc.spaccwebview.android.helpers;
import android.content.Context;
import android.content.SharedPreferences;

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.utils;
package org.eu.spacc.spaccwebview.android.utils;
import android.content.ActivityNotFoundException;
import android.content.Context;

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.utils;
package org.eu.spacc.spaccwebview.android.utils;
import static android.content.Context.DOWNLOAD_SERVICE;
import android.app.DownloadManager;

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.utils;
package org.eu.spacc.spaccwebview.android.utils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@ -7,7 +7,7 @@ import android.os.Build;
import android.os.Environment;
import java.io.File;
import org.eu.spacc.spaccdotweb.android.Constants.*;
import org.eu.spacc.spaccwebview.android.Constants.*;
public class StorageUtils {

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.webview;
package org.eu.spacc.spaccwebview.android.webview;
import android.net.Uri;

View File

@ -1,9 +1,9 @@
package org.eu.spacc.spaccdotweb.android.webview;
package org.eu.spacc.spaccwebview.android.webview;
import android.content.Context;
import android.net.Uri;
import org.eu.spacc.spaccdotweb.android.utils.FileUtils;
import org.eu.spacc.spaccwebview.android.utils.FileUtils;
public class DownloadListener implements android.webkit.DownloadListener {
private final Context context;

View File

@ -1,7 +1,6 @@
package org.eu.spacc.spaccdotweb.android.webview;
package org.eu.spacc.spaccwebview.android.webview;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
@ -11,13 +10,12 @@ import android.webkit.WebChromeClient;
import android.webkit.WebView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eu.spacc.spaccdotweb.android.Config;
import org.eu.spacc.spaccdotweb.android.Constants;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
import org.eu.spacc.spaccwebview.android.Config;
import org.eu.spacc.spaccwebview.android.Constants;
import org.eu.spacc.spaccwebview.android.SpaccWebViewActivity;
import org.eu.spacc.spaccwebview.android.utils.ApiUtils;
public class SpaccWebChromeClient extends WebChromeClient {
private final SpaccWebViewActivity activity;

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.webview;
package org.eu.spacc.spaccwebview.android.webview;
import android.content.Context;
import android.net.Uri;
@ -11,10 +11,11 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.util.ArrayList;
import org.eu.spacc.spaccdotweb.android.Config;
import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity;
import org.eu.spacc.spaccdotweb.android.helpers.ConfigReader;
import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
import org.eu.spacc.spaccwebview.android.Config;
import org.eu.spacc.spaccwebview.android.R;
import org.eu.spacc.spaccwebview.android.SpaccWebViewActivity;
import org.eu.spacc.spaccwebview.android.helpers.ConfigReader;
import org.eu.spacc.spaccwebview.android.utils.ApiUtils;
public class SpaccWebView extends WebView {
private Config config;
@ -22,9 +23,6 @@ public class SpaccWebView extends WebView {
private SpaccWebViewClient webViewClient;
private SpaccWebChromeClient webChromeClient;
private int openString;
private int openExternallyString;
private int copyUrlString;
private Boolean isLoaded = false;
protected ArrayList<String> scriptQueue = new ArrayList<>();
@ -48,12 +46,6 @@ public class SpaccWebView extends WebView {
this.applyConfig(context);
}
public void setStrings(int open, int openExternally, int copyUrl) {
openString = open;
openExternallyString = openExternally;
copyUrlString = copyUrl;
}
@Override
public void setWebViewClient(WebViewClient client) {
super.setWebViewClient(client);
@ -81,19 +73,19 @@ public class SpaccWebView extends WebView {
String href = result.getExtra();
if (href != null) {
menu.setHeaderTitle(href);
menu.add(openString).setOnMenuItemClickListener(menuItem -> {
menu.add(R.string.open_menu).setOnMenuItemClickListener(menuItem -> {
if (!webViewClient.shouldOverrideUrlLoading(this, href)) {
this.loadUrl(href);
}
return false;
});
if (!ApiUtils.isInternalUrl(Uri.parse(href))) {
menu.add(openExternallyString).setOnMenuItemClickListener(menuItem -> {
menu.add(R.string.open_externally_menu).setOnMenuItemClickListener(menuItem -> {
ApiUtils.openOrShareUrl(context, Uri.parse(href));
return false;
});
}
menu.add(copyUrlString).setOnMenuItemClickListener(menuItem -> {
menu.add(R.string.copy_url_menu).setOnMenuItemClickListener(menuItem -> {
ApiUtils.writeToClipboard(context, href);
return false;
});

View File

@ -1,4 +1,4 @@
package org.eu.spacc.spaccdotweb.android.webview;
package org.eu.spacc.spaccwebview.android.webview;
import android.content.Context;
import android.content.Intent;
@ -9,9 +9,9 @@ import android.webkit.WebViewClient;
import java.net.URISyntaxException;
import java.util.Arrays;
import org.eu.spacc.spaccdotweb.android.Config;
import org.eu.spacc.spaccdotweb.android.Constants;
import org.eu.spacc.spaccdotweb.android.utils.ApiUtils;
import org.eu.spacc.spaccwebview.android.Config;
import org.eu.spacc.spaccwebview.android.Constants;
import org.eu.spacc.spaccwebview.android.utils.ApiUtils;
public class SpaccWebViewClient extends WebViewClient {
private final Context context;

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY app_name "SpaccWebView Application">
<!ENTITY about_app "About App">
<!ENTITY exit "Exit">
<!ENTITY hide "Hide">
@ -14,7 +13,6 @@
<!ENTITY execute_javascript "Execute JavaScript">
]>
<resources>
<string name="app_name" translatable="false">&app_name;</string>
<string name="about_app">&about_app;</string>
<string name="about_app_menu"> &about_app;</string>
<string name="yes">Yes</string>

View File

@ -0,0 +1,17 @@
package org.eu.spacc.spaccwebview.android;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -1,4 +1,4 @@
plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'com.android.application' version '8.4.2' apply false
id 'com.android.library' version '8.4.2' apply false
}

View File

@ -1,6 +1,7 @@
#Sun May 25 21:16:27 CEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -19,4 +19,5 @@ dependencyResolutionManagement {
}
rootProject.name = 'SpaccWebView Application'
include ':app'
include ':ExampleApp'
include ':SpaccWebView'