7ab832471271
feat: android — AutofillPlugin + BiometricPlugin + src/shared/biometric.ts (v2.13.0)
- time
- 2026-04-29 19:39 UTC · 6 months ago
- machine
- koh2
- parent
- 96ed311ac197
↓ peach-browser-7ab83247.tar.gzatlas at this save
files changed3 files
android/app/src/main/java/com/peachpasswords/app/AutofillPlugin.kt+36 −0android/app/src/main/java/com/peachpasswords/app/BiometricPlugin.kt+52 −0src/shared/biometric.ts+22 −0android/app/src/main/java/com/peachpasswords/app/AutofillPlugin.kt+36 −0
@@ -0,0 +1,36 @@1+package com.peachpasswords.app2+3+import com.getcapacitor.*4+import com.getcapacitor.annotation.CapacitorPlugin5+6+@CapacitorPlugin(name = "Autofill")7+class AutofillPlugin : Plugin() {8+9+ @PluginMethod10+ fun checkPendingSave(call: PluginCall) {11+ val prefs = context.getSharedPreferences("CapacitorStorage", Context.MODE_PRIVATE)12+ val pending = prefs.getString("peach_pending_save", null)13+ if (pending != null) {14+ prefs.edit().remove("peach_pending_save").apply()15+ call.resolve(JSObject().put("pending", pending))16+ } else {17+ call.resolve(JSObject().put("pending", null))18+ }19+ }20+21+ @PluginMethod22+ fun openAutofillSettings(call: PluginCall) {23+ val intent = android.content.Intent(24+ android.provider.Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE,25+ android.net.Uri.parse("package:${context.packageName}")26+ )27+ activity.startActivity(intent)28+ call.resolve()29+ }30+31+ @PluginMethod32+ fun isEnabledAsAutofillService(call: PluginCall) {33+ val afm = context.getSystemService(android.view.autofill.AutofillManager::class.java)34+ call.resolve(JSObject().put("enabled", afm?.hasEnabledAutofillServices() == true))35+ }36+}android/app/src/main/java/com/peachpasswords/app/BiometricPlugin.kt+52 −0
@@ -0,0 +1,52 @@1+package com.peachpasswords.app2+3+import androidx.biometric.BiometricPrompt4+import androidx.core.content.ContextCompat5+import com.getcapacitor.*6+import com.getcapacitor.annotation.CapacitorPlugin7+8+@CapacitorPlugin(name = "Biometric")9+class BiometricPlugin : Plugin() {10+11+ @PluginMethod12+ fun authenticate(call: PluginCall) {13+ val executor = ContextCompat.getMainExecutor(context)14+ val prompt = BiometricPrompt(activity, executor,15+ object : BiometricPrompt.AuthenticationCallback() {16+ override fun onAuthenticationSucceeded(17+ result: BiometricPrompt.AuthenticationResult18+ ) {19+ call.resolve(JSObject().put("verified", true))20+ }21+ override fun onAuthenticationError(code: Int, message: CharSequence) {22+ call.reject("Biometric authentication failed: $message")23+ }24+ override fun onAuthenticationFailed() {25+ call.reject("Biometric authentication failed")26+ }27+ }28+ )29+30+ val promptInfo = BiometricPrompt.PromptInfo.Builder()31+ .setTitle("Unlock Peach")32+ .setSubtitle("Use biometrics to access your vault")33+ .setNegativeButtonText("Use password instead")34+ .build()35+36+ prompt.authenticate(promptInfo)37+ }38+39+ @PluginMethod40+ fun isAvailable(call: PluginCall) {41+ val manager = androidx.biometric.BiometricManager.from(context)42+ val canAuthenticate = manager.canAuthenticate(43+ androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG44+ )45+ call.resolve(46+ JSObject().put(47+ "available",48+ canAuthenticate == androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS49+ )50+ )51+ }52+}src/shared/biometric.ts+22 −0
@@ -0,0 +1,22 @@1+import { registerPlugin, Capacitor } from "@capacitor/core";2+3+const Biometric = registerPlugin<{4+ authenticate(): Promise<{ verified: boolean }>;5+ isAvailable(): Promise<{ available: boolean }>;6+}>("Biometric");7+8+export async function biometricUnlock(): Promise<boolean> {9+ if (!Capacitor.isNativePlatform()) return false;10+ try {11+ const result = await Biometric.authenticate();12+ return result.verified;13+ } catch {14+ return false;15+ }16+}17+18+export async function biometricAvailable(): Promise<boolean> {19+ if (!Capacitor.isNativePlatform()) return false;20+ const result = await Biometric.isAvailable();21+ return result.available;22+}