From 6e25749cb575fc3124b16c12e922c5d27eb0751b Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sun, 30 Nov 2025 21:36:39 -0500 Subject: [PATCH 1/3] Add Plugins documentation section for Mobile v2 New documentation covering the NativePHP Mobile plugin system: - Introduction to plugins - Using plugins (installation, events) - Creating plugins (scaffolding, structure, manifest) - Bridge functions (Swift/Kotlin implementations) - Events (native-to-PHP dispatching) - Lifecycle hooks (pre_compile, copy_assets, etc.) - Permissions & dependencies (Android, iOS, CocoaPods) - Validation & testing --- .../views/docs/mobile/2/plugins/_index.md | 4 + .../docs/mobile/2/plugins/bridge-functions.md | 130 +++++++++++++++ .../docs/mobile/2/plugins/creating-plugins.md | 154 +++++++++++++++++ .../views/docs/mobile/2/plugins/events.md | 121 ++++++++++++++ .../docs/mobile/2/plugins/introduction.md | 47 ++++++ .../docs/mobile/2/plugins/lifecycle-hooks.md | 124 ++++++++++++++ .../2/plugins/permissions-dependencies.md | 155 ++++++++++++++++++ .../docs/mobile/2/plugins/using-plugins.md | 77 +++++++++ .../mobile/2/plugins/validation-testing.md | 82 +++++++++ 9 files changed, 894 insertions(+) create mode 100644 resources/views/docs/mobile/2/plugins/_index.md create mode 100644 resources/views/docs/mobile/2/plugins/bridge-functions.md create mode 100644 resources/views/docs/mobile/2/plugins/creating-plugins.md create mode 100644 resources/views/docs/mobile/2/plugins/events.md create mode 100644 resources/views/docs/mobile/2/plugins/introduction.md create mode 100644 resources/views/docs/mobile/2/plugins/lifecycle-hooks.md create mode 100644 resources/views/docs/mobile/2/plugins/permissions-dependencies.md create mode 100644 resources/views/docs/mobile/2/plugins/using-plugins.md create mode 100644 resources/views/docs/mobile/2/plugins/validation-testing.md diff --git a/resources/views/docs/mobile/2/plugins/_index.md b/resources/views/docs/mobile/2/plugins/_index.md new file mode 100644 index 00000000..bbc1a5ec --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/_index.md @@ -0,0 +1,4 @@ +--- +title: Plugins +order: 60 +--- diff --git a/resources/views/docs/mobile/2/plugins/bridge-functions.md b/resources/views/docs/mobile/2/plugins/bridge-functions.md new file mode 100644 index 00000000..31ab8b0a --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/bridge-functions.md @@ -0,0 +1,130 @@ +--- +title: Bridge Functions +order: 400 +--- + +## How Bridge Functions Work + +Bridge functions are the connection between your PHP code and native platform code. When you call a method like +`MyPlugin::doSomething()`, NativePHP routes that to your Swift or Kotlin implementation running on the device. + +The flow: +1. PHP calls `nativephp_call('MyPlugin.DoSomething', $params)` +2. The native bridge locates the registered function +3. Your native code executes and returns a response +4. PHP receives the result + +## Declaring Bridge Functions + +In your `nativephp.json`, declare each function with its platform implementations: + +```json +{ + "bridge_functions": [ + { + "name": "MyPlugin.DoSomething", + "ios": "MyPluginFunctions.DoSomething", + "android": "com.vendor.plugin.myplugin.MyPluginFunctions.DoSomething", + "description": "Does something useful" + } + ] +} +``` + +The `name` is what PHP uses. The platform-specific values point to your native class and method. + +## Swift Implementation (iOS) + +Create your functions in `resources/ios/Sources/`: + +```swift +import Foundation + +enum MyPluginFunctions { + + class DoSomething: BridgeFunction { + func execute(parameters: [String: Any]) throws -> [String: Any] { + let option = parameters["option"] as? String ?? "" + + // Do your native work here + + return BridgeResponse.success(data: [ + "result": "completed", + "option": option + ]) + } + } +} +``` + +Key points: +- Implement the `BridgeFunction` protocol +- Parameters come as a dictionary +- Return using `BridgeResponse.success()` or `BridgeResponse.error()` + +## Kotlin Implementation (Android) + +Create your functions in `resources/android/src/.../`: + +```kotlin +package com.vendor.plugin.myplugin + +import com.example.androidphp.bridge.BridgeFunction +import com.example.androidphp.bridge.BridgeResponse + +object MyPluginFunctions { + + class DoSomething : BridgeFunction { + override fun execute(parameters: Map): Map { + val option = parameters["option"] as? String ?: "" + + // Do your native work here + + return BridgeResponse.success(mapOf( + "result" to "completed", + "option" to option + )) + } + } +} +``` + + + +## Calling from PHP + +Create a facade method that calls your bridge function: + +```php +class MyPlugin +{ + public function doSomething(array $options = []): mixed + { + $result = nativephp_call('MyPlugin.DoSomething', json_encode($options)); + + return json_decode($result)?->data; + } +} +``` + +## Error Handling + +Return errors from native code using `BridgeResponse.error()`: + +```swift +// Swift +return BridgeResponse.error(message: "Something went wrong") +``` + +```kotlin +// Kotlin +return BridgeResponse.error("Something went wrong") +``` + +The error message is available in PHP through the response. diff --git a/resources/views/docs/mobile/2/plugins/creating-plugins.md b/resources/views/docs/mobile/2/plugins/creating-plugins.md new file mode 100644 index 00000000..1412f991 --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/creating-plugins.md @@ -0,0 +1,154 @@ +--- +title: Creating Plugins +order: 300 +--- + +## Scaffolding a Plugin + +The quickest way to create a plugin is with the interactive scaffolding command: + +```shell +php artisan native:plugin:create +``` + +This walks you through naming, namespace selection, and feature options, then generates a complete plugin structure. + +## Plugin Structure + +A plugin follows a standard layout: + +``` +my-plugin/ +├── composer.json # Package metadata, type must be "nativephp-plugin" +├── nativephp.json # Plugin manifest +├── src/ +│ ├── MyPluginServiceProvider.php +│ ├── MyPlugin.php # Main class +│ ├── Facades/ +│ │ └── MyPlugin.php +│ ├── Events/ +│ │ └── SomethingHappened.php +│ └── Commands/ # Lifecycle hook commands +├── resources/ +│ ├── android/src/ # Kotlin bridge functions +│ ├── ios/Sources/ # Swift bridge functions +│ └── js/ # JavaScript library stubs +``` + +## The composer.json + +Your `composer.json` must specify the plugin type: + +```json +{ + "name": "vendor/my-plugin", + "type": "nativephp-plugin", + "extra": { + "laravel": { + "providers": ["Vendor\\MyPlugin\\MyPluginServiceProvider"] + }, + "nativephp": { + "manifest": "nativephp.json" + } + } +} +``` + +The `type: nativephp-plugin` tells NativePHP to look for native code in this package. + +## The nativephp.json Manifest + +The manifest declares everything about your plugin: + +```json +{ + "name": "vendor/my-plugin", + "namespace": "MyPlugin", + "bridge_functions": [ + { + "name": "MyPlugin.DoSomething", + "ios": "MyPluginFunctions.DoSomething", + "android": "com.vendor.plugin.myplugin.MyPluginFunctions.DoSomething" + } + ], + "events": ["Vendor\\MyPlugin\\Events\\SomethingHappened"], + "permissions": { + "android": ["android.permission.SOME_PERMISSION"], + "ios": { + "NSCameraUsageDescription": "We need camera access" + } + } +} +``` + +The key fields: + +- **namespace** — Used to generate bridge function registration code +- **bridge_functions** — Maps PHP calls to native implementations +- **events** — Event classes your plugin dispatches +- **permissions** — Platform permissions your plugin requires + +## Local Development + +During development, add your plugin to your app's `composer.json` as a path repository: + +```json +{ + "repositories": [ + {"type": "path", "url": "../packages/my-plugin"} + ] +} +``` + +Then require it: + +```shell +composer require vendor/my-plugin +``` + +Changes to your plugin's PHP code are picked up immediately. Changes to native code require a rebuild with +`php artisan native:run`. + + + +## JavaScript Library + +Plugins can provide a JavaScript library for SPA frameworks. The scaffolding creates a stub in `resources/js/`: + +```js +// resources/js/myPlugin.js +const baseUrl = 'https://github.com/_native/api/call'; + +async function bridgeCall(method, params = {}) { + const response = await fetch(baseUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ method, params }) + }); + return response.json(); +} + +export async function doSomething(options = {}) { + return bridgeCall('MyPlugin.DoSomething', options); +} +``` + +Users can then import your functions directly in Vue, React, or vanilla JS. + +## Plugin Writer Agent + +If you're building a complex plugin, install the plugin-writer agent to help with native code patterns: + +```shell +php artisan native:plugin:install-agent +``` + +This copies a specialized agent configuration to your project's `.claude/agents/` directory. The agent understands +NativePHP plugin patterns and can help write Swift/Kotlin bridge functions, event dispatching, and hook commands. diff --git a/resources/views/docs/mobile/2/plugins/events.md b/resources/views/docs/mobile/2/plugins/events.md new file mode 100644 index 00000000..4f4be8c7 --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/events.md @@ -0,0 +1,121 @@ +--- +title: Events +order: 500 +--- + +## Dispatching Events from Native Code + +Many native operations are asynchronous — ML inference, sensor readings, background tasks. Your native code needs a +way to send results back to PHP when they're ready. That's where events come in. + +Events are dispatched from native code and received by your Livewire components, just like the built-in APIs. + +## Declaring Events + +Add your event classes to the manifest: + +```json +{ + "events": [ + "Vendor\\MyPlugin\\Events\\ProcessingComplete", + "Vendor\\MyPlugin\\Events\\ProcessingError" + ] +} +``` + +## Creating Event Classes + +Events are simple PHP classes: + +```php +namespace Vendor\MyPlugin\Events; + +use Illuminate\Foundation\Events\Dispatchable; +use Illuminate\Queue\SerializesModels; + +class ProcessingComplete +{ + use Dispatchable, SerializesModels; + + public function __construct( + public string $result, + public ?string $id = null + ) {} +} +``` + + + +## Swift Event Dispatching + +Dispatch events using `LaravelBridge.shared.send`: + +```swift +// Build your payload +let payload: [String: Any] = [ + "result": processedData, + "id": requestId +] + +// Dispatch to PHP +LaravelBridge.shared.send?( + "Vendor\\MyPlugin\\Events\\ProcessingComplete", + payload +) +``` + +This runs synchronously on the main thread, so wrap in `DispatchQueue.main.async` if needed. + +## Kotlin Event Dispatching + +Dispatch events using `NativeActionCoordinator.dispatchEvent`: + +```kotlin +import android.os.Handler +import android.os.Looper + +// Build your payload +val payload = JSONObject().apply { + put("result", processedData) + put("id", requestId) +} + +// Must dispatch on main thread +Handler(Looper.getMainLooper()).post { + NativeActionCoordinator.dispatchEvent( + activity, + "Vendor\\MyPlugin\\Events\\ProcessingComplete", + payload.toString() + ) +} +``` + + + +## Listening in Livewire + +Use the `#[OnNative]` attribute to handle plugin events: + +```php +use Native\Mobile\Attributes\OnNative; +use Vendor\MyPlugin\Events\ProcessingComplete; + +#[OnNative(ProcessingComplete::class)] +public function handleComplete(string $result, ?string $id = null) +{ + $this->processedResult = $result; +} +``` + +The attribute wires up the JavaScript event listener automatically. diff --git a/resources/views/docs/mobile/2/plugins/introduction.md b/resources/views/docs/mobile/2/plugins/introduction.md new file mode 100644 index 00000000..c4fbac81 --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/introduction.md @@ -0,0 +1,47 @@ +--- +title: Introduction +order: 100 +--- + +## What are Plugins? + +Plugins extend NativePHP for Mobile with native functionality that goes beyond the built-in APIs. Need on-device ML, +Bluetooth, or a custom hardware integration? Plugins let you add these capabilities without forking the core package. + +A plugin is a Composer package that bundles: +- **PHP code** — Facades, events, and service providers you use in Laravel +- **Native code** — Swift (iOS) and Kotlin (Android) implementations +- **A manifest** — Declares what the plugin provides and needs + +When you build your app, NativePHP automatically discovers installed plugins and compiles their native code into your +app alongside the built-in features. + +## Why Use Plugins? + +The built-in [APIs](../apis/) cover common functionality like camera, biometrics, and push notifications. But mobile +platforms offer much more — AR, ML, NFC, health sensors, and countless third-party SDKs. + +Plugins let the community build and share these integrations. Install a plugin and its features become available to +your PHP code just like the built-in APIs. + +## Plugin Architecture + +Plugins follow the same patterns as NativePHP's core: + +```php +use Vendor\MyPlugin\Facades\MyPlugin; + +// Call native functions +MyPlugin::doSomething(); + +// Listen for events +#[OnNative(MyPlugin\Events\SomethingHappened::class)] +public function handleResult($data) +{ + // Handle it +} +``` + +The native code runs on-device, communicates with your PHP through the bridge, and dispatches events back to your +Livewire components. It's the same model you're already using. + diff --git a/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md b/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md new file mode 100644 index 00000000..b327d2ea --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md @@ -0,0 +1,124 @@ +--- +title: Lifecycle Hooks +order: 600 +--- + +## What are Lifecycle Hooks? + +Hooks let your plugin run code at specific points during the build process. Need to download an ML model before +compilation? Copy assets to the right platform directory? Run validation? Hooks handle these scenarios. + +## Available Hooks + +| Hook | When it Runs | +|------|--------------| +| `pre_compile` | Before native code compilation | +| `post_compile` | After compilation, before build | +| `copy_assets` | When copying assets to native projects | +| `post_build` | After a successful build | + +## Creating Hook Commands + +Generate a hook command with the scaffolding tool: + +```shell +php artisan native:plugin:make-hook +``` + +This walks you through selecting your plugin and which hooks to create. It generates the command class, updates +your manifest, and registers the command in your service provider. + +## Hook Command Structure + +Hook commands extend `NativePluginHookCommand`: + +```php +use Native\Mobile\Plugins\Commands\NativePluginHookCommand; + +class CopyAssetsCommand extends NativePluginHookCommand +{ + protected $signature = 'nativephp:my-plugin:copy-assets'; + + public function handle(): int + { + if ($this->isAndroid()) { + $this->copyToAndroidAssets('models/model.tflite', 'models/model.tflite'); + } + + if ($this->isIos()) { + $this->copyToIosBundle('models/model.mlmodel', 'models/model.mlmodel'); + } + + return self::SUCCESS; + } +} +``` + +## Available Helpers + +The base command provides helpers for common tasks: + +**Platform Detection:** +- `$this->platform()` — Returns `'ios'` or `'android'` +- `$this->isIos()`, `$this->isAndroid()` — Boolean checks + +**Paths:** +- `$this->buildPath()` — Path to the native project being built +- `$this->pluginPath()` — Path to your plugin package +- `$this->appId()` — The app's bundle ID (e.g., `com.example.app`) + +**File Operations:** +- `$this->copyToAndroidAssets($src, $dest)` — Copy to Android assets +- `$this->copyToIosBundle($src, $dest)` — Copy to iOS bundle +- `$this->downloadIfMissing($url, $dest)` — Download a file if it doesn't exist +- `$this->unzip($zipPath, $extractTo)` — Extract a zip file + +## Declaring Hooks in the Manifest + +Add hooks to your `nativephp.json`: + +```json +{ + "hooks": { + "copy_assets": "nativephp:my-plugin:copy-assets", + "pre_compile": "nativephp:my-plugin:pre-compile" + } +} +``` + +The value is your Artisan command signature. + +## Example: Downloading an ML Model + +```php +public function handle(): int +{ + $modelPath = $this->pluginPath() . 'https://github.com/resources/models/model.tflite'; + + // Download if not cached locally + $this->downloadIfMissing( + 'https://example.com/models/v2/model.tflite', + $modelPath + ); + + // Copy to the appropriate platform location + if ($this->isAndroid()) { + $this->copyToAndroidAssets('models/model.tflite', 'models/model.tflite'); + $this->info('Model copied to Android assets'); + } + + if ($this->isIos()) { + $this->copyToIosBundle('models/model.tflite', 'models/model.tflite'); + $this->info('Model copied to iOS bundle'); + } + + return self::SUCCESS; +} +``` + + diff --git a/resources/views/docs/mobile/2/plugins/permissions-dependencies.md b/resources/views/docs/mobile/2/plugins/permissions-dependencies.md new file mode 100644 index 00000000..39de8c60 --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/permissions-dependencies.md @@ -0,0 +1,155 @@ +--- +title: Permissions & Dependencies +order: 700 +--- + +## Declaring Permissions + +If your plugin needs platform permissions (camera, microphone, location, etc.), declare them in the manifest. +NativePHP automatically merges these with the app's permissions during build. + +## Android Permissions + +List Android permissions as strings: + +```json +{ + "permissions": { + "android": [ + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO", + "android.permission.ACCESS_FINE_LOCATION" + ] + } +} +``` + +These are added to the app's `AndroidManifest.xml` at build time. + +## iOS Permissions + +iOS requires usage descriptions for each permission. Provide these as key-value pairs: + +```json +{ + "permissions": { + "ios": { + "NSCameraUsageDescription": "This app uses the camera for scanning", + "NSMicrophoneUsageDescription": "This app records audio for transcription", + "NSLocationWhenInUseUsageDescription": "This app needs your location" + } + } +} +``` + +These are merged into the app's `Info.plist`. + + + +## Android Dependencies + +Add Gradle dependencies for Android: + +```json +{ + "dependencies": { + "android": { + "implementation": [ + "com.google.mlkit:face-detection:16.1.5", + "org.tensorflow:tensorflow-lite:2.13.0" + ] + } + } +} +``` + +These are added to the app's `build.gradle` during compilation. + +## iOS Dependencies + +### Swift Packages + +Add Swift Package dependencies for iOS: + +```json +{ + "dependencies": { + "ios": { + "swift_packages": [ + { + "url": "https://github.com/example/SomePackage", + "version": "1.0.0" + } + ] + } + } +} +``` + +### CocoaPods + +For libraries that only support CocoaPods, add them to the `cocoapods` array: + +```json +{ + "dependencies": { + "ios": { + "cocoapods": [ + "GoogleMLKit/FaceDetection", + "TensorFlowLiteSwift" + ] + } + } +} +``` + +NativePHP generates a `Podfile` and runs `pod install` during the iOS build process. + + + + + +## Full Example + +Here's a complete permissions and dependencies section for an ML plugin: + +```json +{ + "permissions": { + "android": [ + "android.permission.CAMERA" + ], + "ios": { + "NSCameraUsageDescription": "Camera is used for real-time object detection" + } + }, + "dependencies": { + "android": { + "implementation": [ + "com.google.mlkit:object-detection:17.0.0" + ] + }, + "ios": { + "swift_packages": [] + } + } +} +``` diff --git a/resources/views/docs/mobile/2/plugins/using-plugins.md b/resources/views/docs/mobile/2/plugins/using-plugins.md new file mode 100644 index 00000000..9b13509a --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/using-plugins.md @@ -0,0 +1,77 @@ +--- +title: Using Plugins +order: 200 +--- + +## Installing a Plugin + +Plugins are standard Composer packages. Install them like any other Laravel package: + +```shell +composer require vendor/nativephp-plugin-name +``` + +The plugin's service provider will be auto-discovered by Laravel. + +## Verify Installation + +Check that NativePHP sees your plugin: + +```shell +php artisan native:plugin:list +``` + +You'll see the plugin name, version, and what it provides (bridge functions, events, hooks). + +## Rebuild Your App + +After installing a plugin, rebuild to compile its native code: + +```shell +php artisan native:run +``` + +The plugin's Swift and Kotlin code gets compiled into your app automatically. + +## Using Plugin Features + +Each plugin provides its own facade and follows the same patterns as the built-in APIs. + +```php +use Vendor\PluginName\Facades\PluginName; + +// Call a native function +$result = PluginName::doSomething(['option' => 'value']); +``` + +## Listening to Plugin Events + +Plugins dispatch events just like the core APIs. Use the `#[OnNative]` attribute in your Livewire components: + +```php +use Native\Mobile\Attributes\OnNative; +use Vendor\PluginName\Events\SomethingCompleted; + +#[OnNative(SomethingCompleted::class)] +public function handleCompletion($result) +{ + // React to the event +} +``` + + + +## Permissions + +Some plugins require additional permissions. These are declared in the plugin's manifest and automatically merged +into your app's configuration during build. + +If a plugin needs camera access, microphone, or other sensitive permissions, you'll see them listed when you run +`native:plugin:list`. diff --git a/resources/views/docs/mobile/2/plugins/validation-testing.md b/resources/views/docs/mobile/2/plugins/validation-testing.md new file mode 100644 index 00000000..a90d8557 --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/validation-testing.md @@ -0,0 +1,82 @@ +--- +title: Validation & Testing +order: 800 +--- + +## Validating Your Plugin + +Before building, validate your plugin to catch common issues: + +```shell +php artisan native:plugin:validate +``` + +This checks: +- Manifest syntax and required fields +- Bridge function declarations match native code +- Hook commands are registered and exist +- Declared assets are present + +## Common Validation Errors + +**"Bridge function not found in native code"** + +Your manifest declares a function, but the Swift or Kotlin implementation is missing or named differently. Check +that class names and function names match exactly. + +**"Invalid manifest JSON"** + +Your `nativephp.json` has a syntax error. Check for trailing commas, missing quotes, or unclosed brackets. + +**"Hook command not registered"** + +The manifest references an Artisan command that isn't registered in your service provider. Make sure +`native:plugin:make-hook` has updated your service provider, or add it manually. + +## Testing During Development + +### Test PHP Code + +Your PHP facades and event handling work like any Laravel code. Write standard PHPUnit tests: + +```php +public function test_plugin_facade_is_accessible() +{ + $this->assertInstanceOf(MyPlugin::class, app(MyPlugin::class)); +} +``` + +### Test Native Code + +Native code can only be tested by running the app. Use this workflow: + +1. Install your plugin locally via path repository +2. Run `php artisan native:run` +3. Trigger your plugin's functionality in the app +4. Check the console output for errors + + + +## Debugging Tips + +**Plugin not discovered?** +- Verify `composer.json` has `"type": "nativephp-plugin"` +- Run `composer dump-autoload` +- Check `php artisan native:plugin:list` + +**Native function not found at runtime?** +- Rebuild the app after changing native code +- Check the manifest's function names match exactly +- Verify the Kotlin package name is correct + +**Events not firing?** +- Confirm you're dispatching on the main thread +- Check the event class name matches the manifest +- Verify the `#[OnNative]` attribute uses the correct class From ffac7de51a7a011627262ac7bdbf7acc36832e61 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sun, 30 Nov 2025 21:40:06 -0500 Subject: [PATCH 2/3] pint --- app/Support/CommonMark/CommonMark.php | 1 - config/docs.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Support/CommonMark/CommonMark.php b/app/Support/CommonMark/CommonMark.php index f4b4568a..0f3c093f 100644 --- a/app/Support/CommonMark/CommonMark.php +++ b/app/Support/CommonMark/CommonMark.php @@ -3,7 +3,6 @@ namespace App\Support\CommonMark; use App\Extensions\TorchlightWithCopyExtension; -use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; diff --git a/config/docs.php b/config/docs.php index f50cdaa6..47e5dc63 100644 --- a/config/docs.php +++ b/config/docs.php @@ -18,4 +18,4 @@ 'mobile' => 2, ], -]; \ No newline at end of file +]; From c4573ea0e4d6c8f409e0f1d6c98865f7105b4b90 Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Tue, 23 Dec 2025 13:49:01 -0500 Subject: [PATCH 3/3] More updates --- package-lock.json | 2 +- .../2/plugins/advanced-configuration.md | 250 ++++++++++++++++++ .../docs/mobile/2/plugins/bridge-functions.md | 25 +- .../docs/mobile/2/plugins/creating-plugins.md | 116 ++++++-- .../docs/mobile/2/plugins/introduction.md | 25 ++ .../docs/mobile/2/plugins/lifecycle-hooks.md | 12 +- .../2/plugins/permissions-dependencies.md | 198 ++++++++++---- 7 files changed, 544 insertions(+), 84 deletions(-) create mode 100644 resources/views/docs/mobile/2/plugins/advanced-configuration.md diff --git a/package-lock.json b/package-lock.json index 2fcbd77d..abb3f776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "nativephp.com", + "name": "nativephp", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/views/docs/mobile/2/plugins/advanced-configuration.md b/resources/views/docs/mobile/2/plugins/advanced-configuration.md new file mode 100644 index 00000000..0256e31b --- /dev/null +++ b/resources/views/docs/mobile/2/plugins/advanced-configuration.md @@ -0,0 +1,250 @@ +--- +title: Advanced Configuration +order: 750 +--- + +## Secrets & Environment Variables + +Plugins that require API keys, tokens, or other sensitive configuration can declare required environment variables using +the `secrets` field. NativePHP validates these before building. + +```json +{ + "secrets": { + "MAPBOX_DOWNLOADS_TOKEN": { + "description": "Mapbox SDK download token from mapbox.com/account/access-tokens", + "required": true + }, + "FIREBASE_API_KEY": { + "description": "Firebase project API key", + "required": false + } + } +} +``` + +Each secret has: +- **description** — Instructions for obtaining the value +- **required** — Whether the build should fail if missing (default: `true`) + +### Using Secrets + +Reference secrets anywhere in your manifest using `${ENV_VAR}` syntax: + +```json +{ + "android": { + "repositories": [ + { + "url": "https://api.mapbox.com/downloads/v2/releases/maven", + "credentials": { + "password": "${MAPBOX_DOWNLOADS_TOKEN}" + } + } + ] + } +} +``` + +Placeholders are substituted at build time. If a required secret is missing, the build fails with a helpful message +telling users exactly which variables to set in their `.env` file. + +## Android Manifest Components + +Plugins can register Android components (Activities, Services, Receivers, Providers) that get merged into the app's +`AndroidManifest.xml`: + +```json +{ + "android": { + "activities": [ + { + "name": ".MyPluginActivity", + "theme": "@style/Theme.AppCompat.Light.NoActionBar", + "exported": false, + "configChanges": "orientation|screenSize" + } + ], + "services": [ + { + "name": ".BackgroundSyncService", + "exported": false, + "foregroundServiceType": "dataSync" + } + ], + "receivers": [ + { + "name": ".BootReceiver", + "exported": true, + "intent-filters": [ + { + "action": "android.intent.action.BOOT_COMPLETED", + "category": "android.intent.category.DEFAULT" + } + ] + } + ], + "providers": [ + { + "name": ".MyContentProvider", + "authorities": "${applicationId}.myplugin.provider", + "exported": false, + "grantUriPermissions": true + } + ] + } +} +``` + +### Component Names + +Names starting with `.` are relative to your plugin's package. For example, if your plugin uses the package +`com.nativephp.plugins.mlplugin`, then `.MyActivity` becomes `com.nativephp.plugins.mlplugin.MyActivity`. + +Use fully qualified names for components outside your plugin's package. + +### Activity Attributes + +| Attribute | Description | +|-----------|-------------| +| `name` | Component class name (required) | +| `theme` | Activity theme resource | +| `exported` | Whether other apps can start this activity | +| `configChanges` | Configuration changes the activity handles itself | +| `launchMode` | Launch mode (standard, singleTop, singleTask, singleInstance) | +| `screenOrientation` | Orientation lock (portrait, landscape, etc.) | +| `intent-filters` | Array of intent filter configurations | + +### Service Attributes + +| Attribute | Description | +|-----------|-------------| +| `name` | Component class name (required) | +| `exported` | Whether other apps can bind to this service | +| `permission` | Permission required to access the service | +| `foregroundServiceType` | Type for foreground services (camera, microphone, location, etc.) | + +## Declarative Assets + +Copy static files to the native projects using the `assets` field. This is simpler than writing a `copy_assets` hook for +basic file copying: + +```json +{ + "assets": { + "android": { + "models/detector.tflite": "assets/ml/detector.tflite", + "config/settings.xml": "res/raw/plugin_settings.xml" + }, + "ios": { + "models/detector.mlmodel": "Resources/ml/detector.mlmodel", + "config/settings.plist": "Resources/plugin_settings.plist" + } + } +} +``` + +The format is `"source": "destination"`: +- **source** — Relative path from your plugin's `resources/` directory +- **destination** — Where to place the file in the native project + +### Android Destinations + +- `assets/...` — App assets (accessible via `AssetManager`) +- `res/raw/...` — Raw resources (accessible via `R.raw.*`) +- `res/drawable/...` — Drawable resources + +### iOS Destinations + +- `Resources/...` — Bundle resources + +### Placeholder Substitution + +Text-based assets (XML, JSON, plist, etc.) support `${ENV_VAR}` placeholders that are replaced with environment +variable values during the build: + +```xml + + + ${MY_PLUGIN_API_KEY} + +``` + + + +## Complete Example + +Here's a complete manifest for a plugin that integrates Firebase ML Kit with a custom Activity: + +```json +{ + "name": "vendor/firebase-ml-plugin", + "namespace": "FirebaseML", + "version": "2.0.0", + "description": "Firebase ML Kit integration for NativePHP", + "bridge_functions": [ + { + "name": "FirebaseML.Analyze", + "android": "com.nativephp.plugins.firebaseml.AnalyzeFunctions.Analyze", + "ios": "FirebaseMLFunctions.Analyze" + } + ], + "events": [ + "Vendor\\FirebaseML\\Events\\AnalysisComplete" + ], + "android": { + "permissions": [ + "android.permission.CAMERA", + "android.permission.INTERNET" + ], + "dependencies": { + "implementation": [ + "com.google.firebase:firebase-ml-vision:24.1.0", + "com.google.firebase:firebase-core:21.1.1" + ] + }, + "activities": [ + { + "name": ".CameraPreviewActivity", + "theme": "@style/Theme.AppCompat.Light.NoActionBar", + "exported": false, + "configChanges": "orientation|screenSize|keyboardHidden" + } + ] + }, + "ios": { + "permissions": { + "NSCameraUsageDescription": "Camera is used for ML analysis" + }, + "dependencies": { + "pods": [ + {"name": "Firebase/MLVision", "version": "~> 10.0"}, + {"name": "Firebase/Core", "version": "~> 10.0"} + ] + } + }, + "assets": { + "android": { + "google-services.json": "google-services.json" + }, + "ios": { + "GoogleService-Info.plist": "Resources/GoogleService-Info.plist" + } + }, + "secrets": { + "FIREBASE_API_KEY": { + "description": "Firebase API key from Firebase Console", + "required": true + } + }, + "hooks": { + "pre_compile": "nativephp:firebase-ml:setup" + }, + "service_provider": "Vendor\\FirebaseML\\FirebaseMLServiceProvider" +} +``` diff --git a/resources/views/docs/mobile/2/plugins/bridge-functions.md b/resources/views/docs/mobile/2/plugins/bridge-functions.md index 31ab8b0a..48d1a747 100644 --- a/resources/views/docs/mobile/2/plugins/bridge-functions.md +++ b/resources/views/docs/mobile/2/plugins/bridge-functions.md @@ -24,7 +24,7 @@ In your `nativephp.json`, declare each function with its platform implementation { "name": "MyPlugin.DoSomething", "ios": "MyPluginFunctions.DoSomething", - "android": "com.vendor.plugin.myplugin.MyPluginFunctions.DoSomething", + "android": "com.myvendor.plugins.myplugin.MyPluginFunctions.DoSomething", "description": "Does something useful" } ] @@ -33,6 +33,12 @@ In your `nativephp.json`, declare each function with its platform implementation The `name` is what PHP uses. The platform-specific values point to your native class and method. +### Naming Convention + +- **`name`** — A unique identifier like `MyPlugin.DoSomething`. This is what PHP code uses. +- **`ios`** — Swift enum/class path: `EnumName.ClassName` +- **`android`** — Full Kotlin class path including your vendor package (e.g., `com.myvendor.plugins.myplugin.ClassName`) + ## Swift Implementation (iOS) Create your functions in `resources/ios/Sources/`: @@ -64,13 +70,13 @@ Key points: ## Kotlin Implementation (Android) -Create your functions in `resources/android/src/.../`: +Create your functions in `resources/android/src/`. Use your own vendor-namespaced package: ```kotlin -package com.vendor.plugin.myplugin +package com.myvendor.plugins.myplugin -import com.example.androidphp.bridge.BridgeFunction -import com.example.androidphp.bridge.BridgeResponse +import com.nativephp.mobile.bridge.BridgeFunction +import com.nativephp.mobile.bridge.BridgeResponse object MyPluginFunctions { @@ -89,13 +95,8 @@ object MyPluginFunctions { } ``` - +The package declaration determines where your file is placed during compilation. Using `com.myvendor.plugins.myplugin` ensures +your code is isolated from other plugins and the core NativePHP code. ## Calling from PHP diff --git a/resources/views/docs/mobile/2/plugins/creating-plugins.md b/resources/views/docs/mobile/2/plugins/creating-plugins.md index 1412f991..35c06b87 100644 --- a/resources/views/docs/mobile/2/plugins/creating-plugins.md +++ b/resources/views/docs/mobile/2/plugins/creating-plugins.md @@ -35,6 +35,46 @@ my-plugin/ │ └── js/ # JavaScript library stubs ``` +## Android Package Naming + +Android/Kotlin code must declare a package at the top of each file. Use your own vendor-namespaced package to avoid +conflicts: + +```kotlin +// resources/android/src/MyPluginFunctions.kt +package com.myvendor.plugins.myplugin + +import com.nativephp.mobile.bridge.BridgeFunction +import com.nativephp.mobile.bridge.BridgeResponse + +object MyPluginFunctions { + class DoSomething : BridgeFunction { + override fun execute(parameters: Map): Map { + return BridgeResponse.success(mapOf("status" to "done")) + } + } +} +``` + +The compiler places files based on their package declaration, so `package com.myvendor.plugins.myplugin` results in the file +being placed at `app/src/main/java/com/myvendor/plugins/myplugin/MyPluginFunctions.kt`. + + + +Reference the full package path in your manifest's bridge functions: + +```json +{ + "bridge_functions": [{ + "name": "MyPlugin.DoSomething", + "android": "com.myvendor.plugins.myplugin.MyPluginFunctions.DoSomething" + }] +} +``` + ## The composer.json Your `composer.json` must specify the plugin type: @@ -58,35 +98,56 @@ The `type: nativephp-plugin` tells NativePHP to look for native code in this pac ## The nativephp.json Manifest -The manifest declares everything about your plugin: +The manifest declares everything about your plugin. Platform-specific configuration is grouped under `android` and `ios` +keys: ```json { "name": "vendor/my-plugin", "namespace": "MyPlugin", + "version": "1.0.0", + "description": "A sample plugin", "bridge_functions": [ { "name": "MyPlugin.DoSomething", "ios": "MyPluginFunctions.DoSomething", - "android": "com.vendor.plugin.myplugin.MyPluginFunctions.DoSomething" + "android": "com.nativephp.plugins.myplugin.MyPluginFunctions.DoSomething" } ], "events": ["Vendor\\MyPlugin\\Events\\SomethingHappened"], - "permissions": { - "android": ["android.permission.SOME_PERMISSION"], - "ios": { - "NSCameraUsageDescription": "We need camera access" + "android": { + "permissions": ["android.permission.CAMERA"], + "dependencies": { + "implementation": ["com.google.mlkit:barcode-scanning:17.2.0"] + } + }, + "ios": { + "info_plist": { + "NSCameraUsageDescription": "Camera is used for scanning" + }, + "dependencies": { + "pods": [{"name": "GoogleMLKit/BarcodeScanning", "version": "~> 4.0"}] } } } ``` -The key fields: - -- **namespace** — Used to generate bridge function registration code -- **bridge_functions** — Maps PHP calls to native implementations -- **events** — Event classes your plugin dispatches -- **permissions** — Platform permissions your plugin requires +### Manifest Fields + +| Field | Required | Description | +|-------|----------|-------------| +| `name` | Yes | Package name in vendor/package format | +| `namespace` | Yes | PHP namespace for the plugin (used for code generation) | +| `version` | No | Semantic version (default: 1.0.0) | +| `description` | No | Human-readable description | +| `bridge_functions` | No | Array of native function mappings | +| `events` | No | Event classes the plugin dispatches | +| `android` | No | Android-specific configuration | +| `ios` | No | iOS-specific configuration | +| `assets` | No | Declarative asset copying | +| `hooks` | No | Lifecycle hook commands | +| `secrets` | No | Required environment variables | +| `service_provider` | No | Fully-qualified service provider class | ## Local Development @@ -142,13 +203,34 @@ export async function doSomething(options = {}) { Users can then import your functions directly in Vue, React, or vanilla JS. -## Plugin Writer Agent +## Claude Code Plugin -If you're building a complex plugin, install the plugin-writer agent to help with native code patterns: +If you're using [Claude Code](https://claude.com/claude-code), install the NativePHP plugin development tools from +the Claude Code Plugins marketplace. This gives you specialized agents and skills for writing native code. + +### Installation ```shell -php artisan native:plugin:install-agent +claude plugins:add https://github.com/NativePHP/ClaudePlugins/tree/main/nativephp-plugin-dev ``` -This copies a specialized agent configuration to your project's `.claude/agents/` directory. The agent understands -NativePHP plugin patterns and can help write Swift/Kotlin bridge functions, event dispatching, and hook commands. +### What's Included + +The plugin provides: + +- **Specialized Agents** — Expert agents for Kotlin/Android and Swift/iOS native code +- **Plugin Scaffold Command** — Run `/create-nativephp-plugin` to scaffold a complete plugin +- **Plugin Validator** — Run `/validate-nativephp-plugin` to check your plugin structure +- **Skills for Native Patterns** — Documentation for bridge functions, events, and architecture + +### Usage + +Once installed, you can: + +- Ask Claude Code to "create a NativePHP plugin for [your use case]" +- Run `/create-nativephp-plugin` to scaffold a new plugin interactively +- Run `/validate-nativephp-plugin` to validate your plugin structure +- Ask about native code patterns — the agents understand NativePHP conventions + +The agents are context-aware and will help you write correct Kotlin bridge functions, Swift implementations, +manifest configuration, and Laravel facades. diff --git a/resources/views/docs/mobile/2/plugins/introduction.md b/resources/views/docs/mobile/2/plugins/introduction.md index c4fbac81..7df761e1 100644 --- a/resources/views/docs/mobile/2/plugins/introduction.md +++ b/resources/views/docs/mobile/2/plugins/introduction.md @@ -24,6 +24,20 @@ platforms offer much more — AR, ML, NFC, health sensors, and countless third-p Plugins let the community build and share these integrations. Install a plugin and its features become available to your PHP code just like the built-in APIs. +## What Plugins Can Do + +Plugins have full access to native platform capabilities: + +- **Bridge functions** — Call Swift/Kotlin code from PHP and get results back +- **Events** — Dispatch events from native code to your Livewire components +- **Permissions** — Declare required permissions (camera, location, etc.) +- **Dependencies** — Include native libraries via Gradle, CocoaPods, or Swift Package Manager +- **Custom repositories** — Use private Maven repos for enterprise SDKs +- **Android components** — Register Activities, Services, Receivers, and Content Providers +- **Assets** — Bundle ML models, configuration files, and other resources +- **Lifecycle hooks** — Run code at build time to download models, validate config, etc. +- **Secrets** — Declare required environment variables with validation + ## Plugin Architecture Plugins follow the same patterns as NativePHP's core: @@ -45,3 +59,14 @@ public function handleResult($data) The native code runs on-device, communicates with your PHP through the bridge, and dispatches events back to your Livewire components. It's the same model you're already using. +## Getting Started + +Ready to build your own plugin? Check out [Creating Plugins](./creating-plugins) for the full guide. + +If you're using [Claude Code](https://claude.com/claude-code), you can install specialized plugin development tools: + +```shell +claude plugins:add https://github.com/NativePHP/ClaudePlugins/tree/main/nativephp-plugin-dev +``` + +This gives you expert agents for Kotlin and Swift, interactive scaffolding commands, and skills for native code patterns. diff --git a/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md b/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md index b327d2ea..8780cf50 100644 --- a/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md +++ b/resources/views/docs/mobile/2/plugins/lifecycle-hooks.md @@ -8,13 +8,23 @@ order: 600 Hooks let your plugin run code at specific points during the build process. Need to download an ML model before compilation? Copy assets to the right platform directory? Run validation? Hooks handle these scenarios. + + ## Available Hooks | Hook | When it Runs | |------|--------------| | `pre_compile` | Before native code compilation | | `post_compile` | After compilation, before build | -| `copy_assets` | When copying assets to native projects | +| `copy_assets` | When copying assets to native projects (runs after declarative asset copying) | | `post_build` | After a successful build | ## Creating Hook Commands diff --git a/resources/views/docs/mobile/2/plugins/permissions-dependencies.md b/resources/views/docs/mobile/2/plugins/permissions-dependencies.md index 39de8c60..77d2a177 100644 --- a/resources/views/docs/mobile/2/plugins/permissions-dependencies.md +++ b/resources/views/docs/mobile/2/plugins/permissions-dependencies.md @@ -3,19 +3,37 @@ title: Permissions & Dependencies order: 700 --- -## Declaring Permissions +## Platform Configuration -If your plugin needs platform permissions (camera, microphone, location, etc.), declare them in the manifest. -NativePHP automatically merges these with the app's permissions during build. +Platform-specific settings are grouped under `android` and `ios` keys in your manifest. This keeps all configuration for +each platform together: -## Android Permissions +```json +{ + "android": { + "permissions": [...], + "dependencies": {...}, + "repositories": [...], + "activities": [...], + "services": [...] + }, + "ios": { + "info_plist": {...}, + "dependencies": {...} + } +} +``` + +## Permissions -List Android permissions as strings: +### Android Permissions + +List Android permissions as strings under `android.permissions`: ```json { - "permissions": { - "android": [ + "android": { + "permissions": [ "android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.ACCESS_FINE_LOCATION" @@ -26,23 +44,30 @@ List Android permissions as strings: These are added to the app's `AndroidManifest.xml` at build time. -## iOS Permissions +### iOS Info.plist Entries -iOS requires usage descriptions for each permission. Provide these as key-value pairs: +iOS requires usage descriptions for each permission, plus any API keys or configuration tokens your plugin needs. Provide +these as key-value pairs under `ios.info_plist`: ```json { - "permissions": { - "ios": { + "ios": { + "info_plist": { "NSCameraUsageDescription": "This app uses the camera for scanning", "NSMicrophoneUsageDescription": "This app records audio for transcription", - "NSLocationWhenInUseUsageDescription": "This app needs your location" + "NSLocationWhenInUseUsageDescription": "This app needs your location", + "MBXAccessToken": "${MAPBOX_ACCESS_TOKEN}" } } } ``` -These are merged into the app's `Info.plist`. +These are merged into the app's `Info.plist`. You can include: +- Permission usage descriptions (`NS*UsageDescription` keys) +- API tokens and configuration keys +- Any other Info.plist entries your plugin requires + +Use `${ENV_VAR}` placeholders for sensitive values like API tokens. -## Android Dependencies +## Dependencies + +### Android Dependencies -Add Gradle dependencies for Android: +Add Gradle dependencies under `android.dependencies`: ```json { - "dependencies": { - "android": { + "android": { + "dependencies": { "implementation": [ "com.google.mlkit:face-detection:16.1.5", "org.tensorflow:tensorflow-lite:2.13.0" @@ -68,48 +95,57 @@ Add Gradle dependencies for Android: } ``` -These are added to the app's `build.gradle` during compilation. +These are added to the app's `build.gradle.kts` during compilation. You can use any Gradle dependency type: -## iOS Dependencies +- `implementation` — Standard dependency +- `api` — Exposed to consumers +- `compileOnly` — Compile-time only +- `runtimeOnly` — Runtime only -### Swift Packages +### iOS Dependencies -Add Swift Package dependencies for iOS: +#### CocoaPods + +For CocoaPods dependencies, use the `pods` array: ```json { - "dependencies": { - "ios": { - "swift_packages": [ - { - "url": "https://github.com/example/SomePackage", - "version": "1.0.0" - } + "ios": { + "dependencies": { + "pods": [ + {"name": "GoogleMLKit/FaceDetection", "version": "~> 4.0"}, + {"name": "TensorFlowLiteSwift", "version": "~> 2.13"} ] } } } ``` -### CocoaPods +Each pod object accepts: +- `name` — The pod name (required) +- `version` — Version constraint (optional, e.g., `~> 4.0`, `>= 1.0`) + +NativePHP generates a `Podfile` and runs `pod install` during the iOS build process. + +#### Swift Packages -For libraries that only support CocoaPods, add them to the `cocoapods` array: +For Swift Package Manager dependencies: ```json { - "dependencies": { - "ios": { - "cocoapods": [ - "GoogleMLKit/FaceDetection", - "TensorFlowLiteSwift" + "ios": { + "dependencies": { + "swift_packages": [ + { + "url": "https://github.com/example/SomePackage", + "version": "1.0.0" + } ] } } } ``` -NativePHP generates a `Podfile` and runs `pod install` during the iOS build process. - +## Custom Repositories + +Some dependencies require private or non-standard Maven repositories (like Mapbox). Add them under +`android.repositories`: + +```json +{ + "android": { + "repositories": [ + { + "url": "https://api.mapbox.com/downloads/v2/releases/maven", + "credentials": { + "username": "mapbox", + "password": "${MAPBOX_DOWNLOADS_TOKEN}" + } + } + ] + } +} +``` + +Repository configuration: +- `url` — The repository URL (required) +- `credentials` — Optional authentication + - `username` — Username or token name + - `password` — Password or token (supports `${ENV_VAR}` placeholders) + +These are added to the app's `settings.gradle.kts`. + ## Full Example -Here's a complete permissions and dependencies section for an ML plugin: +Here's a complete manifest for an ML plugin that uses Mapbox maps: ```json { - "permissions": { - "android": [ - "android.permission.CAMERA" + "name": "vendor/ml-maps-plugin", + "namespace": "MLMaps", + "android": { + "permissions": [ + "android.permission.CAMERA", + "android.permission.ACCESS_FINE_LOCATION" ], - "ios": { - "NSCameraUsageDescription": "Camera is used for real-time object detection" - } - }, - "dependencies": { - "android": { + "dependencies": { "implementation": [ - "com.google.mlkit:object-detection:17.0.0" + "com.google.mlkit:object-detection:17.0.0", + "com.mapbox.maps:android:11.0.0" + ] + }, + "repositories": [ + { + "url": "https://api.mapbox.com/downloads/v2/releases/maven", + "credentials": { + "username": "mapbox", + "password": "${MAPBOX_DOWNLOADS_TOKEN}" + } + } + ] + }, + "ios": { + "info_plist": { + "NSCameraUsageDescription": "Camera is used for real-time object detection", + "NSLocationWhenInUseUsageDescription": "Location is used to display your position on the map", + "MBXAccessToken": "${MAPBOX_PUBLIC_TOKEN}" + }, + "dependencies": { + "pods": [ + {"name": "MapboxMaps", "version": "~> 11.0"} ] + } + }, + "secrets": { + "MAPBOX_DOWNLOADS_TOKEN": { + "description": "Mapbox SDK download token from mapbox.com/account/access-tokens", + "required": true }, - "ios": { - "swift_packages": [] + "MAPBOX_PUBLIC_TOKEN": { + "description": "Mapbox public access token for runtime API calls", + "required": true } } }