aboutsummaryrefslogtreecommitdiffstats
path: root/android/app
diff options
context:
space:
mode:
authorJurjen de Vries <[email protected]>2025-09-11 00:48:15 +0200
committerGitHub <[email protected]>2025-09-11 00:48:15 +0200
commit0c69c9848c6b98c92609d209103efc60003c9723 (patch)
tree445121def885f13d0f80c06864ad2ff4d5ef95a1 /android/app
parent2abcf6e435fca5d86960f3ba4228002bfb0a19e6 (diff)
parent9277b2921e09ee9dd5f39cf4af30a2330f4d95d1 (diff)
downloadcoinos-ui-0c69c9848c6b98c92609d209103efc60003c9723.tar.gz
coinos-ui-0c69c9848c6b98c92609d209103efc60003c9723.zip
Merge branch 'coinos:master' into master
Diffstat (limited to 'android/app')
-rw-r--r--android/app/build.gradle8
-rw-r--r--android/app/capacitor.build.gradle2
-rw-r--r--android/app/src/main/AndroidManifest.xml49
-rw-r--r--android/app/src/main/java/io/coinos/MainActivity.java208
-rw-r--r--android/app/src/main/java/io/coinos/nfcwriter/NfcWriterPlugin.java62
-rw-r--r--android/app/src/main/res/layout/activity_main.xml13
-rw-r--r--android/app/src/main/res/values/styles.xml13
7 files changed, 277 insertions, 78 deletions
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 88077520..212a6987 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -11,8 +11,8 @@ android {
applicationId "io.coinos"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 6
- versionName "1.0.6"
+ versionCode 9
+ versionName "1.0.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
@@ -24,9 +24,9 @@ android {
signingConfigs {
release {
keyAlias = System.getenv("KEY_ALIAS") ?: "coinos"
- keyPassword = System.getenv("KEY_PASSWORD") ?: "default-password"
+ keyPassword = System.getenv("KEY_PASSWORD") ?: "MPJzfq97"
storeFile = file(System.getenv("STORE_FILE") ?: "coinos.keystore")
- storePassword = System.getenv("STORE_PASSWORD") ?: "default-password"
+ storePassword = System.getenv("STORE_PASSWORD") ?: "MPJzfq97"
}
}
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index fdb4970c..52f8ca0c 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -9,7 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
-
+ implementation project(':capacitor-status-bar')
}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 86de46b2..72d7ccc0 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -14,20 +14,66 @@
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
+
+ <!-- Launcher intent -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
+ <!-- bitcoin: scheme -->
<intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bitcoin" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.nfc.action.NDEF_DISCOVERED" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="bitcoin" />
+ </intent-filter>
+
+ <!-- lightning: scheme -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lightning" />
- <data android:scheme="nostr" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.nfc.action.NDEF_DISCOVERED" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="lightning" />
+ </intent-filter>
+
+ <!-- lnurl: scheme -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="lnurl" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.nfc.action.NDEF_DISCOVERED" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="lnurl" />
+ </intent-filter>
+
+ <!-- nostr: scheme -->
+ <intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="nostr" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.nfc.action.NDEF_DISCOVERED" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="nostr" />
</intent-filter>
+ <!-- Deep linking to https://coinos.io -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
@@ -46,7 +92,6 @@
</application>
<!-- Permissions -->
-
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true"/>
diff --git a/android/app/src/main/java/io/coinos/MainActivity.java b/android/app/src/main/java/io/coinos/MainActivity.java
index 7f710952..271ee363 100644
--- a/android/app/src/main/java/io/coinos/MainActivity.java
+++ b/android/app/src/main/java/io/coinos/MainActivity.java
@@ -1,81 +1,167 @@
package io.coinos;
+import android.os.Build;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import androidx.core.view.ViewCompat;
+
import com.getcapacitor.BridgeActivity;
import com.getcapacitor.Bridge;
-import com.getcapacitor.PluginCall;
-import com.getcapacitor.PluginHandle;
+
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Parcelable;
import android.webkit.CookieManager;
import android.webkit.WebView;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import io.coinos.nfcwriter.NfcWriterPlugin;
+
public class MainActivity extends BridgeActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ private NfcAdapter nfcAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // ✅ Let Capacitor inflate its layout and wire everything
+ super.onCreate(savedInstanceState);
+ registerPlugin(NfcWriterPlugin.class);
+
+ WebView webView = bridge.getWebView(); // This works *after* super.onCreate()
+
+ // ✅ Ensure layout respects system bars
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+
+ // ✅ Apply top inset padding if needed
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ webView.setOnApplyWindowInsetsListener((v, insets) -> {
+ v.setPadding(0, insets.getSystemWindowInsetTop(), 0, 0);
+ return insets.consumeSystemWindowInsets();
+ });
+ }
+
+ // Other app setup
+ CookieManager cookieManager = CookieManager.getInstance();
+ cookieManager.setAcceptCookie(true);
+ webView.getSettings().setJavaScriptEnabled(true);
+ webView.getSettings().setDomStorageEnabled(true);
+
+ handleIntent(getIntent());
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ handleIntent(intent);
+ }
+
+ private void handleIntent(Intent intent) {
+ if (intent == null) return;
- WebView webView = bridge.getWebView();
- CookieManager cookieManager = CookieManager.getInstance();
- cookieManager.setAcceptCookie(true);
- webView.getSettings().setJavaScriptEnabled(true);
- webView.getSettings().setDomStorageEnabled(true);
+ String action = intent.getAction();
+ Uri data = null;
- Intent intent = getIntent();
- if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
- Uri data = intent.getData();
- if (data != null) {
- String url = data.toString();
- Bridge bridge = getBridge();
-
- if (bridge != null) {
- String encodedUrl = Uri.encode(url);
- String destinationUrl = "https://coinos.io/send/" + encodedUrl;
+ if (Intent.ACTION_VIEW.equals(action)) {
+ data = intent.getData();
+ } else if ("android.nfc.action.NDEF_DISCOVERED".equals(action)) {
+ Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
+ if (rawMsgs != null && rawMsgs.length > 0) {
+ NdefMessage ndefMessage = (NdefMessage) rawMsgs[0];
+ for (NdefRecord record : ndefMessage.getRecords()) {
+ if (record.getTnf() == NdefRecord.TNF_WELL_KNOWN &&
+ Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
- bridge.getWebView().post(() -> {
- bridge.getWebView().loadUrl(destinationUrl);
- });
- }
- }
- }
- }
+ byte[] payload = record.getPayload();
+ byte prefix = payload[0];
+ String uriPrefix = resolveUriPrefix(prefix);
+ String uriBody = new String(Arrays.copyOfRange(payload, 1, payload.length), Charset.forName("UTF-8"));
+ data = Uri.parse(uriPrefix + uriBody);
+ break;
+ }
+ }
+ }
+ }
- @Override
- public void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
- Uri data = intent.getData();
- if (data != null) {
- String url = data.toString();
+ if (data != null) {
+ String url = data.toString();
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ String encodedUrl = Uri.encode(url);
+ String destinationUrl = "https://coinos.io/send/" + encodedUrl;
- // Dispatch deep link when app is already open
- Bridge bridge = getBridge();
- if (bridge != null) {
- String encodedUrl = Uri.encode(url);
- String destinationUrl = "https://coinos.io/send/" + encodedUrl;
+ bridge.getWebView().post(() -> {
+ bridge.getWebView().loadUrl(destinationUrl);
+ });
+ }
+ }
+ }
- bridge.getWebView().post(() -> {
- bridge.getWebView().loadUrl(destinationUrl);
- });
- }
- }
- }
- }
+ private String resolveUriPrefix(byte prefix) {
+ switch (prefix) {
+ case 0x00: return "";
+ case 0x01: return "http://www.";
+ case 0x02: return "https://www.";
+ case 0x03: return "http://";
+ case 0x04: return "https://";
+ case 0x05: return "tel:";
+ case 0x06: return "mailto:";
+ case 0x07: return "ftp://anonymous:anonymous@";
+ case 0x08: return "ftp://ftp.";
+ case 0x09: return "ftps://";
+ case 0x0A: return "sftp://";
+ case 0x0B: return "smb://";
+ case 0x0C: return "nfs://";
+ case 0x0D: return "ftp://";
+ case 0x0E: return "dav://";
+ case 0x0F: return "news:";
+ case 0x10: return "telnet://";
+ case 0x11: return "imap:";
+ case 0x12: return "rtsp://";
+ case 0x13: return "urn:";
+ case 0x14: return "pop:";
+ case 0x15: return "sip:";
+ case 0x16: return "sips:";
+ case 0x17: return "tftp:";
+ case 0x18: return "btspp://";
+ case 0x19: return "btl2cap://";
+ case 0x1A: return "btgoep://";
+ case 0x1B: return "tcpobex://";
+ case 0x1C: return "irdaobex://";
+ case 0x1D: return "file://";
+ case 0x1E: return "urn:epc:id:";
+ case 0x1F: return "urn:epc:tag:";
+ case 0x20: return "urn:epc:pat:";
+ case 0x21: return "urn:epc:raw:";
+ case 0x22: return "urn:epc:";
+ case 0x23: return "urn:nfc:";
+ default: return "";
+ }
+ }
- @Override
- public void onPause() {
- super.onPause();
- CookieManager.getInstance().flush();
- }
+ @Override
+ public void onPause() {
+ super.onPause();
+ CookieManager.getInstance().flush();
+ }
- @Override
- public void onBackPressed() {
- WebView webView = getBridge().getWebView();
- if (webView.canGoBack()) {
- webView.goBack();
- } else {
- super.onBackPressed(); // Default behavior, which minimizes the app
- }
- }
+ @Override
+ public void onBackPressed() {
+ WebView webView = getBridge().getWebView();
+ if (webView.canGoBack()) {
+ webView.goBack();
+ } else {
+ super.onBackPressed();
+ }
+ }
}
diff --git a/android/app/src/main/java/io/coinos/nfcwriter/NfcWriterPlugin.java b/android/app/src/main/java/io/coinos/nfcwriter/NfcWriterPlugin.java
new file mode 100644
index 00000000..d2954e9b
--- /dev/null
+++ b/android/app/src/main/java/io/coinos/nfcwriter/NfcWriterPlugin.java
@@ -0,0 +1,62 @@
+// src/android/NfcWriterPlugin.java
+package io.coinos.nfcwriter;
+
+import android.nfc.NfcAdapter;
+import android.nfc.Tag;
+import android.nfc.NfcAdapter.ReaderCallback;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.getcapacitor.Plugin;
+import com.getcapacitor.PluginCall;
+import com.getcapacitor.PluginMethod;
+import com.getcapacitor.annotation.CapacitorPlugin;
+
+@CapacitorPlugin(name = "NfcWriter")
+public class NfcWriterPlugin extends Plugin {
+ private NfcAdapter nfcAdapter;
+
+ @Override
+ public void load() {
+ super.load();
+ nfcAdapter = NfcAdapter.getDefaultAdapter(getContext());
+ }
+
+ @PluginMethod
+ public void enableExclusiveNfcMode(PluginCall call) {
+ if (nfcAdapter == null) {
+ call.reject("NFC adapter not available");
+ return;
+ }
+
+ nfcAdapter.enableReaderMode(
+ getActivity(),
+ new ReaderCallback() {
+ @Override
+ public void onTagDiscovered(Tag tag) {
+ Log.d("NfcWriter", "Tag discovered");
+ bridge.getWebView().post(() -> {
+ bridge.getWebView().evaluateJavascript(
+ "window.dispatchEvent(new CustomEvent('nativeNfcTagDiscovered'))",
+ null
+ );
+ });
+ }
+ },
+ NfcAdapter.FLAG_READER_NFC_A |
+ NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
+ NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
+ null
+ );
+
+ call.resolve();
+ }
+
+ @PluginMethod
+ public void disableExclusiveNfcMode(PluginCall call) {
+ if (nfcAdapter != null) {
+ nfcAdapter.disableReaderMode(getActivity());
+ }
+ call.resolve();
+ }
+}
diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml
index b5ad1387..3710e23b 100644
--- a/android/app/src/main/res/layout/activity_main.xml
+++ b/android/app/src/main/res/layout/activity_main.xml
@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".MainActivity">
+ android:fitsSystemWindows="true">
<WebView
+ android:id="@+id/webview"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true" />
+</FrameLayout>
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index be874e54..07128ac1 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -1,9 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
@@ -15,8 +13,15 @@
<item name="android:background">@null</item>
</style>
-
+ <!-- 🚨 This is your actual launch theme. It must stop fullscreen mode. -->
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
+
+ <!-- ✅ These lines are critical -->
+ <item name="android:windowFullscreen">false</item>
+ <item name="android:windowTranslucentStatus">false</item>
+ <item name="android:fitsSystemWindows">true</item>
+ <item name="android:statusBarColor">#000000</item> <!-- Or match your splash background -->
</style>
-</resources> \ No newline at end of file
+
+</resources>