mirror of
				https://gitlab.com/SpaccInc/SpaccDotWeb.git
				synced 2025-06-05 21:29:12 +02:00 
			
		
		
		
	[Android] Initial upload support, Context menu for links, More config options
This commit is contained in:
		| @@ -27,3 +27,9 @@ android { | ||||
|  | ||||
|     compileSdk 34 | ||||
| } | ||||
|  | ||||
| java { | ||||
|     toolchain { | ||||
|         languageVersion = JavaLanguageVersion.of(17) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,7 +2,8 @@ | ||||
| <manifest | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:installLocation="auto"> | ||||
|     android:installLocation="auto" | ||||
|     tools:ignore="UnusedAttribute"> | ||||
|  | ||||
|     <!-- Lets the app access the Internet — not needed for fully offline apps --> | ||||
|     <!-- <uses-permission android:name="android.permission.INTERNET" /> --> | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|     <li><a href="intent://#Intent;action=android.intent.action.VIEW;scheme=http;type=video/mp4;end">intent://</a></li> | ||||
| </ul> | ||||
| <h3>Files</h3> | ||||
|     <p>Upload: <label><input type="file"/></label></p> | ||||
|     <p>Upload: <label><input type="file" onchange="(function(f){ alert(f.name + ' ' + f.size + ' ' + f.type); })(this.files[0]);" /></label></p> | ||||
|     <p>Download:</p> | ||||
|     <ul> | ||||
|         <li><a download="Hello World.txt" href="data:text/plain;utf8,Hello World!">data:, .txt</a></li> | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package org.eu.spacc.spaccdotweb.android; | ||||
|  | ||||
| import android.webkit.WebSettings; | ||||
|  | ||||
| import org.eu.spacc.spaccdotweb.android.Constants.*; | ||||
| import org.eu.spacc.spaccdotweb.android.helpers.ConfigReader; | ||||
|  | ||||
| @@ -22,6 +24,16 @@ public class Config extends Defaults { | ||||
|         return (value != null ? value : Defaults.ALLOW_STORAGE); | ||||
|     } | ||||
|  | ||||
|     public Boolean getAllowZoomControls() { | ||||
|         Boolean value = getBoolean("allow_zoom_controls"); | ||||
|         return (value != null ? value : Defaults.ALLOW_ZOOM_CONTROLS); | ||||
|     } | ||||
|  | ||||
|     public Boolean getDisplayZoomControls() { | ||||
|         Boolean value = getBoolean("display_zoom_controls"); | ||||
|         return (value != null ? value : Defaults.DISPLAY_ZOOM_CONTROLS); | ||||
|     } | ||||
|  | ||||
|     public AppIndex getAppIndex() { | ||||
|         AppIndex value = (AppIndex)get("app_index"); | ||||
|         return (value != null ? value : Defaults.APP_INDEX); | ||||
| @@ -36,12 +48,18 @@ public class Config extends Defaults { | ||||
|         return getString("remote_index"); | ||||
|     } | ||||
|  | ||||
|     public String getStandardFontFamily() { | ||||
|         return getString("standard_font_family"); | ||||
|     } | ||||
|  | ||||
|     public String getUserAgent() { | ||||
|         return getString("user_agent"); | ||||
|     } | ||||
|  | ||||
|     private Object get(String key) { | ||||
|         if (configReader != null) { | ||||
|             return configReader.get(key); | ||||
|         } else { | ||||
|             return null; | ||||
|         } | ||||
|         return (configReader != null | ||||
|                 ? configReader.get(key) | ||||
|                 : null); | ||||
|     } | ||||
|  | ||||
|     private Boolean getBoolean(String key) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package org.eu.spacc.spaccdotweb.android; | ||||
|  | ||||
| public class Constants { | ||||
|     //public enum ActivityCodes { DOWNLOAD_FILE, UPLOAD_FILE } | ||||
|     public enum ActivityCodes { UPLOAD_FILE /* , DOWNLOAD_FILE */ } | ||||
|     public enum AppIndex { LOCAL, REMOTE } | ||||
|     public enum DataLocation { INTERNAL, EXTERNAL } | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,8 @@ import org.eu.spacc.spaccdotweb.android.Constants.*; | ||||
| public class Defaults { | ||||
|     public static final Boolean ALLOW_JAVASCRIPT = true; | ||||
|     public static final Boolean ALLOW_STORAGE = true; | ||||
|     public static final Boolean ALLOW_ZOOM_CONTROLS = false; | ||||
|     public static final Boolean DISPLAY_ZOOM_CONTROLS = false; | ||||
|     public static final AppIndex APP_INDEX = AppIndex.LOCAL; | ||||
|     public static final String LOCAL_INDEX = "index.html"; | ||||
| } | ||||
|   | ||||
| @@ -1,23 +1,49 @@ | ||||
| package org.eu.spacc.spaccdotweb.android; | ||||
|  | ||||
| import android.annotation.SuppressLint; | ||||
| import android.annotation.TargetApi; | ||||
| import android.app.Activity; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.webkit.ValueCallback; | ||||
| import org.eu.spacc.spaccdotweb.android.webview.SpaccWebChromeClient; | ||||
| import org.eu.spacc.spaccdotweb.android.webview.SpaccWebView; | ||||
| import java.io.File; | ||||
|  | ||||
| public class SpaccWebViewActivity extends Activity { | ||||
|     protected SpaccWebView webView; | ||||
|     public ValueCallback<Uri> fileUploadCallback; | ||||
|     public ValueCallback<Uri[]> filesUploadCallback; | ||||
|  | ||||
| //    @Override | ||||
| //    protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||
| //        super.onActivityResult(requestCode, resultCode, data); | ||||
|     @TargetApi(Build.VERSION_CODES.ECLAIR_MR1) | ||||
|     @Override | ||||
|     protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||
|         super.onActivityResult(requestCode, resultCode, data); | ||||
| //        if (requestCode == Constants.CREATE_FILE_REQUEST_CODE && resultCode == RESULT_OK && data != null) { | ||||
| //            Uri fileUri = data.getData(); | ||||
| //            if (fileUri != null) { | ||||
| //                enqueueDownload(Uri.parse(fileUri.toString())); | ||||
| //            } | ||||
| //        } | ||||
| //    } | ||||
|         if (requestCode == Constants.ActivityCodes.UPLOAD_FILE.ordinal()) { | ||||
|             if (resultCode == Activity.RESULT_OK && data != null) { | ||||
|                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && filesUploadCallback != null) { | ||||
|                     filesUploadCallback.onReceiveValue(SpaccWebChromeClient.FileChooserParams.parseResult(resultCode, data)); | ||||
|                 } else if (fileUploadCallback != null) { | ||||
|                     fileUploadCallback.onReceiveValue(data.getData()); | ||||
|                 } | ||||
|             } else { | ||||
|                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && filesUploadCallback != null) { | ||||
|                     filesUploadCallback.onReceiveValue(null); | ||||
|                 } else if (fileUploadCallback != null) { | ||||
|                     fileUploadCallback.onReceiveValue(null); | ||||
|                 } | ||||
|             } | ||||
|             fileUploadCallback = null; | ||||
|             filesUploadCallback = null; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void onBackPressed() { | ||||
|   | ||||
| @@ -23,4 +23,15 @@ public class ApiUtils { | ||||
|             context.startActivity(new Intent(Intent.ACTION_SEND).setType("text/plain").putExtra(Intent.EXTRA_TEXT, url.toString())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void writeToClipboard(Context context, String text) { | ||||
|         if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) { | ||||
|             android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
|             clipboard.setText(text); | ||||
|         } else { | ||||
|             android.content.ClipboardManager clipboard = (android.content.ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); | ||||
|             android.content.ClipData clip = android.content.ClipData.newPlainText(null, text); | ||||
|             clipboard.setPrimaryClip(clip); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,40 @@ | ||||
| package org.eu.spacc.spaccdotweb.android.webview; | ||||
|  | ||||
| import android.annotation.TargetApi; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.os.Build; | ||||
| import android.webkit.ValueCallback; | ||||
| import android.webkit.WebChromeClient; | ||||
| import android.webkit.WebView; | ||||
|  | ||||
| import org.eu.spacc.spaccdotweb.android.Constants; | ||||
| import org.eu.spacc.spaccdotweb.android.SpaccWebViewActivity; | ||||
|  | ||||
| public class SpaccWebChromeClient extends WebChromeClient { | ||||
|     private final SpaccWebViewActivity activity; | ||||
|  | ||||
|     public SpaccWebChromeClient(SpaccWebViewActivity activity) { | ||||
|         super(); | ||||
|         this.activity = activity; | ||||
|     } | ||||
|  | ||||
|     // TODO: Android < 4 support | ||||
|  | ||||
|     @TargetApi(Build.VERSION_CODES.LOLLIPOP) | ||||
|     @Override | ||||
|     public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> valueCallback, FileChooserParams fileChooserParams) { | ||||
|         activity.filesUploadCallback = valueCallback; | ||||
|         activity.startActivityForResult(fileChooserParams.createIntent(), Constants.ActivityCodes.UPLOAD_FILE.ordinal()); | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     //@Override // Android 4.1+ | ||||
|     protected void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) { | ||||
|         Intent intent = new Intent(Intent.ACTION_GET_CONTENT); | ||||
|         intent.addCategory(Intent.CATEGORY_OPENABLE); | ||||
|         intent.setType("*/*"); | ||||
|         activity.fileUploadCallback = valueCallback; | ||||
|         activity.startActivityForResult(Intent.createChooser(intent, null), Constants.ActivityCodes.UPLOAD_FILE.ordinal()); | ||||
|     } | ||||
| } | ||||
| @@ -1,32 +1,51 @@ | ||||
| package org.eu.spacc.spaccdotweb.android.webview; | ||||
|  | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.Context; | ||||
| import android.net.Uri; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.ContextMenu; | ||||
| import android.webkit.WebSettings; | ||||
| import android.webkit.WebView; | ||||
| 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; | ||||
|  | ||||
| public class SpaccWebView extends WebView { | ||||
|     private Config config; | ||||
|     private Context context; | ||||
|  | ||||
|     @SuppressLint("SetJavaScriptEnabled") | ||||
|     public SpaccWebView(Context context, AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|         this.setWebViewClient(new WebViewClient(context)); | ||||
|         this.setWebChromeClient(new WebChromeClient(context)); | ||||
|         this.context = context; | ||||
|         this.setWebViewClient(new SpaccWebViewClient(context)); | ||||
|         this.setWebChromeClient(new SpaccWebChromeClient((SpaccWebViewActivity)context)); | ||||
|         this.setDownloadListener(new DownloadListener(context)); | ||||
|         this.config = new Config(); | ||||
|         this.applyConfig(context); | ||||
|     } | ||||
|  | ||||
|     // TODO: Implement context menu (long-press on links, images, etc...) | ||||
| //    @Override | ||||
| //    protected void onCreateContextMenu(ContextMenu menu) { | ||||
| //        super.onCreateContextMenu(menu); | ||||
| //    } | ||||
|     @Override | ||||
|     protected void onCreateContextMenu(ContextMenu menu) { | ||||
|         super.onCreateContextMenu(menu); | ||||
|         HitTestResult result = getHitTestResult(); | ||||
|         switch (result.getType()) { | ||||
|             case HitTestResult.SRC_ANCHOR_TYPE: | ||||
|             case HitTestResult.SRC_IMAGE_ANCHOR_TYPE: | ||||
|                 String href = result.getExtra(); | ||||
|                 menu.setHeaderTitle(href); | ||||
|                 menu.add("Copy URL").setOnMenuItemClickListener(menuItem -> { | ||||
|                     ApiUtils.writeToClipboard(context, href); | ||||
|                     return false; | ||||
|                 }); | ||||
|                 menu.add("Open or Share Externally").setOnMenuItemClickListener(menuItem -> { | ||||
|                     ApiUtils.openOrShareUrl(context, Uri.parse(href)); | ||||
|                     return false; | ||||
|                 }); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void applyConfig(Context context) { | ||||
|         WebSettings webSettings = this.getSettings(); | ||||
| @@ -41,6 +60,12 @@ public class SpaccWebView extends WebView { | ||||
|         } | ||||
|  | ||||
|         ApiUtils.apiRun(3, () -> webSettings.setAllowFileAccess(false)); | ||||
|  | ||||
|         webSettings.setStandardFontFamily(config.getStandardFontFamily()); | ||||
|         ApiUtils.apiRun(3, () -> webSettings.setUserAgentString(config.getUserAgent())); | ||||
|  | ||||
|         ApiUtils.apiRun(3, () -> webSettings.setBuiltInZoomControls(config.getAllowZoomControls())); | ||||
|         ApiUtils.apiRun(11, () -> webSettings.setDisplayZoomControls(config.getDisplayZoomControls())); | ||||
|     } | ||||
|  | ||||
|     public void loadConfig(Context context, int configResource) { | ||||
|   | ||||
| @@ -4,16 +4,18 @@ import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.webkit.WebView; | ||||
| import android.webkit.WebViewClient; | ||||
| 
 | ||||
| import org.eu.spacc.spaccdotweb.android.utils.ApiUtils; | ||||
| 
 | ||||
| import java.net.URISyntaxException; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| public class WebViewClient extends android.webkit.WebViewClient { | ||||
| public class SpaccWebViewClient extends WebViewClient { | ||||
|     private final Context context; | ||||
| 
 | ||||
|     public WebViewClient(Context context) { | ||||
|     public SpaccWebViewClient(Context context) { | ||||
|         super(); | ||||
|         this.context = context; | ||||
|     } | ||||
| @@ -1,32 +0,0 @@ | ||||
| package org.eu.spacc.spaccdotweb.android.webview; | ||||
|  | ||||
| import android.app.Activity; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.net.Uri; | ||||
| import android.webkit.ValueCallback; | ||||
| import android.webkit.WebView; | ||||
|  | ||||
| public class WebChromeClient extends android.webkit.WebChromeClient { | ||||
|     private static final int INPUT_FILE_REQUEST_CODE = 1; | ||||
|  | ||||
|     private final Context context; | ||||
|  | ||||
|     public WebChromeClient(Context context) { | ||||
|         super(); | ||||
|         this.context = context; | ||||
|     } | ||||
|  | ||||
|     // TODO: This only opens a file selector but then doesn't do anything | ||||
|     // TODO: Android < 5 support | ||||
|     @Override | ||||
|     public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) { | ||||
|         Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); | ||||
|         contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); | ||||
|         contentSelectionIntent.setType("*/*"); // TODO: Read type from HTML input | ||||
|         Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); | ||||
|         chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); | ||||
|         ((Activity)context).startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE); | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @@ -1,9 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <config> | ||||
|     <boolean name="allow_javascript">true</boolean> | ||||
|     <boolean name="allow_storage">true</boolean> | ||||
|  | ||||
|     <AppIndex name="app_index">local</AppIndex> <!-- local or remote --> | ||||
|     <string name="local_index">index.html</string> | ||||
|     <string name="remote_index">https://example.com</string> | ||||
|     <!-- <string name="standard_font_family"></string> --> | ||||
|     <!-- <string name="user_agent"></string> --> | ||||
| </config> | ||||
| @@ -1,4 +1,4 @@ | ||||
| plugins { | ||||
|     id 'com.android.application' version '7.3.1' apply false | ||||
|     id 'com.android.library' version '7.3.1' apply false | ||||
|     id 'com.android.application' version '7.4.2' apply false | ||||
|     id 'com.android.library' version '7.4.2' apply false | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,10 @@ pluginManagement { | ||||
|         mavenCentral() | ||||
|     } | ||||
| } | ||||
|  | ||||
| plugins { | ||||
|     id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' | ||||
| } | ||||
| dependencyResolutionManagement { | ||||
|     repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) | ||||
|     repositories { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user