init
30
build/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Internet permission for WebView -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.WailsApp"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="31">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
198
build/android/app/src/main/java/com/wails/app/MainActivity.java
Normal file
@@ -0,0 +1,198 @@
|
||||
package com.wails.app;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.webkit.WebViewAssetLoader;
|
||||
import com.wails.app.BuildConfig;
|
||||
|
||||
/**
|
||||
* MainActivity hosts the WebView and manages the Wails application lifecycle.
|
||||
* It uses WebViewAssetLoader to serve assets from the Go library without
|
||||
* requiring a network server.
|
||||
*/
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "WailsActivity";
|
||||
private static final String WAILS_SCHEME = "https";
|
||||
private static final String WAILS_HOST = "wails.localhost";
|
||||
|
||||
private WebView webView;
|
||||
private WailsBridge bridge;
|
||||
private WebViewAssetLoader assetLoader;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Initialize the native Go library
|
||||
bridge = new WailsBridge(this);
|
||||
bridge.initialize();
|
||||
|
||||
// Set up WebView
|
||||
setupWebView();
|
||||
|
||||
// Load the application
|
||||
loadApplication();
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private void setupWebView() {
|
||||
webView = findViewById(R.id.webview);
|
||||
|
||||
// Configure WebView settings
|
||||
WebSettings settings = webView.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
settings.setDatabaseEnabled(true);
|
||||
settings.setAllowFileAccess(false);
|
||||
settings.setAllowContentAccess(false);
|
||||
settings.setMediaPlaybackRequiresUserGesture(false);
|
||||
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
|
||||
|
||||
// Enable debugging in debug builds
|
||||
if (BuildConfig.DEBUG) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
|
||||
// Set up asset loader for serving local assets
|
||||
assetLoader = new WebViewAssetLoader.Builder()
|
||||
.setDomain(WAILS_HOST)
|
||||
.addPathHandler("/", new WailsPathHandler(bridge))
|
||||
.build();
|
||||
|
||||
// Set up WebView client to intercept requests
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Nullable
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
String url = request.getUrl().toString();
|
||||
Log.d(TAG, "Intercepting request: " + url);
|
||||
|
||||
// Handle wails.localhost requests
|
||||
if (request.getUrl().getHost() != null &&
|
||||
request.getUrl().getHost().equals(WAILS_HOST)) {
|
||||
|
||||
// For wails API calls (runtime, capabilities, etc.), we need to pass the full URL
|
||||
// including query string because WebViewAssetLoader.PathHandler strips query params
|
||||
String path = request.getUrl().getPath();
|
||||
if (path != null && path.startsWith("/wails/")) {
|
||||
// Get full path with query string for runtime calls
|
||||
String fullPath = path;
|
||||
String query = request.getUrl().getQuery();
|
||||
if (query != null && !query.isEmpty()) {
|
||||
fullPath = path + "?" + query;
|
||||
}
|
||||
Log.d(TAG, "Wails API call detected, full path: " + fullPath);
|
||||
|
||||
// Call bridge directly with full path
|
||||
byte[] data = bridge.serveAsset(fullPath, request.getMethod(), "{}");
|
||||
if (data != null && data.length > 0) {
|
||||
java.io.InputStream inputStream = new java.io.ByteArrayInputStream(data);
|
||||
java.util.Map<String, String> headers = new java.util.HashMap<>();
|
||||
headers.put("Access-Control-Allow-Origin", "*");
|
||||
headers.put("Cache-Control", "no-cache");
|
||||
headers.put("Content-Type", "application/json");
|
||||
|
||||
return new WebResourceResponse(
|
||||
"application/json",
|
||||
"UTF-8",
|
||||
200,
|
||||
"OK",
|
||||
headers,
|
||||
inputStream
|
||||
);
|
||||
}
|
||||
// Return error response if data is null
|
||||
return new WebResourceResponse(
|
||||
"application/json",
|
||||
"UTF-8",
|
||||
500,
|
||||
"Internal Error",
|
||||
new java.util.HashMap<>(),
|
||||
new java.io.ByteArrayInputStream("{}".getBytes())
|
||||
);
|
||||
}
|
||||
|
||||
// For regular assets, use the asset loader
|
||||
return assetLoader.shouldInterceptRequest(request.getUrl());
|
||||
}
|
||||
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
Log.d(TAG, "Page loaded: " + url);
|
||||
// Inject Wails runtime
|
||||
bridge.injectRuntime(webView, url);
|
||||
}
|
||||
});
|
||||
|
||||
// Add JavaScript interface for Go communication
|
||||
webView.addJavascriptInterface(new WailsJSBridge(bridge, webView), "wails");
|
||||
}
|
||||
|
||||
private void loadApplication() {
|
||||
// Load the main page from the asset server
|
||||
String url = WAILS_SCHEME + "://" + WAILS_HOST + "/";
|
||||
Log.d(TAG, "Loading URL: " + url);
|
||||
webView.loadUrl(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute JavaScript in the WebView from the Go side
|
||||
*/
|
||||
public void executeJavaScript(final String js) {
|
||||
runOnUiThread(() -> {
|
||||
if (webView != null) {
|
||||
webView.evaluateJavascript(js, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (bridge != null) {
|
||||
bridge.onResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (bridge != null) {
|
||||
bridge.onPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (bridge != null) {
|
||||
bridge.shutdown();
|
||||
}
|
||||
if (webView != null) {
|
||||
webView.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (webView != null && webView.canGoBack()) {
|
||||
webView.goBack();
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
}
|
||||
214
build/android/app/src/main/java/com/wails/app/WailsBridge.java
Normal file
@@ -0,0 +1,214 @@
|
||||
package com.wails.app;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* WailsBridge manages the connection between the Java/Android side and the Go native library.
|
||||
* It handles:
|
||||
* - Loading and initializing the native Go library
|
||||
* - Serving asset requests from Go
|
||||
* - Passing messages between JavaScript and Go
|
||||
* - Managing callbacks for async operations
|
||||
*/
|
||||
public class WailsBridge {
|
||||
private static final String TAG = "WailsBridge";
|
||||
|
||||
static {
|
||||
// Load the native Go library
|
||||
System.loadLibrary("wails");
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final AtomicInteger callbackIdGenerator = new AtomicInteger(0);
|
||||
private final ConcurrentHashMap<Integer, AssetCallback> pendingAssetCallbacks = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Integer, MessageCallback> pendingMessageCallbacks = new ConcurrentHashMap<>();
|
||||
private WebView webView;
|
||||
private volatile boolean initialized = false;
|
||||
|
||||
// Native methods - implemented in Go
|
||||
private static native void nativeInit(WailsBridge bridge);
|
||||
private static native void nativeShutdown();
|
||||
private static native void nativeOnResume();
|
||||
private static native void nativeOnPause();
|
||||
private static native void nativeOnPageFinished(String url);
|
||||
private static native byte[] nativeServeAsset(String path, String method, String headers);
|
||||
private static native String nativeHandleMessage(String message);
|
||||
private static native String nativeGetAssetMimeType(String path);
|
||||
|
||||
public WailsBridge(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the native Go library
|
||||
*/
|
||||
public void initialize() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Initializing Wails bridge...");
|
||||
try {
|
||||
nativeInit(this);
|
||||
initialized = true;
|
||||
Log.i(TAG, "Wails bridge initialized successfully");
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to initialize Wails bridge", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the native Go library
|
||||
*/
|
||||
public void shutdown() {
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Shutting down Wails bridge...");
|
||||
try {
|
||||
nativeShutdown();
|
||||
initialized = false;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error during shutdown", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity resumes
|
||||
*/
|
||||
public void onResume() {
|
||||
if (initialized) {
|
||||
nativeOnResume();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the activity pauses
|
||||
*/
|
||||
public void onPause() {
|
||||
if (initialized) {
|
||||
nativeOnPause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve an asset from the Go asset server
|
||||
* @param path The URL path requested
|
||||
* @param method The HTTP method
|
||||
* @param headers The request headers as JSON
|
||||
* @return The asset data, or null if not found
|
||||
*/
|
||||
public byte[] serveAsset(String path, String method, String headers) {
|
||||
if (!initialized) {
|
||||
Log.w(TAG, "Bridge not initialized, cannot serve asset: " + path);
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.d(TAG, "Serving asset: " + path);
|
||||
try {
|
||||
return nativeServeAsset(path, method, headers);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error serving asset: " + path, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type for an asset
|
||||
* @param path The asset path
|
||||
* @return The MIME type string
|
||||
*/
|
||||
public String getAssetMimeType(String path) {
|
||||
if (!initialized) {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
try {
|
||||
String mimeType = nativeGetAssetMimeType(path);
|
||||
return mimeType != null ? mimeType : "application/octet-stream";
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error getting MIME type for: " + path, e);
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a message from JavaScript
|
||||
* @param message The message from JavaScript (JSON)
|
||||
* @return The response to send back to JavaScript (JSON)
|
||||
*/
|
||||
public String handleMessage(String message) {
|
||||
if (!initialized) {
|
||||
Log.w(TAG, "Bridge not initialized, cannot handle message");
|
||||
return "{\"error\":\"Bridge not initialized\"}";
|
||||
}
|
||||
|
||||
Log.d(TAG, "Handling message from JS: " + message);
|
||||
try {
|
||||
return nativeHandleMessage(message);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error handling message", e);
|
||||
return "{\"error\":\"" + e.getMessage() + "\"}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject the Wails runtime JavaScript into the WebView.
|
||||
* Called when the page finishes loading.
|
||||
* @param webView The WebView to inject into
|
||||
* @param url The URL that finished loading
|
||||
*/
|
||||
public void injectRuntime(WebView webView, String url) {
|
||||
this.webView = webView;
|
||||
// Notify Go side that page has finished loading so it can inject the runtime
|
||||
Log.d(TAG, "Page finished loading: " + url + ", notifying Go side");
|
||||
if (initialized) {
|
||||
nativeOnPageFinished(url);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute JavaScript in the WebView (called from Go side)
|
||||
* @param js The JavaScript code to execute
|
||||
*/
|
||||
public void executeJavaScript(String js) {
|
||||
if (webView != null) {
|
||||
webView.post(() -> webView.evaluateJavascript(js, null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from Go when an event needs to be emitted to JavaScript
|
||||
* @param eventName The event name
|
||||
* @param eventData The event data (JSON)
|
||||
*/
|
||||
public void emitEvent(String eventName, String eventData) {
|
||||
String js = String.format("window.wails && window.wails._emit('%s', %s);",
|
||||
escapeJsString(eventName), eventData);
|
||||
executeJavaScript(js);
|
||||
}
|
||||
|
||||
private String escapeJsString(String str) {
|
||||
return str.replace("\\", "\\\\")
|
||||
.replace("'", "\\'")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r");
|
||||
}
|
||||
|
||||
// Callback interfaces
|
||||
public interface AssetCallback {
|
||||
void onAssetReady(byte[] data, String mimeType);
|
||||
void onAssetError(String error);
|
||||
}
|
||||
|
||||
public interface MessageCallback {
|
||||
void onResponse(String response);
|
||||
void onError(String error);
|
||||
}
|
||||
}
|
||||
142
build/android/app/src/main/java/com/wails/app/WailsJSBridge.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package com.wails.app;
|
||||
|
||||
import android.util.Log;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebView;
|
||||
import com.wails.app.BuildConfig;
|
||||
|
||||
/**
|
||||
* WailsJSBridge provides the JavaScript interface that allows the web frontend
|
||||
* to communicate with the Go backend. This is exposed to JavaScript as the
|
||||
* `window.wails` object.
|
||||
*
|
||||
* Similar to iOS's WKScriptMessageHandler but using Android's addJavascriptInterface.
|
||||
*/
|
||||
public class WailsJSBridge {
|
||||
private static final String TAG = "WailsJSBridge";
|
||||
|
||||
private final WailsBridge bridge;
|
||||
private final WebView webView;
|
||||
|
||||
public WailsJSBridge(WailsBridge bridge, WebView webView) {
|
||||
this.bridge = bridge;
|
||||
this.webView = webView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to Go and return the response synchronously.
|
||||
* Called from JavaScript: wails.invoke(message)
|
||||
*
|
||||
* @param message The message to send (JSON string)
|
||||
* @return The response from Go (JSON string)
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public String invoke(String message) {
|
||||
Log.d(TAG, "Invoke called: " + message);
|
||||
return bridge.handleMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to Go asynchronously.
|
||||
* The response will be sent back via a callback.
|
||||
* Called from JavaScript: wails.invokeAsync(callbackId, message)
|
||||
*
|
||||
* @param callbackId The callback ID to use for the response
|
||||
* @param message The message to send (JSON string)
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void invokeAsync(final String callbackId, final String message) {
|
||||
Log.d(TAG, "InvokeAsync called: " + message);
|
||||
|
||||
// Handle in background thread to not block JavaScript
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String response = bridge.handleMessage(message);
|
||||
sendCallback(callbackId, response, null);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error in async invoke", e);
|
||||
sendCallback(callbackId, null, e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a message from JavaScript to Android's logcat
|
||||
* Called from JavaScript: wails.log(level, message)
|
||||
*
|
||||
* @param level The log level (debug, info, warn, error)
|
||||
* @param message The message to log
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void log(String level, String message) {
|
||||
switch (level.toLowerCase()) {
|
||||
case "debug":
|
||||
Log.d(TAG + "/JS", message);
|
||||
break;
|
||||
case "info":
|
||||
Log.i(TAG + "/JS", message);
|
||||
break;
|
||||
case "warn":
|
||||
Log.w(TAG + "/JS", message);
|
||||
break;
|
||||
case "error":
|
||||
Log.e(TAG + "/JS", message);
|
||||
break;
|
||||
default:
|
||||
Log.v(TAG + "/JS", message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the platform name
|
||||
* Called from JavaScript: wails.platform()
|
||||
*
|
||||
* @return "android"
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public String platform() {
|
||||
return "android";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're running in debug mode
|
||||
* Called from JavaScript: wails.isDebug()
|
||||
*
|
||||
* @return true if debug build, false otherwise
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public boolean isDebug() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a callback response to JavaScript
|
||||
*/
|
||||
private void sendCallback(String callbackId, String result, String error) {
|
||||
final String js;
|
||||
if (error != null) {
|
||||
js = String.format(
|
||||
"window.wails && window.wails._callback('%s', null, '%s');",
|
||||
escapeJsString(callbackId),
|
||||
escapeJsString(error)
|
||||
);
|
||||
} else {
|
||||
js = String.format(
|
||||
"window.wails && window.wails._callback('%s', %s, null);",
|
||||
escapeJsString(callbackId),
|
||||
result != null ? result : "null"
|
||||
);
|
||||
}
|
||||
|
||||
webView.post(() -> webView.evaluateJavascript(js, null));
|
||||
}
|
||||
|
||||
private String escapeJsString(String str) {
|
||||
if (str == null) return "";
|
||||
return str.replace("\\", "\\\\")
|
||||
.replace("'", "\\'")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.wails.app;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebResourceResponse;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.webkit.WebViewAssetLoader;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* WailsPathHandler implements WebViewAssetLoader.PathHandler to serve assets
|
||||
* from the Go asset server. This allows the WebView to load assets without
|
||||
* using a network server, similar to iOS's WKURLSchemeHandler.
|
||||
*/
|
||||
public class WailsPathHandler implements WebViewAssetLoader.PathHandler {
|
||||
private static final String TAG = "WailsPathHandler";
|
||||
|
||||
private final WailsBridge bridge;
|
||||
|
||||
public WailsPathHandler(WailsBridge bridge) {
|
||||
this.bridge = bridge;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public WebResourceResponse handle(@NonNull String path) {
|
||||
Log.d(TAG, "Handling path: " + path);
|
||||
|
||||
// Normalize path
|
||||
if (path.isEmpty() || path.equals("/")) {
|
||||
path = "/index.html";
|
||||
}
|
||||
|
||||
// Get asset from Go
|
||||
byte[] data = bridge.serveAsset(path, "GET", "{}");
|
||||
|
||||
if (data == null || data.length == 0) {
|
||||
Log.w(TAG, "Asset not found: " + path);
|
||||
return null; // Return null to let WebView handle 404
|
||||
}
|
||||
|
||||
// Determine MIME type
|
||||
String mimeType = bridge.getAssetMimeType(path);
|
||||
Log.d(TAG, "Serving " + path + " with type " + mimeType + " (" + data.length + " bytes)");
|
||||
|
||||
// Create response
|
||||
InputStream inputStream = new ByteArrayInputStream(data);
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Access-Control-Allow-Origin", "*");
|
||||
headers.put("Cache-Control", "no-cache");
|
||||
|
||||
return new WebResourceResponse(
|
||||
mimeType,
|
||||
"UTF-8",
|
||||
200,
|
||||
"OK",
|
||||
headers,
|
||||
inputStream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine MIME type from file extension
|
||||
*/
|
||||
private String getMimeType(String path) {
|
||||
String lowerPath = path.toLowerCase();
|
||||
|
||||
if (lowerPath.endsWith(".html") || lowerPath.endsWith(".htm")) {
|
||||
return "text/html";
|
||||
} else if (lowerPath.endsWith(".js") || lowerPath.endsWith(".mjs")) {
|
||||
return "application/javascript";
|
||||
} else if (lowerPath.endsWith(".css")) {
|
||||
return "text/css";
|
||||
} else if (lowerPath.endsWith(".json")) {
|
||||
return "application/json";
|
||||
} else if (lowerPath.endsWith(".png")) {
|
||||
return "image/png";
|
||||
} else if (lowerPath.endsWith(".jpg") || lowerPath.endsWith(".jpeg")) {
|
||||
return "image/jpeg";
|
||||
} else if (lowerPath.endsWith(".gif")) {
|
||||
return "image/gif";
|
||||
} else if (lowerPath.endsWith(".svg")) {
|
||||
return "image/svg+xml";
|
||||
} else if (lowerPath.endsWith(".ico")) {
|
||||
return "image/x-icon";
|
||||
} else if (lowerPath.endsWith(".woff")) {
|
||||
return "font/woff";
|
||||
} else if (lowerPath.endsWith(".woff2")) {
|
||||
return "font/woff2";
|
||||
} else if (lowerPath.endsWith(".ttf")) {
|
||||
return "font/ttf";
|
||||
} else if (lowerPath.endsWith(".eot")) {
|
||||
return "application/vnd.ms-fontobject";
|
||||
} else if (lowerPath.endsWith(".xml")) {
|
||||
return "application/xml";
|
||||
} else if (lowerPath.endsWith(".txt")) {
|
||||
return "text/plain";
|
||||
} else if (lowerPath.endsWith(".wasm")) {
|
||||
return "application/wasm";
|
||||
} else if (lowerPath.endsWith(".mp3")) {
|
||||
return "audio/mpeg";
|
||||
} else if (lowerPath.endsWith(".mp4")) {
|
||||
return "video/mp4";
|
||||
} else if (lowerPath.endsWith(".webm")) {
|
||||
return "video/webm";
|
||||
} else if (lowerPath.endsWith(".webp")) {
|
||||
return "image/webp";
|
||||
}
|
||||
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
12
build/android/app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</FrameLayout>
|
||||
BIN
build/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
build/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
build/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
build/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
build/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
BIN
build/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
BIN
build/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
8
build/android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="wails_blue">#3574D4</color>
|
||||
<color name="wails_blue_dark">#2C5FB8</color>
|
||||
<color name="wails_background">#1B2636</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="black">#FF000000</color>
|
||||
</resources>
|
||||
4
build/android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Wails App</string>
|
||||
</resources>
|
||||
14
build/android/app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.WailsApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/wails_blue</item>
|
||||
<item name="colorPrimaryVariant">@color/wails_blue_dark</item>
|
||||
<item name="colorOnPrimary">@android:color/white</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">@color/wails_background</item>
|
||||
<item name="android:navigationBarColor">@color/wails_background</item>
|
||||
<!-- Window background -->
|
||||
<item name="android:windowBackground">@color/wails_background</item>
|
||||
</style>
|
||||
</resources>
|
||||