fix(android): remove custom MainActivity that broke app startup (black screen)

The injected MainActivity overrode Capacitor's WebViewClient, wiping out the
client Capacitor needs to load the web layer — causing a black-screen crash on
launch. Remove the MainActivity template, its injection script, and the build
wiring. The real signing fix (setPackageName before signEvent) is unaffected and
remains in place. Bump to 1.2.10.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-09-04 22:02:23 +07:00
parent 50914b7b9b
commit 88685262ce
4 changed files with 14 additions and 164 deletions

View file

@ -1,101 +0,0 @@
package org.skatespots.app;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.getcapacitor.BridgeActivity;
/**
* Custom MainActivity that lets the WebView hand OFF `intent:` / `nostrsigner:`
* URLs to the Android OS so Amber's approval screen actually opens.
*
* WHY THIS EXISTS
* ───────────────
* When the JS signer navigates to an `intent:...;scheme=nostrsigner;...;end`
* URL, Capacitor's default WebView treats it as a page navigation and silently
* DROPS it — so Amber never launches and every signing action (delete, accept
* crew invite, zap, profile edit) hangs forever. Login works only because it
* goes through the native plugin's startActivityForResult; JS-initiated intents
* were never reaching the OS. On GrapheneOS this is enforced strictly.
*
* The fix: override shouldOverrideUrlLoading. Whenever the WebView tries to load
* an `intent:` or `nostrsigner:` URL, we parse it and call startActivity()
* ourselves — a REAL native launch that opens Amber, exactly like login. The
* result comes back via the app's custom-scheme deep link
* (org.skatespots.app://amber-callback...), handled by DeepLinkHandler /
* resolveAmberIntent in the web layer.
*
* This file is injected into the generated android/ project at build time by
* scripts/inject-main-activity.mjs (android/ is not committed).
*/
public class MainActivity extends BridgeActivity {
private static final String TAG = "SkateSpotsAmber";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final WebView webView = getBridge().getWebView();
if (webView == null) {
return;
}
final WebViewClient existing = new WebViewClient();
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (handleExternalScheme(url)) {
return true;
}
return super.shouldOverrideUrlLoading(view, url);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, android.webkit.WebResourceRequest request) {
final String url = request.getUrl() != null ? request.getUrl().toString() : null;
if (url != null && handleExternalScheme(url)) {
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}
});
}
/**
* If the URL is an Amber/signer intent, launch it natively and return true.
* Returns false for normal in-app URLs so Capacitor handles them as usual.
*/
private boolean handleExternalScheme(String url) {
if (url == null) {
return false;
}
try {
if (url.startsWith("intent:")) {
// Parse the full Android intent: URI (carries scheme, extras, end).
Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return true;
}
if (url.startsWith("nostrsigner:")) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
return true;
}
} catch (Exception e) {
Log.e(TAG, "Failed to launch external signer intent: " + e.getMessage(), e);
// Fall through — let the WebView try to handle it (will no-op safely).
}
return false;
}
}

View file

@ -1,7 +1,7 @@
{
"name": "skate-spots",
"private": true,
"version": "1.2.9",
"version": "1.2.10",
"type": "module",
"homepage": "https://skate-spots.com",
"scripts": {
@ -9,11 +9,11 @@
"build": "npm i --silent && vite build -l error && cp dist/index.html dist/404.html && echo 'Project built successfully!'",
"test": "npm i --silent && tsc --noEmit && eslint && vitest run --reporter=dot --silent && vite build -l error && cp dist/index.html dist/404.html && echo 'All tests passed!'",
"deploy": "npm i --silent && npm run build && npx -y nostr-deploy-cli deploy --skip-setup",
"android:init": "npm run android:build:apk && npx cap add android && npx cap sync android && node scripts/inject-amber-queries.mjs && node scripts/inject-main-activity.mjs",
"android:sync": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && node scripts/inject-main-activity.mjs",
"android:init": "npm run android:build:apk && npx cap add android && npx cap sync android && node scripts/inject-amber-queries.mjs",
"android:sync": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs",
"android:open": "npx cap open android",
"android:version": "node scripts/sync-android-version.mjs",
"android:build": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && node scripts/inject-main-activity.mjs && cd android && ./gradlew assembleRelease",
"android:build": "npm run android:build:apk && npx cap sync android && node scripts/sync-android-version.mjs && node scripts/inject-amber-queries.mjs && cd android && ./gradlew assembleRelease",
"android:build:apk": "CAPACITOR_BUILD=1 vite build && sed -i 's|<script type=\"module\" src=\"/shakespeare_tailwind[^\"]*\\.js\"></script>||g' dist/index.html && sed -i 's|<style type=\"text/tailwindcss\">|<style>|g' dist/index.html && cp dist/index.html dist/404.html"
},
"dependencies": {

View file

@ -1,3 +1,13 @@
v1.2.10
🛠️ Fixes a startup crash (black screen) introduced in the previous build and
keeps the Amber signing fix: signing now opens Amber the same reliable way
login does, so delete / accept invite / zap complete instead of hanging.
After updating, log OUT and log back IN once.
---
v1.2.8
🛠️ Amber finally pops up — signing works (the real fix)

View file

@ -1,59 +0,0 @@
#!/usr/bin/env node
/**
* Inject the custom MainActivity.java into the generated Android project.
*
* WHY THIS EXISTS
* ───────────────
* Capacitor's default MainActivity uses a WebView that silently DROPS
* `intent:` / `nostrsigner:` URL navigations, so Amber's approval screen never
* opens and every signing action (delete, accept crew invite, zap, profile
* edit) hangs forever. Our MainActivity overrides shouldOverrideUrlLoading to
* launch those intents natively via startActivity() — the real fix that opens
* Amber, matching how login already works.
*
* The android/ folder is generated per-machine (not committed), so we keep the
* source of truth in android-templates/MainActivity.java and copy it in on
* every build. Idempotent. Exits quietly if android/ hasn't been generated yet.
*/
import { readFileSync, writeFileSync, existsSync, copyFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
// Package path org.skatespots.app → org/skatespots/app
const target = join(
root,
'android',
'app',
'src',
'main',
'java',
'org',
'skatespots',
'app',
'MainActivity.java',
);
const template = join(root, 'android-templates', 'MainActivity.java');
if (!existsSync(target)) {
console.log('[inject-main-activity] android/ not generated yet — skipping.');
process.exit(0);
}
if (!existsSync(template)) {
console.error('[inject-main-activity] template android-templates/MainActivity.java missing — aborting.');
process.exit(1);
}
const current = readFileSync(target, 'utf8');
const desired = readFileSync(template, 'utf8');
if (current === desired) {
console.log('[inject-main-activity] MainActivity.java already up to date.');
process.exit(0);
}
copyFileSync(template, target);
console.log('[inject-main-activity] ✓ Installed custom MainActivity.java (intent hand-off for Amber).');