From 6e25749cb575fc3124b16c12e922c5d27eb0751b Mon Sep 17 00:00:00 2001 From: Shane Rosenthal Date: Sun, 30 Nov 2025 21:36:39 -0500 Subject: [PATCH 1/2] 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 = '/_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() . '/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/2] 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 +];