diff options
| author | Jurjen de Vries <[email protected]> | 2025-09-11 00:48:15 +0200 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-09-11 00:48:15 +0200 |
| commit | 0c69c9848c6b98c92609d209103efc60003c9723 (patch) | |
| tree | 445121def885f13d0f80c06864ad2ff4d5ef95a1 /android/app/src | |
| parent | 2abcf6e435fca5d86960f3ba4228002bfb0a19e6 (diff) | |
| parent | 9277b2921e09ee9dd5f39cf4af30a2330f4d95d1 (diff) | |
| download | coinos-ui-0c69c9848c6b98c92609d209103efc60003c9723.tar.gz coinos-ui-0c69c9848c6b98c92609d209103efc60003c9723.zip | |
Merge branch 'coinos:master' into master
Diffstat (limited to 'android/app/src')
| -rw-r--r-- | android/app/src/main/AndroidManifest.xml | 49 | ||||
| -rw-r--r-- | android/app/src/main/java/io/coinos/MainActivity.java | 208 | ||||
| -rw-r--r-- | android/app/src/main/java/io/coinos/nfcwriter/NfcWriterPlugin.java | 62 | ||||
| -rw-r--r-- | android/app/src/main/res/layout/activity_main.xml | 13 | ||||
| -rw-r--r-- | android/app/src/main/res/values/styles.xml | 13 |
5 files changed, 272 insertions, 73 deletions
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> |
