feat(wallet-android): camera + notification permissions, prompted at launch

The APK had neither declared, so Settings → Apps → Deviant → Permissions
was effectively empty. First-time users would hit a dead end on "Scan QR"
and never receive a system notification for incoming WalletConnect /
WizardConnect sign requests.

Manifest additions:
- android.permission.CAMERA (for @capacitor-mlkit/barcode-scanning)
- android.hardware.camera + .front as uses-feature required=false
  (declares front-camera support without blocking cameraless installs)
- android.permission.POST_NOTIFICATIONS (required API 33+)

App.tsx: new one-shot startup effect on native platforms that calls
BarcodeScanner.requestPermissions() + Notification.requestPermission()
once per install (tracked via localStorage key
`deviant.native-perms.requested`). Android requires user consent via the
system dialog — this triggers both dialogs on first launch so Settings
shows them as granted from the start, instead of the user hitting the
dead-end later.

capacitor.build.gradle + capacitor.settings.gradle regenerated by
cap sync alongside this change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Local Dev 2026-10-22 01:32:00 +03:00
parent 0a0ee52750
commit a7c0a0eea5
4 changed files with 39 additions and 8 deletions

View file

@ -9,6 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-mlkit-barcode-scanning')
implementation project(':capgo-capacitor-native-biometric')
implementation project(':capacitor-secure-storage-plugin')

View file

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
@ -8,7 +7,6 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
android:name=".MainActivity"
@ -16,12 +14,10 @@
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
@ -29,9 +25,7 @@
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>
@ -43,4 +37,15 @@
USE_FINGERPRINT covers API 23–27. -->
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<!-- QR scanner (@capacitor-mlkit/barcode-scanning for WalletConnect pairings,
addresses, BIP-21 URIs). CAMERA covers all cameras; the two uses-feature
hints are optional so the APK still installs on cameraless devices. -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />
<!-- Push + in-app notifications (incoming WC/WZ sign requests, confirmed txs).
POST_NOTIFICATIONS is required for API 33+ to show any notification. -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>

View file

@ -2,6 +2,9 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-mlkit-barcode-scanning'
project(':capacitor-mlkit-barcode-scanning').projectDir = new File('../node_modules/@capacitor-mlkit/barcode-scanning/android')
include ':capgo-capacitor-native-biometric'
project(':capgo-capacitor-native-biometric').projectDir = new File('../node_modules/@capgo/capacitor-native-biometric/android')

View file

@ -155,6 +155,28 @@ export function App() {
// The CosmoGenesis loading splash (index.html) stays up until the user taps it —
// it dismisses itself on that gesture, so the shell does not auto-dismiss it here.
// Native: proactively request camera + notifications on first launch so they
// show up in Settings as "allowed" and the user doesn't hit a dead-end the
// first time they tap "Scan QR" or expect an incoming sign-request push.
// Android requires user consent for both — the dialog fires once per install.
useEffect(() => {
if (!Capacitor.isNativePlatform()) return;
const KEY = 'deviant.native-perms.requested';
try { if (localStorage.getItem(KEY)) return; } catch { /* private mode */ }
(async () => {
try {
const { BarcodeScanner } = await import('@capacitor-mlkit/barcode-scanning');
await BarcodeScanner.requestPermissions();
} catch { /* plugin missing or declined — Settings still carries the entry */ }
try {
if (typeof Notification !== 'undefined' && Notification.permission === 'default') {
await Notification.requestPermission();
}
} catch { /* ignore */ }
try { localStorage.setItem(KEY, '1'); } catch { /* ignore */ }
})();
}, []);
// Boot: dev-wallet shortcut first (chipnet-only escape hatch), then KeyStore.
useEffect(() => {
(async () => {